2026-04-20 19:29:22 +02:00
const path = require ( 'path' ) ;
2026-04-19 10:26:34 +02:00
const { test , expect } = require ( '@playwright/test' ) ;
const {
2026-04-20 00:11:03 +02:00
LIVE _EXPECTED _RACE _STORAGE ,
2026-04-19 10:38:32 +02:00
LIVE _FACEAI _BASE _URL ,
LIVE _SITE _BASE _URL ,
2026-04-20 00:11:03 +02:00
LIVE _SITE _EXPECT _RACE _DATA _AVAILABLE ,
LIVE _SITE _EXPECT _UNAVAILABLE _REASON _CODE ,
2026-04-19 10:38:32 +02:00
LIVE _SITE _PORTRAIT _PATH ,
2026-04-20 00:11:03 +02:00
LIVE _SITE _RACE _ID ,
2026-04-19 10:26:34 +02:00
LIVE _SITE _RACE _URL ,
2026-04-19 10:38:32 +02:00
LIVE _SITE _RUN _UPLOAD _FLOW ,
2026-04-20 00:11:03 +02:00
LIVE _SITE _SAMPLE _PHOTO _IDS ,
2026-04-19 10:38:32 +02:00
ensureLiveAuthenticatedRacePage ,
2026-04-20 00:11:03 +02:00
hasExpectedRaceStorage ,
2026-04-19 10:38:32 +02:00
requirePortraitFixture
2026-04-19 10:26:34 +02:00
} = require ( './live-site-test-utils' ) ;
2026-04-19 10:38:32 +02:00
function escapeRegExp ( value ) {
return String ( value ) . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ;
}
2026-04-20 00:11:03 +02:00
async function readFaceAiSession ( page ) {
return page . evaluate ( async ( ) => {
const response = await fetch ( '/api/session' , { credentials : 'include' } ) ;
const payload = await response . json ( ) . catch ( ( ) => null ) ;
return {
ok : response . ok ,
status : response . status ,
payload
} ;
} ) ;
}
2026-04-19 10:38:32 +02:00
async function openLiveFaceAi ( page ) {
const consoleErrors = [ ] ;
page . on ( 'console' , ( message ) => {
if ( message . type ( ) === 'error' ) {
consoleErrors . push ( message . text ( ) ) ;
}
} ) ;
await ensureLiveAuthenticatedRacePage ( page ) ;
2026-04-20 00:11:03 +02:00
await expect ( page . locator ( 'h1' ) ) . not . toHaveText ( /^\s*$/ ) ;
2026-04-19 10:26:34 +02:00
2026-04-19 10:38:32 +02:00
const launchUrl = await page . evaluate ( ( ) => {
return typeof buildFaceAiLaunchUrl === 'function' ? buildFaceAiLaunchUrl ( ) : '' ;
} ) ;
expect ( launchUrl , 'Expected the legacy race page script to expose a FaceAI handoff URL builder.' ) . toBeTruthy ( ) ;
const parsedLaunchUrl = new URL ( launchUrl , LIVE _SITE _BASE _URL ) ;
2026-04-20 00:11:03 +02:00
expect ( parsedLaunchUrl . searchParams . get ( 'raceId' ) || '' ) . toBeTruthy ( ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'returnUrl' ) || '' ) . toBeTruthy ( ) ;
if ( hasExpectedRaceStorage ( ) ) {
expect ( parsedLaunchUrl . searchParams . get ( 'raceYear' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . year ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceMonthFolder' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . monthFolder ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceFolder' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . raceFolder ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceStorageRelativeDir' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . relativeDir ) ;
}
2026-04-19 10:38:32 +02:00
await expect ( page . locator ( '#faceaiLaunchButton' ) ) . toBeVisible ( ) ;
await page . locator ( '#faceaiLaunchButton' ) . click ( ) ;
await page . waitForURL ( new RegExp ( ` ^ ${ escapeRegExp ( LIVE _FACEAI _BASE _URL ) } / ` ) , {
timeout : 60 * 1000
} ) ;
await expect ( page . getByRole ( 'heading' , { name : /Trova le tue foto con un selfie|Find your photos with a selfie/i } ) ) . toBeVisible ( ) ;
2026-04-20 00:11:03 +02:00
const sessionResponse = await readFaceAiSession ( page ) ;
expect ( sessionResponse . ok , 'Expected the live FaceAI app to expose a readable session payload after launch.' ) . toBe ( true ) ;
2026-04-19 10:38:32 +02:00
return {
consoleErrors ,
2026-04-20 00:11:03 +02:00
launchUrl : parsedLaunchUrl ,
session : sessionResponse . payload
2026-04-19 10:38:32 +02:00
} ;
}
2026-04-19 11:00:50 +02:00
async function waitForSearchCompletion ( page , searchId ) {
return expect . poll ( async ( ) => {
return page . evaluate ( async ( id ) => {
const response = await fetch ( ` /api/searches/ ${ id } ` , { credentials : 'include' } ) ;
if ( ! response . ok ) {
return {
status : 'poll-error' ,
httpStatus : response . status
} ;
}
const payload = await response . json ( ) ;
return {
status : payload . status ,
errorMessage : payload . errorMessage || null ,
completionCode : payload . completionCode || null ,
matchCount : payload . matchCount ? ? null ,
resultId : payload . resultId || null
} ;
} , searchId ) ;
} , {
timeout : 3 * 60 * 1000 ,
message : ` Expected FaceAI search ${ searchId } to complete successfully. `
} ) . toMatchObject ( {
status : 'completed'
} ) ;
}
2026-04-19 14:18:00 +02:00
async function readVisibleLegacyPhotoIds ( page ) {
return page . locator ( '#demo [data-faceai-photo-id]' ) . evaluateAll ( ( elements ) => {
return elements . map ( ( element ) => String ( element . getAttribute ( 'data-faceai-photo-id' ) || '' ) . trim ( ) ) . filter ( Boolean ) ;
} ) ;
}
2026-04-20 19:29:22 +02:00
async function readFaceAiMatchState ( page ) {
return page . evaluate ( ( ) => {
if ( typeof getFaceAiMatchState === 'function' ) {
return getFaceAiMatchState ( ) ;
}
return window . faceAiMatchState || null ;
} ) ;
}
2026-04-19 14:18:00 +02:00
async function waitForVisibleLegacyPhotoIds ( page , expectedCount ) {
await expect . poll ( async ( ) => {
const visiblePhotoIds = await readVisibleLegacyPhotoIds ( page ) ;
return visiblePhotoIds . length ;
} , {
timeout : 30 * 1000 ,
message : 'Expected the legacy FaceAI gallery to finish loading matched thumbnails.'
} ) . toBe ( expectedCount ) ;
return readVisibleLegacyPhotoIds ( page ) ;
}
async function waitForVisibleLegacyThumbs ( page , expectedCount ) {
const thumbs = page . locator ( '#demo [data-faceai-photo-id] img.thumb' ) ;
await expect ( thumbs ) . toHaveCount ( expectedCount ) ;
await expect . poll ( async ( ) => {
return thumbs . evaluateAll ( ( elements ) => {
return elements . every ( ( element ) => {
const src = String ( element . getAttribute ( 'src' ) || '' ) . trim ( ) ;
return src . length > 0 && src . indexOf ( '_imgNotFound' ) === - 1 ;
} ) ;
} ) ;
} , {
timeout : 30 * 1000 ,
message : 'Expected the legacy FaceAI gallery to resolve real thumbnail image URLs.'
} ) . toBe ( true ) ;
return thumbs ;
}
async function waitForLegacyFaceAiCount ( page , expectedCount ) {
await expect . poll ( async ( ) => {
return page . locator ( '#faceAiPhotoCountValue' ) . textContent ( ) ;
} , {
timeout : 30 * 1000 ,
message : 'Expected the legacy FaceAI count to match the resolved gallery size.'
} ) . toBe ( String ( expectedCount ) ) ;
}
2026-04-19 16:12:48 +02:00
function basenameOfPhotoKey ( photoKey ) {
return String ( photoKey ) . replace ( /\\/g , '/' ) . split ( '/' ) . pop ( ) ;
}
async function lookupLivePhoto ( page , photoKey ) {
return page . evaluate ( async ( value ) => {
const lookupData = getFaceAiPhotoLookupData ( value ) ;
const params = new URLSearchParams ( lookupData . request ) ;
const response = await fetch ( ` ${ getFaceAiLookupEndpoint ( ) } ? ${ params . toString ( ) } ` , {
credentials : 'include'
} ) ;
const text = await response . text ( ) ;
let payload = null ;
try {
payload = JSON . parse ( text ) ;
} catch ( error ) {
payload = {
parseError : String ( error ) ,
raw : text
} ;
}
return {
status : response . status ,
request : lookupData . request ,
payload
} ;
} , photoKey ) ;
}
2026-04-19 14:18:00 +02:00
async function expectLegacyFaceAiGalleryToRemainStable ( page , expectedPhotoIds , holdMs = 4000 ) {
await page . waitForTimeout ( holdMs ) ;
const visiblePhotoIds = await readVisibleLegacyPhotoIds ( page ) ;
expect ( visiblePhotoIds . sort ( ) ) . toEqual ( expectedPhotoIds . slice ( ) . sort ( ) ) ;
await expect ( page . locator ( '#demo [data-faceai-photo-id]' ) ) . toHaveCount ( expectedPhotoIds . length ) ;
await waitForVisibleLegacyThumbs ( page , expectedPhotoIds . length ) ;
await waitForLegacyFaceAiCount ( page , expectedPhotoIds . length ) ;
}
test ( 'renders the exact live FaceAI filtered sample URL with visible thumbnails' , async ( { page } ) => {
2026-04-20 00:11:03 +02:00
const samplePhotoIds = LIVE _SITE _SAMPLE _PHOTO _IDS ;
test . skip ( ! samplePhotoIds . length , 'Set LIVE_SITE_SAMPLE_PHOTO_IDS to validate a race-specific filtered sample URL.' ) ;
const sampleUrl = ` ${ LIVE _SITE _RACE _URL } ?faceaiMatchSource=faceai&faceaiMatchCount= ${ samplePhotoIds . length } &faceaiPhotoIds= ${ encodeURIComponent ( samplePhotoIds . join ( ',' ) ) } ` ;
2026-04-19 14:18:00 +02:00
await ensureLiveAuthenticatedRacePage ( page ) ;
await page . goto ( sampleUrl , {
waitUntil : 'domcontentloaded'
} ) ;
await expect ( page . locator ( 'form[onsubmit="return searching()"]' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#faceAiFilterBanner' ) ) . toContainText ( /Face ID filter active|Filtro Face ID attivo/i ) ;
const visiblePhotoIds = await waitForVisibleLegacyPhotoIds ( page , samplePhotoIds . length ) ;
expect ( visiblePhotoIds . sort ( ) ) . toEqual ( samplePhotoIds . slice ( ) . sort ( ) ) ;
await waitForVisibleLegacyThumbs ( page , samplePhotoIds . length ) ;
await waitForLegacyFaceAiCount ( page , samplePhotoIds . length ) ;
await expectLegacyFaceAiGalleryToRemainStable ( page , samplePhotoIds ) ;
} ) ;
2026-04-20 00:11:03 +02:00
test ( 'keeps the live FaceAI race storage metadata pinned to the stored server path' , async ( { page } ) => {
test . skip ( ! hasExpectedRaceStorage ( ) , 'Set LIVE_SITE_EXPECTED_RACE_* values to validate exact race storage metadata.' ) ;
2026-04-19 16:12:48 +02:00
await ensureLiveAuthenticatedRacePage ( page ) ;
await expect ( page . locator ( '#faceAiRaceYear' ) ) . toHaveValue ( LIVE _EXPECTED _RACE _STORAGE . year ) ;
await expect ( page . locator ( '#faceAiRaceMonthFolder' ) ) . toHaveValue ( LIVE _EXPECTED _RACE _STORAGE . monthFolder ) ;
await expect ( page . locator ( '#faceAiRaceFolder' ) ) . toHaveValue ( LIVE _EXPECTED _RACE _STORAGE . raceFolder ) ;
await expect ( page . locator ( '#faceAiRaceStorageRelativeDir' ) ) . toHaveValue ( LIVE _EXPECTED _RACE _STORAGE . relativeDir ) ;
const launchUrl = await page . evaluate ( ( ) => {
return typeof buildFaceAiLaunchUrl === 'function' ? buildFaceAiLaunchUrl ( ) : '' ;
} ) ;
const parsedLaunchUrl = new URL ( launchUrl , LIVE _SITE _BASE _URL ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceYear' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . year ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceMonthFolder' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . monthFolder ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceFolder' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . raceFolder ) ;
expect ( parsedLaunchUrl . searchParams . get ( 'raceStorageRelativeDir' ) ) . toBe ( LIVE _EXPECTED _RACE _STORAGE . relativeDir ) ;
} ) ;
2026-04-20 00:11:03 +02:00
test ( 'resolves the live sample photo lookups inside the current race' , async ( { page } ) => {
const samplePhotoIds = LIVE _SITE _SAMPLE _PHOTO _IDS ;
test . skip ( ! samplePhotoIds . length , 'Set LIVE_SITE_SAMPLE_PHOTO_IDS to validate race-specific photo lookups.' ) ;
2026-04-19 16:12:48 +02:00
await ensureLiveAuthenticatedRacePage ( page ) ;
for ( const photoKey of samplePhotoIds ) {
const lookup = await lookupLivePhoto ( page , photoKey ) ;
expect ( lookup . status , ` Expected faceai_photo_lookup.jsp to resolve ${ photoKey } on the live Firenze race page. ` ) . toBe ( 200 ) ;
2026-04-20 00:11:03 +02:00
expect ( String ( lookup . request . raceId || '' ) ) . toBe ( LIVE _SITE _RACE _ID ) ;
2026-04-19 16:12:48 +02:00
expect ( lookup . payload && lookup . payload . found , ` Expected ${ photoKey } to resolve within the current live race. ` ) . toBe ( true ) ;
expect ( String ( lookup . payload . photoId || '' ) ) . toBe ( photoKey ) ;
expect ( basenameOfPhotoKey ( String ( lookup . payload . resolvedFile || '' ) ) ) . toBe ( basenameOfPhotoKey ( photoKey ) ) ;
expect ( String ( lookup . payload . thumbSrc || '' ) ) . toContain ( ` +tn- ${ lookup . payload . legacyId } .jpg ` ) ;
}
} ) ;
2026-04-19 10:38:32 +02:00
test ( 'loads a live race page with an authenticated session' , async ( { page } ) => {
await ensureLiveAuthenticatedRacePage ( page ) ;
2026-04-19 10:26:34 +02:00
const cookies = await page . context ( ) . cookies ( LIVE _SITE _RACE _URL ) ;
const faceAiIdentityCookie = cookies . find ( ( cookie ) => cookie . name === 'rus_faceai_identity' ) ;
expect ( faceAiIdentityCookie , 'Expected the race page to mint the FaceAI identity cookie for the authenticated session.' ) . toBeTruthy ( ) ;
expect ( faceAiIdentityCookie . httpOnly ) . toBe ( true ) ;
expect ( faceAiIdentityCookie . secure ) . toBe ( true ) ;
expect ( faceAiIdentityCookie . value ) . toMatch ( /\./ ) ;
2026-04-19 10:38:32 +02:00
} ) ;
test ( 'launches the live FaceAI app with race storage metadata and a styled header' , async ( { page } ) => {
2026-04-20 00:11:03 +02:00
const { consoleErrors , launchUrl , session } = await openLiveFaceAi ( page ) ;
2026-04-19 10:38:32 +02:00
2026-04-20 00:11:03 +02:00
expect ( launchUrl . searchParams . get ( 'raceId' ) ) . toBe ( LIVE _SITE _RACE _ID ) ;
expect ( launchUrl . searchParams . get ( 'raceStorageRelativeDir' ) || '' ) . toBeTruthy ( ) ;
2026-04-19 10:38:32 +02:00
await expect ( page . locator ( 'nav.navbar' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( 'link[data-legacy-href*="bootstrap.min.css"]' ) ) . toHaveCount ( 1 ) ;
await expect ( page . locator ( 'link[data-legacy-href*="custom-style.css"]' ) ) . toHaveCount ( 1 ) ;
2026-04-19 11:00:50 +02:00
await expect ( page . locator ( 'link[data-legacy-href*="font-awesome"]' ) ) . toHaveCount ( 0 ) ;
2026-04-19 10:38:32 +02:00
const legacyStylesheetHrefs = await page . locator ( 'link[data-legacy-href]' ) . evaluateAll ( ( elements ) => {
return elements . map ( ( element ) => element . getAttribute ( 'href' ) || '' ) ;
} ) ;
expect ( legacyStylesheetHrefs . some ( ( href ) => href . startsWith ( ` ${ LIVE _SITE _BASE _URL } / ` ) ) ) . toBe ( true ) ;
const navComputedStyles = await page . locator ( 'nav.navbar' ) . evaluate ( ( element ) => {
const styles = window . getComputedStyle ( element ) ;
return {
position : styles . position ,
display : styles . display
} ;
} ) ;
expect ( navComputedStyles . position ) . toBe ( 'fixed' ) ;
expect ( navComputedStyles . display ) . not . toBe ( 'block' ) ;
2026-04-20 00:11:03 +02:00
if ( LIVE _SITE _EXPECT _RACE _DATA _AVAILABLE ) {
expect ( session ? . availability ? . available ) . toBe ( true ) ;
expect ( consoleErrors . find ( ( entry ) => entry . includes ( 'MISSING_RACE_STORAGE' ) ) || '' ) . toBe ( '' ) ;
expect ( consoleErrors . find ( ( entry ) => entry . includes ( '[FaceAI] Invalid race data:' ) ) || '' ) . toBe ( '' ) ;
await expect ( page . locator ( 'input[type="file"]' ) ) . toBeEnabled ( ) ;
} else {
expect ( session ? . availability ? . available ) . toBe ( false ) ;
expect ( session ? . availability ? . reasonCode ) . toBe ( LIVE _SITE _EXPECT _UNAVAILABLE _REASON _CODE ) ;
await expect ( page . locator ( 'input[type="file"]' ) ) . toBeDisabled ( ) ;
await expect ( page . locator ( '.faceai-feedback' ) ) . toContainText ( /FaceAI data is not available for this race|I dati FaceAI non sono disponibili per questa gara|The race data received for this session is invalid/i ) ;
}
2026-04-19 10:38:32 +02:00
} ) ;
2026-04-19 12:10:18 +02:00
test ( 'returns to the live race page from FaceAI without leaving the legacy spinner stuck' , async ( { page } ) => {
await openLiveFaceAi ( page ) ;
await page . getByRole ( 'button' , { name : /Torna alla pagina gara|Back to the race page/i } ) . click ( ) ;
await page . waitForURL ( ( url ) => {
return url . toString ( ) . startsWith ( LIVE _SITE _BASE _URL ) && ! url . toString ( ) . startsWith ( LIVE _FACEAI _BASE _URL ) ;
} , {
2026-04-19 14:18:00 +02:00
timeout : 60 * 1000 ,
waitUntil : 'commit'
2026-04-19 12:10:18 +02:00
} ) ;
await expect ( page . locator ( 'form[onsubmit="return searching()"]' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#faceaiLaunchButton' ) ) . toBeVisible ( ) ;
const bodyState = await page . locator ( 'body' ) . evaluate ( ( element ) => {
return {
className : element . className ,
ariaBusy : element . getAttribute ( 'aria-busy' )
} ;
} ) ;
expect ( bodyState . className ) . not . toContain ( 'loading' ) ;
expect ( bodyState . ariaBusy ) . not . toBe ( 'true' ) ;
} ) ;
2026-04-19 10:38:32 +02:00
test ( 'accepts the supplied portrait image for the live upload flow' , async ( { page } ) => {
2026-04-19 14:18:00 +02:00
test . slow ( ) ;
2026-04-19 16:59:04 +02:00
test . skip ( ! LIVE _SITE _RUN _UPLOAD _FLOW , 'Set LIVE_SITE_RUN_UPLOAD_FLOW=1 to exercise the live upload flow.' ) ;
2026-04-20 00:11:03 +02:00
test . skip ( ! LIVE _SITE _EXPECT _RACE _DATA _AVAILABLE , 'Set LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE=1 when the target race has FaceAI data available.' ) ;
2026-04-19 14:18:00 +02:00
2026-04-19 10:38:32 +02:00
requirePortraitFixture ( ) ;
await openLiveFaceAi ( page ) ;
const fileInput = page . locator ( 'input[type="file"]' ) ;
await expect ( fileInput ) . toBeEnabled ( ) ;
await fileInput . setInputFiles ( LIVE _SITE _PORTRAIT _PATH ) ;
2026-04-20 19:29:22 +02:00
await expect ( page . locator ( '.faceai-file-name' ) ) . toContainText ( path . basename ( LIVE _SITE _PORTRAIT _PATH ) ) ;
2026-04-19 10:38:32 +02:00
const searchResponsePromise = page . waitForResponse ( ( response ) => {
return response . request ( ) . method ( ) === 'POST' && response . url ( ) . includes ( '/api/searches' ) ;
} , {
timeout : 60 * 1000
} ) ;
await page . getByRole ( 'button' , { name : /Avvia ricerca Face ID|Start Face ID search/i } ) . click ( ) ;
const searchResponse = await searchResponsePromise ;
expect ( searchResponse . ok ( ) , 'Expected the live upload flow to create a FaceAI search successfully.' ) . toBe ( true ) ;
const searchPayload = await searchResponse . json ( ) ;
2026-04-19 11:00:50 +02:00
const searchId = searchPayload . id || searchPayload . searchId ;
expect ( searchId , 'Expected the search creation response to include a search identifier.' ) . toBeTruthy ( ) ;
const completion = await page . evaluate ( async ( id ) => {
const response = await fetch ( ` /api/searches/ ${ id } ` , { credentials : 'include' } ) ;
return response . json ( ) ;
} , searchId ) . catch ( ( ) => null ) ;
if ( completion ? . status === 'failed' ) {
throw new Error ( ` FaceAI search ${ searchId } failed immediately: ${ completion . errorMessage || 'unknown error' } ` ) ;
}
await waitForSearchCompletion ( page , searchId ) ;
await expect . poll ( async ( ) => page . url ( ) , {
2026-04-19 14:18:00 +02:00
timeout : 30 * 1000 ,
message : 'Expected the browser to land on the legacy race page with FaceAI filter parameters after FaceAI completed.'
2026-04-20 19:29:22 +02:00
} ) . toMatch ( new RegExp ( ` ^ ${ escapeRegExp ( LIVE _SITE _BASE _URL ) } /.*(?:faceaiPhotoIds=|faceaiMatchStorageKey=) ` ) ) ;
2026-04-19 14:18:00 +02:00
await expect ( page . locator ( 'form[onsubmit="return searching()"]' ) ) . toBeVisible ( ) ;
await expect ( page . locator ( '#faceAiFilterBanner' ) ) . toContainText ( /Face ID filter active|Filtro Face ID attivo/i ) ;
await expect ( page . locator ( '.gallery-card' ) ) . toHaveCount ( 0 ) ;
const finalUrl = new URL ( page . url ( ) ) ;
2026-04-20 19:29:22 +02:00
await expect . poll ( async ( ) => {
const matchState = await readFaceAiMatchState ( page ) ;
return Array . isArray ( matchState && matchState . photoIds ) ? matchState . photoIds . length : 0 ;
} , {
timeout : 30 * 1000 ,
message : 'Expected the legacy race page to resolve FaceAI match state after redirect.'
} ) . toBeGreaterThan ( 0 ) ;
const matchState = await readFaceAiMatchState ( page ) ;
const expectedPhotoIds = Array . isArray ( matchState . photoIds ) ? matchState . photoIds . map ( ( value ) => String ( value || '' ) . trim ( ) ) . filter ( Boolean ) : [ ] ;
2026-04-19 14:18:00 +02:00
expect ( expectedPhotoIds . length , 'Expected the final race page URL to include at least one FaceAI photo identifier.' ) . toBeGreaterThan ( 0 ) ;
2026-04-20 19:29:22 +02:00
expect ( Number ( finalUrl . searchParams . get ( 'faceaiMatchCount' ) || 0 ) ) . toBe ( expectedPhotoIds . length ) ;
2026-04-19 14:18:00 +02:00
2026-04-19 16:59:04 +02:00
for ( const photoKey of expectedPhotoIds ) {
const lookup = await lookupLivePhoto ( page , photoKey ) ;
expect ( lookup . status , ` Expected FaceAI to return a photo key that resolves on the current live race page: ${ photoKey } ` ) . toBe ( 200 ) ;
expect ( lookup . payload && lookup . payload . found , ` Expected FaceAI to return a photo key that exists in the current live race: ${ photoKey } ` ) . toBe ( true ) ;
}
2026-04-19 14:18:00 +02:00
const visiblePhotoIds = await waitForVisibleLegacyPhotoIds ( page , expectedPhotoIds . length ) ;
expect ( visiblePhotoIds . length , 'Expected at least one legacy race thumbnail to remain visible after FaceAI filtering.' ) . toBeGreaterThan ( 0 ) ;
expect ( visiblePhotoIds . sort ( ) ) . toEqual ( expectedPhotoIds . slice ( ) . sort ( ) ) ;
await waitForVisibleLegacyThumbs ( page , expectedPhotoIds . length ) ;
await waitForLegacyFaceAiCount ( page , expectedPhotoIds . length ) ;
await expectLegacyFaceAiGalleryToRemainStable ( page , expectedPhotoIds ) ;
} ) ;