feat: Update FaceAI upload panel and improve race storage metadata handling in tests
All checks were successful
Publish FaceAI Container / publish (push) Successful in 4m32s
All checks were successful
Publish FaceAI Container / publish (push) Successful in 4m32s
This commit is contained in:
parent
0926c52a00
commit
c0d072c6ea
6 changed files with 130 additions and 47 deletions
|
|
@ -46,8 +46,8 @@ const props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
fileInput: {
|
assignFileInput: {
|
||||||
type: Object,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
t: {
|
t: {
|
||||||
|
|
@ -56,10 +56,6 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function assignFileInput(element) {
|
|
||||||
props.fileInput.value = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'open-file-picker',
|
'open-file-picker',
|
||||||
'file-change',
|
'file-change',
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ const knownServerCodes = {
|
||||||
RATE_LIMITED: 'rateLimited',
|
RATE_LIMITED: 'rateLimited',
|
||||||
MISSING_SELFIE: 'chooseSelfie',
|
MISSING_SELFIE: 'chooseSelfie',
|
||||||
RACE_PKL_UNAVAILABLE: 'raceDataUnavailable',
|
RACE_PKL_UNAVAILABLE: 'raceDataUnavailable',
|
||||||
RACE_DIRECTORY_NOT_FOUND: 'invalidRaceData',
|
RACE_DIRECTORY_NOT_FOUND: 'raceDataUnavailable',
|
||||||
MISSING_RACE_STORAGE: 'invalidRaceData'
|
MISSING_RACE_STORAGE: 'invalidRaceData'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ const simulatorUrl = legacyUrl('/faceai_simulator.php?raceId=101&lang=it');
|
||||||
const legacyHomeUrl = legacyUrl('/');
|
const legacyHomeUrl = legacyUrl('/');
|
||||||
|
|
||||||
function isInvalidRaceAvailability(availability) {
|
function isInvalidRaceAvailability(availability) {
|
||||||
return availability?.reasonCode === 'RACE_DIRECTORY_NOT_FOUND' || availability?.reasonCode === 'MISSING_RACE_STORAGE';
|
return availability?.reasonCode === 'MISSING_RACE_STORAGE';
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLegacyReturnUrl(url) {
|
function buildLegacyReturnUrl(url) {
|
||||||
|
|
@ -204,8 +204,9 @@ export function useFaceAiHome() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailabilityUserMessage(availability, fallbackKey = 'unavailableDefault') {
|
function getAvailabilityUserMessage(availability, fallbackKey = 'unavailableDefault') {
|
||||||
if (isInvalidRaceAvailability(availability)) {
|
const mappedCodeKey = availability?.reasonCode ? knownServerCodes[availability.reasonCode] : null;
|
||||||
return t('invalidRaceData');
|
if (mappedCodeKey) {
|
||||||
|
return t(mappedCodeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return localizeServerMessage(availability?.message, fallbackKey);
|
return localizeServerMessage(availability?.message, fallbackKey);
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ const {
|
||||||
submitSearch,
|
submitSearch,
|
||||||
t
|
t
|
||||||
} = useFaceAiHome();
|
} = useFaceAiHome();
|
||||||
|
|
||||||
|
function assignFileInput(element) {
|
||||||
|
fileInput.value = element;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -63,7 +67,7 @@ const {
|
||||||
:selected-file="selectedFile"
|
:selected-file="selectedFile"
|
||||||
:selected-file-size-label="selectedFileSizeLabel"
|
:selected-file-size-label="selectedFileSizeLabel"
|
||||||
:can-start-search="canStartSearch"
|
:can-start-search="canStartSearch"
|
||||||
:file-input="fileInput"
|
:assign-file-input="assignFileInput"
|
||||||
:t="t"
|
:t="t"
|
||||||
@open-file-picker="openFilePicker"
|
@open-file-picker="openFilePicker"
|
||||||
@file-change="onFileChange"
|
@file-change="onFileChange"
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,13 @@ test('builds the simulator FaceAI handoff URL with the exact local race storage
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shows the unsupported-race message when the current race has no PKL data and lets the user go back', async ({ page }) => {
|
test('shows the unsupported-race message when the current race has no PKL data and lets the user go back', async ({ page }) => {
|
||||||
|
const consoleErrors = [];
|
||||||
|
page.on('console', (message) => {
|
||||||
|
if (message.type() === 'error') {
|
||||||
|
consoleErrors.push(message.text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await launchFromSimulator(page, {
|
await launchFromSimulator(page, {
|
||||||
raceId: '404',
|
raceId: '404',
|
||||||
raceSlug: 'corsa-di-livorno',
|
raceSlug: 'corsa-di-livorno',
|
||||||
|
|
@ -211,6 +218,9 @@ test('shows the unsupported-race message when the current race has no PKL data a
|
||||||
await expect(page.locator('.faceai-feedback')).toContainText('FaceAI non è disponibile per questa gara.');
|
await expect(page.locator('.faceai-feedback')).toContainText('FaceAI non è disponibile per questa gara.');
|
||||||
await expect(page.locator('input[type="file"]')).toBeDisabled();
|
await expect(page.locator('input[type="file"]')).toBeDisabled();
|
||||||
await expect(page.getByRole('button', { name: 'Scegli immagine' })).toBeDisabled();
|
await expect(page.getByRole('button', { name: 'Scegli immagine' })).toBeDisabled();
|
||||||
|
await expect.poll(() => {
|
||||||
|
return consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || null;
|
||||||
|
}).toBeNull();
|
||||||
|
|
||||||
await page.waitForTimeout(2000);
|
await page.waitForTimeout(2000);
|
||||||
await expect(page).toHaveURL(FACEAI_HOME_URL_RE);
|
await expect(page).toHaveURL(FACEAI_HOME_URL_RE);
|
||||||
|
|
@ -219,7 +229,7 @@ test('shows the unsupported-race message when the current race has no PKL data a
|
||||||
await expect(page).toHaveURL(buildLegacySimulatorReturnMatcher('404'));
|
await expect(page).toHaveURL(buildLegacySimulatorReturnMatcher('404'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shows a localized invalid-race error when session race data points to a missing folder', async ({ page }) => {
|
test('shows a localized invalid-race error when the handoff omits race storage metadata', async ({ page }) => {
|
||||||
const consoleErrors = [];
|
const consoleErrors = [];
|
||||||
page.on('console', (message) => {
|
page.on('console', (message) => {
|
||||||
if (message.type() === 'error') {
|
if (message.type() === 'error') {
|
||||||
|
|
@ -227,17 +237,18 @@ test('shows a localized invalid-race error when session race data points to a mi
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const simulatorUrl = buildSimulatorUrl({
|
const handoffUrl = new URL(buildHandoffUrl({
|
||||||
raceId: '405',
|
raceId: '405',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
raceSlug: 'ghost-race',
|
raceSlug: 'ghost-race',
|
||||||
raceName: 'Ghost Race',
|
raceName: 'Ghost Race',
|
||||||
raceFolder: 'THIS RACE DOES NOT EXIST'
|
raceFolder: 'THIS RACE DOES NOT EXIST'
|
||||||
});
|
}));
|
||||||
|
handoffUrl.searchParams.delete('raceYear');
|
||||||
|
handoffUrl.searchParams.delete('raceMonthFolder');
|
||||||
|
handoffUrl.searchParams.delete('raceFolder');
|
||||||
|
|
||||||
await page.goto(simulatorUrl, { waitUntil: 'domcontentloaded' });
|
await page.goto(handoffUrl.toString(), { waitUntil: 'domcontentloaded' });
|
||||||
await expect(page.locator('#faceaiLaunchButton')).toBeVisible();
|
|
||||||
await page.locator('#faceaiLaunchButton').click();
|
|
||||||
|
|
||||||
await page.waitForURL(FACEAI_HOME_URL_RE, {
|
await page.waitForURL(FACEAI_HOME_URL_RE, {
|
||||||
timeout: SHORT_UI_TIMEOUT_MS
|
timeout: SHORT_UI_TIMEOUT_MS
|
||||||
|
|
@ -250,10 +261,10 @@ test('shows a localized invalid-race error when session race data points to a mi
|
||||||
await expect(page.getByRole('button', { name: 'Back to the race page' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Back to the race page' })).toBeVisible();
|
||||||
await expect.poll(() => {
|
await expect.poll(() => {
|
||||||
return consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || null;
|
return consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || null;
|
||||||
}).toContain('RACE_DIRECTORY_NOT_FOUND');
|
}).toContain('MISSING_RACE_STORAGE');
|
||||||
await expect.poll(() => {
|
await expect.poll(() => {
|
||||||
return consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || null;
|
return consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || null;
|
||||||
}).toContain('THIS RACE DOES NOT EXIST');
|
}).toContain('MISSING_RACE_STORAGE');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Back to the race page' }).click();
|
await page.getByRole('button', { name: 'Back to the race page' }).click();
|
||||||
await expect(page).toHaveURL(buildLegacySimulatorReturnMatcher('405'));
|
await expect(page).toHaveURL(buildLegacySimulatorReturnMatcher('405'));
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,36 @@
|
||||||
const { test, expect } = require('@playwright/test');
|
const { test, expect } = require('@playwright/test');
|
||||||
const {
|
const {
|
||||||
|
LIVE_EXPECTED_RACE_STORAGE,
|
||||||
LIVE_FACEAI_BASE_URL,
|
LIVE_FACEAI_BASE_URL,
|
||||||
LIVE_SITE_BASE_URL,
|
LIVE_SITE_BASE_URL,
|
||||||
|
LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE,
|
||||||
|
LIVE_SITE_EXPECT_UNAVAILABLE_REASON_CODE,
|
||||||
LIVE_SITE_PORTRAIT_PATH,
|
LIVE_SITE_PORTRAIT_PATH,
|
||||||
|
LIVE_SITE_RACE_ID,
|
||||||
LIVE_SITE_RACE_URL,
|
LIVE_SITE_RACE_URL,
|
||||||
LIVE_SITE_RUN_UPLOAD_FLOW,
|
LIVE_SITE_RUN_UPLOAD_FLOW,
|
||||||
|
LIVE_SITE_SAMPLE_PHOTO_IDS,
|
||||||
ensureLiveAuthenticatedRacePage,
|
ensureLiveAuthenticatedRacePage,
|
||||||
|
hasExpectedRaceStorage,
|
||||||
requirePortraitFixture
|
requirePortraitFixture
|
||||||
} = require('./live-site-test-utils');
|
} = require('./live-site-test-utils');
|
||||||
|
|
||||||
const LIVE_EXPECTED_RACE_STORAGE = {
|
|
||||||
year: '2026',
|
|
||||||
monthFolder: '03.MARZO',
|
|
||||||
raceFolder: 'HMF_2026',
|
|
||||||
relativeDir: '2026/03.MARZO/HMF_2026'
|
|
||||||
};
|
|
||||||
|
|
||||||
const LIVE_SAMPLE_PHOTO_IDS = [
|
|
||||||
'21.ARRIVO_CON TEMPO\\DSC_8385.JPG',
|
|
||||||
'21.ARRIVO_CON TEMPO\\DSC_8295.JPG'
|
|
||||||
];
|
|
||||||
|
|
||||||
function escapeRegExp(value) {
|
function escapeRegExp(value) {
|
||||||
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function openLiveFaceAi(page) {
|
async function openLiveFaceAi(page) {
|
||||||
const consoleErrors = [];
|
const consoleErrors = [];
|
||||||
|
|
||||||
|
|
@ -35,7 +41,7 @@ async function openLiveFaceAi(page) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await ensureLiveAuthenticatedRacePage(page);
|
await ensureLiveAuthenticatedRacePage(page);
|
||||||
await expect(page.locator('h1')).toContainText(/HALF MARATHON FIRENZE|Competitions|Gare/i);
|
await expect(page.locator('h1')).not.toHaveText(/^\s*$/);
|
||||||
|
|
||||||
const launchUrl = await page.evaluate(() => {
|
const launchUrl = await page.evaluate(() => {
|
||||||
return typeof buildFaceAiLaunchUrl === 'function' ? buildFaceAiLaunchUrl() : '';
|
return typeof buildFaceAiLaunchUrl === 'function' ? buildFaceAiLaunchUrl() : '';
|
||||||
|
|
@ -44,10 +50,15 @@ async function openLiveFaceAi(page) {
|
||||||
expect(launchUrl, 'Expected the legacy race page script to expose a FaceAI handoff URL builder.').toBeTruthy();
|
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);
|
const parsedLaunchUrl = new URL(launchUrl, LIVE_SITE_BASE_URL);
|
||||||
expect(parsedLaunchUrl.searchParams.get('raceYear')).toBe(LIVE_EXPECTED_RACE_STORAGE.year);
|
expect(parsedLaunchUrl.searchParams.get('raceId') || '').toBeTruthy();
|
||||||
expect(parsedLaunchUrl.searchParams.get('raceMonthFolder')).toBe(LIVE_EXPECTED_RACE_STORAGE.monthFolder);
|
expect(parsedLaunchUrl.searchParams.get('returnUrl') || '').toBeTruthy();
|
||||||
expect(parsedLaunchUrl.searchParams.get('raceFolder')).toBe(LIVE_EXPECTED_RACE_STORAGE.raceFolder);
|
|
||||||
expect(parsedLaunchUrl.searchParams.get('raceStorageRelativeDir')).toBe(LIVE_EXPECTED_RACE_STORAGE.relativeDir);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
await expect(page.locator('#faceaiLaunchButton')).toBeVisible();
|
await expect(page.locator('#faceaiLaunchButton')).toBeVisible();
|
||||||
await page.locator('#faceaiLaunchButton').click();
|
await page.locator('#faceaiLaunchButton').click();
|
||||||
|
|
@ -57,9 +68,13 @@ async function openLiveFaceAi(page) {
|
||||||
});
|
});
|
||||||
await expect(page.getByRole('heading', { name: /Trova le tue foto con un selfie|Find your photos with a selfie/i })).toBeVisible();
|
await expect(page.getByRole('heading', { name: /Trova le tue foto con un selfie|Find your photos with a selfie/i })).toBeVisible();
|
||||||
|
|
||||||
|
const sessionResponse = await readFaceAiSession(page);
|
||||||
|
expect(sessionResponse.ok, 'Expected the live FaceAI app to expose a readable session payload after launch.').toBe(true);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
consoleErrors,
|
consoleErrors,
|
||||||
launchUrl: parsedLaunchUrl
|
launchUrl: parsedLaunchUrl,
|
||||||
|
session: sessionResponse.payload
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,8 +195,10 @@ async function expectLegacyFaceAiGalleryToRemainStable(page, expectedPhotoIds, h
|
||||||
}
|
}
|
||||||
|
|
||||||
test('renders the exact live FaceAI filtered sample URL with visible thumbnails', async ({ page }) => {
|
test('renders the exact live FaceAI filtered sample URL with visible thumbnails', async ({ page }) => {
|
||||||
const samplePhotoIds = LIVE_SAMPLE_PHOTO_IDS;
|
const samplePhotoIds = LIVE_SITE_SAMPLE_PHOTO_IDS;
|
||||||
const sampleUrl = `${LIVE_SITE_BASE_URL}/42%20HALF%20MARATHON%20FIRENZE_gara-1018545---48-1.html?faceaiMatchSource=faceai&faceaiMatchCount=2&faceaiPhotoIds=${encodeURIComponent(samplePhotoIds.join(','))}`;
|
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(','))}`;
|
||||||
|
|
||||||
await ensureLiveAuthenticatedRacePage(page);
|
await ensureLiveAuthenticatedRacePage(page);
|
||||||
await page.goto(sampleUrl, {
|
await page.goto(sampleUrl, {
|
||||||
|
|
@ -199,7 +216,9 @@ test('renders the exact live FaceAI filtered sample URL with visible thumbnails'
|
||||||
await expectLegacyFaceAiGalleryToRemainStable(page, samplePhotoIds);
|
await expectLegacyFaceAiGalleryToRemainStable(page, samplePhotoIds);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('keeps the live Firenze FaceAI race storage metadata pinned to the stored server path', async ({ page }) => {
|
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.');
|
||||||
|
|
||||||
await ensureLiveAuthenticatedRacePage(page);
|
await ensureLiveAuthenticatedRacePage(page);
|
||||||
|
|
||||||
await expect(page.locator('#faceAiRaceYear')).toHaveValue(LIVE_EXPECTED_RACE_STORAGE.year);
|
await expect(page.locator('#faceAiRaceYear')).toHaveValue(LIVE_EXPECTED_RACE_STORAGE.year);
|
||||||
|
|
@ -218,15 +237,16 @@ test('keeps the live Firenze FaceAI race storage metadata pinned to the stored s
|
||||||
expect(parsedLaunchUrl.searchParams.get('raceStorageRelativeDir')).toBe(LIVE_EXPECTED_RACE_STORAGE.relativeDir);
|
expect(parsedLaunchUrl.searchParams.get('raceStorageRelativeDir')).toBe(LIVE_EXPECTED_RACE_STORAGE.relativeDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolves the live Firenze sample photo lookups inside the current race', async ({ page }) => {
|
test('resolves the live sample photo lookups inside the current race', async ({ page }) => {
|
||||||
const samplePhotoIds = LIVE_SAMPLE_PHOTO_IDS;
|
const samplePhotoIds = LIVE_SITE_SAMPLE_PHOTO_IDS;
|
||||||
|
test.skip(!samplePhotoIds.length, 'Set LIVE_SITE_SAMPLE_PHOTO_IDS to validate race-specific photo lookups.');
|
||||||
|
|
||||||
await ensureLiveAuthenticatedRacePage(page);
|
await ensureLiveAuthenticatedRacePage(page);
|
||||||
|
|
||||||
for (const photoKey of samplePhotoIds) {
|
for (const photoKey of samplePhotoIds) {
|
||||||
const lookup = await lookupLivePhoto(page, photoKey);
|
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);
|
expect(lookup.status, `Expected faceai_photo_lookup.jsp to resolve ${photoKey} on the live Firenze race page.`).toBe(200);
|
||||||
expect(String(lookup.request.raceId || '')).toBe('1018545');
|
expect(String(lookup.request.raceId || '')).toBe(LIVE_SITE_RACE_ID);
|
||||||
expect(lookup.payload && lookup.payload.found, `Expected ${photoKey} to resolve within the current live race.`).toBe(true);
|
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(String(lookup.payload.photoId || '')).toBe(photoKey);
|
||||||
expect(basenameOfPhotoKey(String(lookup.payload.resolvedFile || ''))).toBe(basenameOfPhotoKey(photoKey));
|
expect(basenameOfPhotoKey(String(lookup.payload.resolvedFile || ''))).toBe(basenameOfPhotoKey(photoKey));
|
||||||
|
|
@ -247,9 +267,10 @@ test('loads a live race page with an authenticated session', async ({ page }) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
test('launches the live FaceAI app with race storage metadata and a styled header', async ({ page }) => {
|
test('launches the live FaceAI app with race storage metadata and a styled header', async ({ page }) => {
|
||||||
const { consoleErrors, launchUrl } = await openLiveFaceAi(page);
|
const { consoleErrors, launchUrl, session } = await openLiveFaceAi(page);
|
||||||
|
|
||||||
expect(launchUrl.searchParams.get('raceStorageRelativeDir')).toBe(LIVE_EXPECTED_RACE_STORAGE.relativeDir);
|
expect(launchUrl.searchParams.get('raceId')).toBe(LIVE_SITE_RACE_ID);
|
||||||
|
expect(launchUrl.searchParams.get('raceStorageRelativeDir') || '').toBeTruthy();
|
||||||
await expect(page.locator('nav.navbar')).toBeVisible();
|
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*="bootstrap.min.css"]')).toHaveCount(1);
|
||||||
await expect(page.locator('link[data-legacy-href*="custom-style.css"]')).toHaveCount(1);
|
await expect(page.locator('link[data-legacy-href*="custom-style.css"]')).toHaveCount(1);
|
||||||
|
|
@ -271,8 +292,18 @@ test('launches the live FaceAI app with race storage metadata and a styled heade
|
||||||
|
|
||||||
expect(navComputedStyles.position).toBe('fixed');
|
expect(navComputedStyles.position).toBe('fixed');
|
||||||
expect(navComputedStyles.display).not.toBe('block');
|
expect(navComputedStyles.display).not.toBe('block');
|
||||||
expect(consoleErrors.find((entry) => entry.includes('MISSING_RACE_STORAGE')) || '').toBe('');
|
|
||||||
expect(consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || '').toBe('');
|
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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns to the live race page from FaceAI without leaving the legacy spinner stuck', async ({ page }) => {
|
test('returns to the live race page from FaceAI without leaving the legacy spinner stuck', async ({ page }) => {
|
||||||
|
|
@ -304,6 +335,7 @@ test('returns to the live race page from FaceAI without leaving the legacy spinn
|
||||||
test('accepts the supplied portrait image for the live upload flow', async ({ page }) => {
|
test('accepts the supplied portrait image for the live upload flow', async ({ page }) => {
|
||||||
test.slow();
|
test.slow();
|
||||||
test.skip(!LIVE_SITE_RUN_UPLOAD_FLOW, 'Set LIVE_SITE_RUN_UPLOAD_FLOW=1 to exercise the live upload flow.');
|
test.skip(!LIVE_SITE_RUN_UPLOAD_FLOW, 'Set LIVE_SITE_RUN_UPLOAD_FLOW=1 to exercise the live upload flow.');
|
||||||
|
test.skip(!LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE, 'Set LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE=1 when the target race has FaceAI data available.');
|
||||||
|
|
||||||
requirePortraitFixture();
|
requirePortraitFixture();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,40 @@ const LIVE_SITE_USERNAME = process.env.LIVE_SITE_USERNAME || '';
|
||||||
const LIVE_SITE_PASSWORD = process.env.LIVE_SITE_PASSWORD || '';
|
const LIVE_SITE_PASSWORD = process.env.LIVE_SITE_PASSWORD || '';
|
||||||
const LIVE_SITE_PORTRAIT_PATH = process.env.LIVE_SITE_PORTRAIT_PATH || path.join(WORKSPACE_ROOT, 'test_pkl', 'live', 'test_portrait_1.png');
|
const LIVE_SITE_PORTRAIT_PATH = process.env.LIVE_SITE_PORTRAIT_PATH || path.join(WORKSPACE_ROOT, 'test_pkl', 'live', 'test_portrait_1.png');
|
||||||
const LIVE_SITE_RUN_UPLOAD_FLOW = process.env.LIVE_SITE_RUN_UPLOAD_FLOW === '1';
|
const LIVE_SITE_RUN_UPLOAD_FLOW = process.env.LIVE_SITE_RUN_UPLOAD_FLOW === '1';
|
||||||
|
const LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE = process.env.LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE !== '0';
|
||||||
|
const LIVE_SITE_EXPECT_UNAVAILABLE_REASON_CODE = process.env.LIVE_SITE_EXPECT_UNAVAILABLE_REASON_CODE || 'RACE_DIRECTORY_NOT_FOUND';
|
||||||
const AUTH_FILE = path.join(__dirname, '.auth', 'user.json');
|
const AUTH_FILE = path.join(__dirname, '.auth', 'user.json');
|
||||||
|
|
||||||
|
function parseOptionalCsv(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.split(',')
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRaceIdFromUrl(value) {
|
||||||
|
const match = String(value || '').match(/_gara-(\d+)---/i);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIVE_SITE_RACE_ID = process.env.LIVE_SITE_RACE_ID || parseRaceIdFromUrl(LIVE_SITE_RACE_URL);
|
||||||
|
const LIVE_EXPECTED_RACE_STORAGE = {
|
||||||
|
year: String(process.env.LIVE_SITE_EXPECTED_RACE_YEAR || '').trim(),
|
||||||
|
monthFolder: String(process.env.LIVE_SITE_EXPECTED_RACE_MONTH_FOLDER || '').trim(),
|
||||||
|
raceFolder: String(process.env.LIVE_SITE_EXPECTED_RACE_FOLDER || '').trim(),
|
||||||
|
relativeDir: String(process.env.LIVE_SITE_EXPECTED_RACE_STORAGE_RELATIVE_DIR || '').trim()
|
||||||
|
};
|
||||||
|
const LIVE_SITE_SAMPLE_PHOTO_IDS = parseOptionalCsv(process.env.LIVE_SITE_SAMPLE_PHOTO_IDS);
|
||||||
|
|
||||||
|
function hasExpectedRaceStorage() {
|
||||||
|
return Boolean(
|
||||||
|
LIVE_EXPECTED_RACE_STORAGE.year
|
||||||
|
&& LIVE_EXPECTED_RACE_STORAGE.monthFolder
|
||||||
|
&& LIVE_EXPECTED_RACE_STORAGE.raceFolder
|
||||||
|
&& LIVE_EXPECTED_RACE_STORAGE.relativeDir
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ensureAuthDirectory() {
|
function ensureAuthDirectory() {
|
||||||
fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true });
|
fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
@ -155,19 +187,26 @@ function requirePortraitFixture() {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
AUTH_FILE,
|
AUTH_FILE,
|
||||||
LIVE_FACEAI_BASE_URL,
|
LIVE_FACEAI_BASE_URL,
|
||||||
|
LIVE_EXPECTED_RACE_STORAGE,
|
||||||
LIVE_SITE_BASE_URL,
|
LIVE_SITE_BASE_URL,
|
||||||
LIVE_SITE_LOGIN_URL,
|
LIVE_SITE_LOGIN_URL,
|
||||||
LIVE_SITE_PASSWORD,
|
LIVE_SITE_PASSWORD,
|
||||||
LIVE_SITE_PORTRAIT_PATH,
|
LIVE_SITE_PORTRAIT_PATH,
|
||||||
|
LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE,
|
||||||
|
LIVE_SITE_EXPECT_UNAVAILABLE_REASON_CODE,
|
||||||
|
LIVE_SITE_RACE_ID,
|
||||||
LIVE_SITE_RACE_URL,
|
LIVE_SITE_RACE_URL,
|
||||||
LIVE_SITE_RESULT_URL_PATTERN,
|
LIVE_SITE_RESULT_URL_PATTERN,
|
||||||
LIVE_SITE_RUN_UPLOAD_FLOW,
|
LIVE_SITE_RUN_UPLOAD_FLOW,
|
||||||
|
LIVE_SITE_SAMPLE_PHOTO_IDS,
|
||||||
LIVE_SITE_USERNAME,
|
LIVE_SITE_USERNAME,
|
||||||
dismissCookieBanner,
|
dismissCookieBanner,
|
||||||
ensureLiveAuthenticatedRacePage,
|
ensureLiveAuthenticatedRacePage,
|
||||||
ensureAuthDirectory,
|
ensureAuthDirectory,
|
||||||
expectRacePageLoaded,
|
expectRacePageLoaded,
|
||||||
|
hasExpectedRaceStorage,
|
||||||
loginSubmitLocator,
|
loginSubmitLocator,
|
||||||
|
parseRaceIdFromUrl,
|
||||||
performLiveLogin,
|
performLiveLogin,
|
||||||
performLiveLoginRequest,
|
performLiveLoginRequest,
|
||||||
requirePortraitFixture,
|
requirePortraitFixture,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue