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

This commit is contained in:
MaddoScientisto 2026-04-20 00:11:03 +02:00
commit c0d072c6ea
6 changed files with 130 additions and 47 deletions

View file

@ -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',

View file

@ -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);

View file

@ -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"

View file

@ -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'));

View file

@ -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();

View file

@ -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,