feat: Enhance FaceAI functionality and improve login process
All checks were successful
Publish FaceAI Container / publish (push) Successful in 4m43s
All checks were successful
Publish FaceAI Container / publish (push) Successful in 4m43s
- Added a retry mechanism for page navigation in `live-site-test-utils.js` to handle transient errors during login. - Introduced a new function `performLiveLoginRequest` to handle login requests via API, improving the login flow. - Updated the login process to utilize the new API request method, ensuring a more robust authentication. - Implemented new utility functions in `rus-ecom-240621.js` for managing FaceAI state and filtering. - Created `faceai_photo_lookup.jsp` to handle photo lookups, returning JSON responses for better integration with the frontend. - Updated `faceai_return.php` to redirect users with appropriate parameters after FaceAI processing. - Modified `fotoCR.jsp` and `fotoCR-en.jsp` to include FaceAI photo IDs in the image elements for better tracking. - Enhanced the UI to display the number of matched photos dynamically based on FaceAI results.
This commit is contained in:
parent
6f191de115
commit
bba8026b7c
17 changed files with 1077 additions and 95 deletions
|
|
@ -11,8 +11,12 @@ const {
|
|||
|
||||
const FACEAI_HOME_URL_RE = /http:\/\/(localhost|127\.0\.0\.1):3001\/?(?:\?.*)?$/;
|
||||
const FACEAI_CALLBACK_URL_RE = /http:\/\/(localhost|127\.0\.0\.1):3001\/auth\/callback\?token=/;
|
||||
const FACEAI_RETURN_URL_RE = /http:\/\/(localhost|127\.0\.0\.1):8080\/faceai_return\.php\?resultId=.*token=.*/;
|
||||
const LEGACY_HOME_URL_RE = /http:\/\/(localhost|127\.0\.0\.1):8080\/index\.jsp$/;
|
||||
const LONG_TEST_TIMEOUT_MS = 3 * 60 * 1000;
|
||||
const SHORT_UI_TIMEOUT_MS = 30 * 1000;
|
||||
const SEARCH_COMPLETION_TIMEOUT_MS = 75 * 1000;
|
||||
const LEGACY_RETURN_TIMEOUT_MS = 75 * 1000;
|
||||
const FILE_CHOOSER_TIMEOUT_MS = 8 * 1000;
|
||||
|
||||
function buildLegacySimulatorReturnMatcher(raceId) {
|
||||
return new RegExp(`http:\\/\\/(localhost|127\\.0\\.0\\.1):8080\\/faceai_simulator\\.php\\?raceId=${raceId}.*`);
|
||||
|
|
@ -26,7 +30,7 @@ function assertLogDoesNotContain(content, patterns, label) {
|
|||
|
||||
async function waitForFaceAiHome(page) {
|
||||
await page.waitForURL((url) => FACEAI_CALLBACK_URL_RE.test(url.toString()) || FACEAI_HOME_URL_RE.test(url.toString()), {
|
||||
timeout: 60 * 1000
|
||||
timeout: SHORT_UI_TIMEOUT_MS
|
||||
});
|
||||
await expect(page.getByRole('heading', { name: 'Trova le tue foto con un selfie' })).toBeVisible();
|
||||
}
|
||||
|
|
@ -89,18 +93,24 @@ async function waitForSearchCondition(page, searchId, predicate, timeoutMs = 30
|
|||
throw new Error(`Timed out waiting for search ${searchId}. Last payload: ${JSON.stringify(lastPayload)}`);
|
||||
}
|
||||
|
||||
async function waitForLegacyResult(page, expectedMatchCount = null) {
|
||||
await page.waitForURL(FACEAI_RETURN_URL_RE, {
|
||||
timeout: 6 * 60 * 1000
|
||||
async function waitForLegacyResult(page, raceId, expectedMatchCount = null) {
|
||||
await page.waitForURL(buildLegacySimulatorReturnMatcher(raceId), {
|
||||
timeout: LEGACY_RETURN_TIMEOUT_MS,
|
||||
waitUntil: 'commit'
|
||||
});
|
||||
await expect(page.locator('.sim-banner')).toContainText('Vista filtrata da FaceAI');
|
||||
|
||||
await expect.poll(() => page.url(), {
|
||||
timeout: 15 * 1000,
|
||||
message: 'Expected the legacy simulator return URL to include FaceAI filter parameters.'
|
||||
}).toMatch(/faceaiPhotoIds=/);
|
||||
|
||||
if (expectedMatchCount === null) {
|
||||
await expect(page.locator('.gallery-card').first()).toBeVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
await expect(page.locator('.sim-banner')).toContainText(String(expectedMatchCount));
|
||||
await expect(page.locator('.gallery-card')).toHaveCount(expectedMatchCount);
|
||||
const finalUrl = new URL(page.url());
|
||||
const photoIds = (finalUrl.searchParams.get('faceaiPhotoIds') || '').split(',').map((value) => value.trim()).filter(Boolean);
|
||||
expect(photoIds.length).toBe(expectedMatchCount);
|
||||
}
|
||||
|
||||
async function verifySearchLogs(searchId, { expectedMatchCount, expectedSelfieName }) {
|
||||
|
|
@ -129,10 +139,20 @@ async function verifySearchLogs(searchId, { expectedMatchCount, expectedSelfieNa
|
|||
}
|
||||
|
||||
async function closeContexts(contexts) {
|
||||
await Promise.all(contexts.map((context) => context.close()));
|
||||
await Promise.all(contexts.map(async (context) => {
|
||||
try {
|
||||
await context.close();
|
||||
} catch (error) {
|
||||
if (!/ENOENT|Target page, context or browser has been closed/i.test(String(error))) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
test('runs the simulator flow through FaceAI and returns to the filtered legacy result', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
await launchFromSimulator(page, {
|
||||
raceId: '202',
|
||||
raceSlug: 'mezza-di-pisa',
|
||||
|
|
@ -142,8 +162,7 @@ test('runs the simulator flow through FaceAI and returns to the filtered legacy
|
|||
|
||||
const search = await startSearch(page, 'DSC_1960.JPG');
|
||||
|
||||
await waitForLegacyResult(page, EXPECTED_MATCH_COUNT);
|
||||
await expect(page.locator('.gallery-card').filter({ hasText: 'DSC_1960.JPG' }).first()).toBeVisible();
|
||||
await waitForLegacyResult(page, '202', EXPECTED_MATCH_COUNT);
|
||||
|
||||
await verifySearchLogs(search.id, {
|
||||
expectedMatchCount: EXPECTED_MATCH_COUNT,
|
||||
|
|
@ -191,7 +210,7 @@ test('shows a localized invalid-race error when session race data points to a mi
|
|||
await page.locator('#faceaiLaunchButton').click();
|
||||
|
||||
await page.waitForURL(FACEAI_HOME_URL_RE, {
|
||||
timeout: 60 * 1000
|
||||
timeout: SHORT_UI_TIMEOUT_MS
|
||||
});
|
||||
await expect(page.getByRole('heading', { name: 'Find your photos with a selfie' })).toBeVisible();
|
||||
await expect(page.locator('.faceai-feedback')).toContainText('The race data received for this session is invalid. Go back to the race page and reopen Face ID from the correct race.');
|
||||
|
|
@ -240,6 +259,8 @@ test('rejects a not-logged-in user after clicking the Face ID button and sends t
|
|||
});
|
||||
|
||||
test('shows the no-face message and allows the user to return to the race page', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
await launchFromSimulator(page, {
|
||||
raceId: '202',
|
||||
raceSlug: 'mezza-di-pisa',
|
||||
|
|
@ -251,7 +272,7 @@ test('shows the no-face message and allows the user to return to the race page',
|
|||
|
||||
await waitForSearchCondition(page, search.id, (payload) => {
|
||||
return payload.status === 'completed' && payload.completionCode === 'NO_FACES_FOUND';
|
||||
}, 2 * 60 * 1000);
|
||||
}, SEARCH_COMPLETION_TIMEOUT_MS);
|
||||
|
||||
await expect(page.locator('.faceai-feedback')).toContainText('Nessun volto rilevato nella foto caricata');
|
||||
await page.waitForTimeout(2000);
|
||||
|
|
@ -266,7 +287,26 @@ test('shows the no-face message and allows the user to return to the race page',
|
|||
await expect(page).toHaveURL(buildLegacySimulatorReturnMatcher('202'));
|
||||
});
|
||||
|
||||
test('opens the file chooser when the user clicks the upload button', async ({ page }) => {
|
||||
await launchFromSimulator(page, {
|
||||
raceId: '202',
|
||||
raceSlug: 'mezza-di-pisa',
|
||||
raceName: 'Mezza di Pisa',
|
||||
raceFolder: 'PISA'
|
||||
});
|
||||
|
||||
const fileChooserPromise = page.waitForEvent('filechooser', { timeout: FILE_CHOOSER_TIMEOUT_MS });
|
||||
await page.getByRole('button', { name: 'Scegli immagine' }).click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
|
||||
await fileChooser.setFiles(getSelfiePath('DSC_1960.JPG'));
|
||||
await expect(page.getByText('DSC_1960.JPG')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Avvia ricerca Face ID' })).toBeEnabled();
|
||||
});
|
||||
|
||||
test('lets the user retry with a valid photo after a no-face upload and then returns to the filtered legacy result', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
await launchFromSimulator(page, {
|
||||
raceId: '202',
|
||||
raceSlug: 'mezza-di-pisa',
|
||||
|
|
@ -277,13 +317,13 @@ test('lets the user retry with a valid photo after a no-face upload and then ret
|
|||
const noFaceSearch = await startSearch(page, 'DSC_1994.JPG');
|
||||
await waitForSearchCondition(page, noFaceSearch.id, (payload) => {
|
||||
return payload.status === 'completed' && payload.completionCode === 'NO_FACES_FOUND';
|
||||
}, 2 * 60 * 1000);
|
||||
}, SEARCH_COMPLETION_TIMEOUT_MS);
|
||||
|
||||
await expect(page.locator('.faceai-feedback')).toContainText('Nessun volto rilevato nella foto caricata');
|
||||
await expect(page.locator('input[type="file"]')).toBeEnabled();
|
||||
|
||||
const retrySearch = await startSearch(page, 'DSC_1960.JPG');
|
||||
await waitForLegacyResult(page, EXPECTED_MATCH_COUNT);
|
||||
await waitForLegacyResult(page, '202', EXPECTED_MATCH_COUNT);
|
||||
|
||||
await verifySearchLogs(noFaceSearch.id, {
|
||||
expectedMatchCount: 0,
|
||||
|
|
@ -301,13 +341,16 @@ test('redirects direct-entry users without FaceAI session data back to the legac
|
|||
|
||||
try {
|
||||
await page.goto(`${FACEAI_BASE_URL}/`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForURL(LEGACY_HOME_URL_RE, { timeout: 30 * 1000 });
|
||||
await page.waitForURL(LEGACY_HOME_URL_RE, { timeout: SHORT_UI_TIMEOUT_MS });
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('allows two users to process different photos at the same time', async ({ browser }) => {
|
||||
test.slow();
|
||||
test.setTimeout(LONG_TEST_TIMEOUT_MS);
|
||||
|
||||
const contexts = [await browser.newContext(), await browser.newContext()];
|
||||
const pages = await Promise.all(contexts.map((context) => context.newPage()));
|
||||
|
||||
|
|
@ -323,8 +366,8 @@ test('allows two users to process different photos at the same time', async ({ b
|
|||
]);
|
||||
|
||||
await Promise.all([
|
||||
waitForLegacyResult(pages[0]),
|
||||
waitForLegacyResult(pages[1])
|
||||
waitForLegacyResult(pages[0], '202'),
|
||||
waitForLegacyResult(pages[1], '202')
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
|
|
@ -337,6 +380,9 @@ test('allows two users to process different photos at the same time', async ({ b
|
|||
});
|
||||
|
||||
test('queues the third user until a worker is free and then completes all three searches normally', async ({ browser }) => {
|
||||
test.slow();
|
||||
test.setTimeout(LONG_TEST_TIMEOUT_MS);
|
||||
|
||||
const contexts = [await browser.newContext(), await browser.newContext(), await browser.newContext()];
|
||||
const pages = await Promise.all(contexts.map((context) => context.newPage()));
|
||||
|
||||
|
|
@ -384,9 +430,9 @@ test('queues the third user until a worker is free and then completes all three
|
|||
|
||||
await waitForSearchCondition(queuedSearch.page, queuedSearch.searchId, (payload) => {
|
||||
return payload.status === 'processing' || payload.status === 'completed';
|
||||
}, 2 * 60 * 1000);
|
||||
}, SEARCH_COMPLETION_TIMEOUT_MS);
|
||||
|
||||
await Promise.all(pages.map((page) => waitForLegacyResult(page)));
|
||||
await Promise.all(pages.map((page) => waitForLegacyResult(page, '202')));
|
||||
|
||||
await Promise.all([
|
||||
verifySearchLogs(searchOne.id, { expectedSelfieName: 'DSC_1960.JPG' }),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue