Enhance Docker and PowerShell scripts for improved functionality and maintainability
All checks were successful
Publish FaceAI Container / publish (push) Successful in 6m52s
All checks were successful
Publish FaceAI Container / publish (push) Successful in 6m52s
- Updated Dockerfile to include default MySQL client for better database interaction. - Modified entrypoint.sh to support additional workspace for legacy applications and added MySQL readiness check before startup. - Enhanced PowerShell script for trimming MySQL dumps to include overlay dumps and improved error handling for missing race and user IDs. - Added new image files and face encoding pickles for various projects, ensuring comprehensive data availability. - Removed outdated face encoding pickle from PISA directory to maintain data relevance. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
c227fce036
commit
dd7d4c865b
54 changed files with 492 additions and 144 deletions
|
|
@ -1,8 +1,10 @@
|
|||
const { test, expect } = require('@playwright/test');
|
||||
const {
|
||||
ensureLocalAuthenticatedRacePage,
|
||||
EXPECTED_MATCH_COUNT,
|
||||
FACEAI_BASE_URL,
|
||||
LEGACY_RACE_ID,
|
||||
SELFIE_NAME,
|
||||
buildHandoffUrl,
|
||||
buildSimulatorUrl,
|
||||
getSearchArtifacts,
|
||||
|
|
@ -61,6 +63,7 @@ async function readLaunchUrlFromLegacyPage(page) {
|
|||
}
|
||||
|
||||
async function startSearch(page, selfieName) {
|
||||
const selfieLabel = selfieName.split(/[\\/]+/u).pop();
|
||||
const createResponsePromise = page.waitForResponse((response) => {
|
||||
return response.url().includes('/api/searches')
|
||||
&& response.request().method() === 'POST'
|
||||
|
|
@ -68,7 +71,7 @@ async function startSearch(page, selfieName) {
|
|||
});
|
||||
|
||||
await page.locator('input[type="file"]').setInputFiles(getSelfiePath(selfieName));
|
||||
await expect(page.getByText(selfieName)).toBeVisible();
|
||||
await expect(page.getByText(selfieLabel)).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Avvia ricerca Face ID' }).click();
|
||||
|
||||
const createResponse = await createResponsePromise;
|
||||
|
|
@ -165,40 +168,40 @@ test('runs the legacy Tomcat flow through FaceAI and returns to the filtered leg
|
|||
|
||||
await launchFromSimulator(page, {
|
||||
raceId: LEGACY_RACE_ID,
|
||||
raceSlug: 'livorno',
|
||||
raceName: 'Livorno',
|
||||
raceFolder: 'LIVORNO'
|
||||
raceSlug: 'isolotto',
|
||||
raceName: 'Festa sociale UP Isolotto',
|
||||
raceFolder: 'ISOLOTTO'
|
||||
});
|
||||
|
||||
const search = await startSearch(page, 'DSC_1960.JPG');
|
||||
const search = await startSearch(page, SELFIE_NAME);
|
||||
|
||||
await waitForLegacyResult(page, LEGACY_RACE_ID, EXPECTED_MATCH_COUNT);
|
||||
|
||||
await verifySearchLogs(search.id, {
|
||||
expectedMatchCount: EXPECTED_MATCH_COUNT,
|
||||
expectedSelfieName: 'DSC_1960.JPG'
|
||||
expectedSelfieName: SELFIE_NAME.split(/[\\/]+/u).pop()
|
||||
});
|
||||
});
|
||||
|
||||
test('builds the legacy FaceAI handoff URL with the exact local race storage metadata', async ({ page }) => {
|
||||
await page.goto(buildSimulatorUrl({
|
||||
raceId: LEGACY_RACE_ID,
|
||||
raceSlug: 'livorno',
|
||||
raceName: 'Livorno',
|
||||
raceSlug: 'isolotto',
|
||||
raceName: 'Festa sociale UP Isolotto',
|
||||
raceYear: '2026',
|
||||
raceMonthFolder: '04.APRILE',
|
||||
raceFolder: 'LIVORNO'
|
||||
raceFolder: 'ISOLOTTO'
|
||||
}), { waitUntil: 'domcontentloaded' });
|
||||
|
||||
await expect(page.locator('#faceAiRaceYear')).toHaveValue('2026');
|
||||
await expect(page.locator('#faceAiRaceMonthFolder')).toHaveValue('04.APRILE');
|
||||
await expect(page.locator('#faceAiRaceFolder')).toHaveValue('LIVORNO');
|
||||
await expect(page.locator('#faceAiRaceFolder')).toHaveValue('ISOLOTTO');
|
||||
|
||||
const launchUrl = await readLaunchUrlFromLegacyPage(page);
|
||||
expect(launchUrl.searchParams.get('raceYear')).toBe('2026');
|
||||
expect(launchUrl.searchParams.get('raceMonthFolder')).toBe('04.APRILE');
|
||||
expect(launchUrl.searchParams.get('raceFolder')).toBe('LIVORNO');
|
||||
expect(launchUrl.searchParams.get('raceStorageRelativeDir')).toBe('2026/04.APRILE/LIVORNO');
|
||||
expect(launchUrl.searchParams.get('raceFolder')).toBe('ISOLOTTO');
|
||||
expect(launchUrl.searchParams.get('raceStorageRelativeDir')).toBe('2026/04.APRILE/ISOLOTTO');
|
||||
});
|
||||
|
||||
test('shows the unsupported-race message when the current race has no PKL data and lets the user go back', async ({ page }) => {
|
||||
|
|
@ -300,6 +303,19 @@ test('rejects a not-logged-in user after clicking the Face ID button and sends t
|
|||
await expect(page.locator('#faceAiErrorModalMessage')).toContainText('Il servizio Face ID non e al momento disponibile. Riprova piu tardi.');
|
||||
});
|
||||
|
||||
test('authenticates with the seeded local user and lets that user browse and launch the Livorno race page', async ({ page }) => {
|
||||
await ensureLocalAuthenticatedRacePage(page, {
|
||||
raceId: '1018557',
|
||||
raceName: 'VIVICITTA LIVORNO',
|
||||
raceYear: '2026',
|
||||
raceMonthFolder: '04.APRILE',
|
||||
raceFolder: 'LIVORNO'
|
||||
});
|
||||
|
||||
await page.locator('#faceaiLaunchButton').click();
|
||||
await waitForFaceAiHome(page);
|
||||
});
|
||||
|
||||
test('shows the no-face message and allows the user to return to the race page', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
|
|
@ -341,8 +357,8 @@ test('opens the file chooser when the user clicks the upload button', async ({ p
|
|||
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 fileChooser.setFiles(getSelfiePath(SELFIE_NAME));
|
||||
await expect(page.getByText(SELFIE_NAME.split(/[\\/]+/u).pop())).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Avvia ricerca Face ID' })).toBeEnabled();
|
||||
});
|
||||
|
||||
|
|
@ -364,7 +380,7 @@ test('lets the user retry with a valid photo after a no-face upload and then ret
|
|||
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');
|
||||
const retrySearch = await startSearch(page, SELFIE_NAME);
|
||||
await waitForLegacyResult(page, '202', EXPECTED_MATCH_COUNT);
|
||||
|
||||
await verifySearchLogs(noFaceSearch.id, {
|
||||
|
|
@ -373,7 +389,7 @@ test('lets the user retry with a valid photo after a no-face upload and then ret
|
|||
});
|
||||
await verifySearchLogs(retrySearch.id, {
|
||||
expectedMatchCount: EXPECTED_MATCH_COUNT,
|
||||
expectedSelfieName: 'DSC_1960.JPG'
|
||||
expectedSelfieName: SELFIE_NAME.split(/[\\/]+/u).pop()
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -403,7 +419,7 @@ test('allows two users to process different photos at the same time', async ({ b
|
|||
]);
|
||||
|
||||
const [searchOne, searchTwo] = await Promise.all([
|
||||
startSearch(pages[0], 'DSC_1960.JPG'),
|
||||
startSearch(pages[0], SELFIE_NAME),
|
||||
startSearch(pages[1], 'DSC_1987.JPG')
|
||||
]);
|
||||
|
||||
|
|
@ -413,7 +429,7 @@ test('allows two users to process different photos at the same time', async ({ b
|
|||
]);
|
||||
|
||||
await Promise.all([
|
||||
verifySearchLogs(searchOne.id, { expectedSelfieName: 'DSC_1960.JPG' }),
|
||||
verifySearchLogs(searchOne.id, { expectedSelfieName: SELFIE_NAME.split(/[\\/]+/u).pop() }),
|
||||
verifySearchLogs(searchTwo.id, { expectedSelfieName: 'DSC_1987.JPG' })
|
||||
]);
|
||||
} finally {
|
||||
|
|
@ -436,7 +452,7 @@ test('queues the third user until a worker is free and then completes all three
|
|||
]);
|
||||
|
||||
const [searchOne, searchTwo, searchThree] = await Promise.all([
|
||||
startSearch(pages[0], 'DSC_1960.JPG'),
|
||||
startSearch(pages[0], SELFIE_NAME),
|
||||
startSearch(pages[1], 'DSC_1987.JPG'),
|
||||
startSearch(pages[2], 'DSC_2058.JPG')
|
||||
]);
|
||||
|
|
@ -477,7 +493,7 @@ test('queues the third user until a worker is free and then completes all three
|
|||
await Promise.all(pages.map((page) => waitForLegacyResult(page, '202')));
|
||||
|
||||
await Promise.all([
|
||||
verifySearchLogs(searchOne.id, { expectedSelfieName: 'DSC_1960.JPG' }),
|
||||
verifySearchLogs(searchOne.id, { expectedSelfieName: SELFIE_NAME.split(/[\\/]+/u).pop() }),
|
||||
verifySearchLogs(searchTwo.id, { expectedSelfieName: 'DSC_1987.JPG' }),
|
||||
verifySearchLogs(searchThree.id, { expectedSelfieName: 'DSC_2058.JPG' })
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@ const WORKSPACE_ROOT = path.resolve(ROOT_DIR, '..');
|
|||
const LOG_ROOT = path.join(ROOT_DIR, 'logs');
|
||||
const SEARCH_LOG_ROOT = path.join(LOG_ROOT, 'searches');
|
||||
const FACEAI_BASE_URL = process.env.FACEAI_E2E_BASE_URL || 'http://127.0.0.1:3001';
|
||||
const SIMULATOR_URL = process.env.FACEAI_E2E_SIMULATOR_URL || 'http://127.0.0.1:8080/Foto2.abl?id_gara=1018557&pageRow=96&pageNumber=1';
|
||||
const SIMULATOR_URL = process.env.FACEAI_E2E_SIMULATOR_URL || 'http://127.0.0.1:8080/Foto2.abl?id_gara=1018547&pageRow=96&pageNumber=1';
|
||||
const LEGACY_BASE_URL = process.env.FACEAI_E2E_LEGACY_BASE_URL || 'http://127.0.0.1:8080';
|
||||
const LEGACY_HOME_URL = process.env.FACEAI_E2E_LEGACY_HOME_URL || `${LEGACY_BASE_URL}/index.jsp`;
|
||||
const SELFIE_NAME = process.env.FACEAI_E2E_SELFIE || 'DSC_1960.JPG';
|
||||
const LEGACY_LOGIN_URL = process.env.FACEAI_E2E_LEGACY_LOGIN_URL || `${LEGACY_BASE_URL}/login_clienti.html`;
|
||||
const LEGACY_USERNAME = process.env.FACEAI_E2E_LEGACY_USERNAME || 'test';
|
||||
const LEGACY_PASSWORD = process.env.FACEAI_E2E_LEGACY_PASSWORD || 'test1';
|
||||
const SELFIE_NAME = process.env.FACEAI_E2E_SELFIE || '2026/04.APRILE/ISOLOTTO/01.FOTO/CKL_8459.JPG';
|
||||
const EXPECTED_MATCH_COUNT = Number(process.env.FACEAI_E2E_EXPECTED_MATCH_COUNT || '6');
|
||||
const LEGACY_RACE_ID = process.env.FACEAI_E2E_RACE_ID || '1018557';
|
||||
const LEGACY_RACE_ID = process.env.FACEAI_E2E_RACE_ID || '1018547';
|
||||
|
||||
function quoteShellArg(value) {
|
||||
if (!/[\s"]/u.test(value)) {
|
||||
|
|
@ -114,17 +117,22 @@ async function waitForHttp(url, validate, timeoutMs = 3 * 60 * 1000) {
|
|||
}
|
||||
|
||||
function getSelfiePath(fileName = SELFIE_NAME) {
|
||||
const relativeSegments = fileName.split(/[\\/]+/u);
|
||||
if (relativeSegments.length > 1) {
|
||||
return path.join(WORKSPACE_ROOT, 'test_pkl', ...relativeSegments);
|
||||
}
|
||||
|
||||
return path.join(WORKSPACE_ROOT, 'test_pkl', 'test_images', fileName);
|
||||
}
|
||||
|
||||
function buildSimulatorUrl({
|
||||
raceId = LEGACY_RACE_ID,
|
||||
lang = 'it',
|
||||
raceSlug = 'livorno',
|
||||
raceName = 'Livorno',
|
||||
raceSlug = 'isolotto',
|
||||
raceName = 'Festa sociale UP Isolotto',
|
||||
raceYear = '2026',
|
||||
raceMonthFolder = '04.APRILE',
|
||||
raceFolder = 'LIVORNO',
|
||||
raceFolder = 'ISOLOTTO',
|
||||
pageRow = '96',
|
||||
pageNumber = '1'
|
||||
} = {}) {
|
||||
|
|
@ -139,11 +147,11 @@ function buildSimulatorUrl({
|
|||
function buildHandoffUrl({
|
||||
raceId = LEGACY_RACE_ID,
|
||||
lang = 'it',
|
||||
raceSlug = 'livorno',
|
||||
raceName = 'Livorno',
|
||||
raceSlug = 'isolotto',
|
||||
raceName = 'Festa sociale UP Isolotto',
|
||||
raceYear = '2026',
|
||||
raceMonthFolder = '04.APRILE',
|
||||
raceFolder = 'LIVORNO',
|
||||
raceFolder = 'ISOLOTTO',
|
||||
userId = '1',
|
||||
displayName = `Local Test User ${userId}`,
|
||||
email = `local-test-${userId}@example.invalid`,
|
||||
|
|
@ -166,6 +174,86 @@ function buildHandoffUrl({
|
|||
return url.toString();
|
||||
}
|
||||
|
||||
function buildLegacyLoginFormData() {
|
||||
if (!LEGACY_USERNAME || !LEGACY_PASSWORD) {
|
||||
throw new Error('FACEAI_E2E_LEGACY_USERNAME and FACEAI_E2E_LEGACY_PASSWORD must be set before running authenticated local E2E checks.');
|
||||
}
|
||||
|
||||
return {
|
||||
login: LEGACY_USERNAME,
|
||||
pwd: LEGACY_PASSWORD,
|
||||
cmdIU: 'check',
|
||||
act: '',
|
||||
thePage: ''
|
||||
};
|
||||
}
|
||||
|
||||
async function performLocalLoginRequest(requestContext) {
|
||||
const response = await requestContext.post(`${LEGACY_BASE_URL}/Logon.abl`, {
|
||||
form: buildLegacyLoginFormData(),
|
||||
failOnStatusCode: false
|
||||
});
|
||||
|
||||
const finalUrl = response.url();
|
||||
const bodyText = await response.text();
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Local login request failed with HTTP ${response.status()} at ${finalUrl}`);
|
||||
}
|
||||
|
||||
if (/login_clienti|Username \/ Email|Password/iu.test(bodyText) && !/user_logout|dettaglio_clienti|Il mio account/iu.test(bodyText)) {
|
||||
throw new Error(`Local login request appears to have remained on the login page at ${finalUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function expectLocalRacePageLoaded(page) {
|
||||
await page.waitForSelector('form[onsubmit="return searching()"]', { state: 'visible' });
|
||||
const raceId = await page.locator('#id_gara').inputValue();
|
||||
if (!/\d+/u.test(raceId)) {
|
||||
throw new Error(`Expected the local race page to expose a numeric race id, got: ${raceId}`);
|
||||
}
|
||||
|
||||
await page.waitForSelector('#faceaiLaunchButton', { state: 'visible' });
|
||||
await page.waitForFunction(() => {
|
||||
return document.querySelectorAll('a[data-faceai-photo-id] img.thumb').length > 0;
|
||||
}, null, {
|
||||
timeout: 30 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureLocalAuthenticatedRacePage(page, options = {}) {
|
||||
await page.goto(LEGACY_LOGIN_URL, { waitUntil: 'domcontentloaded' });
|
||||
await page.locator('#login').fill(LEGACY_USERNAME);
|
||||
await page.locator('#pwd').fill(LEGACY_PASSWORD);
|
||||
|
||||
const submitLocator = page.locator('input[type="submit"], button[type="submit"], a.btn').filter({ hasText: /Accedi|Sign in/i }).first();
|
||||
if (await submitLocator.count()) {
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
||||
submitLocator.click()
|
||||
]);
|
||||
} else {
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
||||
page.evaluate(() => {
|
||||
const form = document.querySelector('form[action="Logon.abl"]');
|
||||
if (!form) {
|
||||
throw new Error('Local login page did not expose a Logon.abl form.');
|
||||
}
|
||||
|
||||
const cmdIUField = form.querySelector('[name="cmdIU"]');
|
||||
if (cmdIUField) {
|
||||
cmdIUField.value = 'check';
|
||||
}
|
||||
form.submit();
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
await page.goto(buildSimulatorUrl(options), { waitUntil: 'domcontentloaded' });
|
||||
await expectLocalRacePageLoaded(page);
|
||||
}
|
||||
|
||||
function getSearchArtifacts(searchId) {
|
||||
const searchRoot = path.join(SEARCH_LOG_ROOT, searchId);
|
||||
return {
|
||||
|
|
@ -188,15 +276,22 @@ module.exports = {
|
|||
FACEAI_BASE_URL,
|
||||
LEGACY_BASE_URL,
|
||||
LEGACY_HOME_URL,
|
||||
LEGACY_LOGIN_URL,
|
||||
LEGACY_PASSWORD,
|
||||
SIMULATOR_URL,
|
||||
SELFIE_NAME,
|
||||
EXPECTED_MATCH_COUNT,
|
||||
LEGACY_RACE_ID,
|
||||
LEGACY_USERNAME,
|
||||
buildHandoffUrl,
|
||||
buildLegacyLoginFormData,
|
||||
buildSimulatorUrl,
|
||||
dockerCompose,
|
||||
ensureLocalAuthenticatedRacePage,
|
||||
expectLocalRacePageLoaded,
|
||||
getSearchArtifacts,
|
||||
getSelfiePath,
|
||||
performLocalLoginRequest,
|
||||
prepareHostState,
|
||||
readUtf8,
|
||||
runCommand,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue