Regalamiunsorriso/faceai/tests/live-site/live-race.spec.js
MaddoScientisto f19b8e20ff
Some checks failed
Publish FaceAI Container / publish (push) Failing after 4s
Enhance Git LFS validation in CI workflow and update Docker Compose configuration
2026-04-19 12:10:18 +02:00

202 lines
No EOL
7.6 KiB
JavaScript

const { test, expect } = require('@playwright/test');
const {
LIVE_FACEAI_BASE_URL,
LIVE_SITE_BASE_URL,
LIVE_SITE_PORTRAIT_PATH,
LIVE_SITE_RACE_URL,
LIVE_SITE_RESULT_URL_PATTERN,
LIVE_SITE_RUN_UPLOAD_FLOW,
ensureLiveAuthenticatedRacePage,
requirePortraitFixture
} = require('./live-site-test-utils');
function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
async function openLiveFaceAi(page) {
const consoleErrors = [];
page.on('console', (message) => {
if (message.type() === 'error') {
consoleErrors.push(message.text());
}
});
await ensureLiveAuthenticatedRacePage(page);
await expect(page.locator('h1')).toContainText(/HALF MARATHON FIRENZE|Competitions|Gare/i);
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);
expect(parsedLaunchUrl.searchParams.get('raceYear')).toBeTruthy();
expect(parsedLaunchUrl.searchParams.get('raceMonthFolder')).toBeTruthy();
expect(parsedLaunchUrl.searchParams.get('raceFolder')).toBeTruthy();
expect(parsedLaunchUrl.searchParams.get('raceStorageRelativeDir')).toBeTruthy();
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();
return {
consoleErrors,
launchUrl: parsedLaunchUrl
};
}
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'
});
}
test('loads a live race page with an authenticated session', async ({ page }) => {
await ensureLiveAuthenticatedRacePage(page);
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(/\./);
});
test('launches the live FaceAI app with race storage metadata and a styled header', async ({ page }) => {
const { consoleErrors, launchUrl } = await openLiveFaceAi(page);
expect(launchUrl.searchParams.get('raceStorageRelativeDir')).toContain('/');
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);
await expect(page.locator('link[data-legacy-href*="font-awesome"]')).toHaveCount(0);
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');
expect(consoleErrors.find((entry) => entry.includes('MISSING_RACE_STORAGE')) || '').toBe('');
expect(consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || '').toBe('');
});
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);
}, {
timeout: 60 * 1000
});
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');
});
test.skip(!LIVE_SITE_RUN_UPLOAD_FLOW, 'Set LIVE_SITE_RUN_UPLOAD_FLOW=1 to exercise the live upload flow.');
test('accepts the supplied portrait image for the live upload flow', async ({ page }) => {
requirePortraitFixture();
await openLiveFaceAi(page);
const fileInput = page.locator('input[type="file"]');
await expect(fileInput).toBeEnabled();
await fileInput.setInputFiles(LIVE_SITE_PORTRAIT_PATH);
await expect(page.locator('.faceai-file-name')).toContainText('test_portrait_1.png');
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();
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 page.waitForURL(new RegExp(`^${escapeRegExp(LIVE_SITE_RESULT_URL_PATTERN)}`), {
timeout: 3 * 60 * 1000
});
await expect.poll(async () => page.url(), {
timeout: 15 * 1000,
message: 'Expected the browser to land on the legacy result page after FaceAI completed.'
}).toMatch(new RegExp(`^${escapeRegExp(LIVE_SITE_BASE_URL)}/`));
await expect(page.locator('body')).toContainText(/Vista filtrata da FaceAI|foto da FaceAI|ID foto:/i);
await expect.poll(async () => page.locator('.gallery-card').count(), {
timeout: 15 * 1000,
message: 'Expected the legacy return page to render at least one FaceAI result card.'
}).toBeGreaterThan(0);
});