const fs = require('fs'); const path = require('path'); const { expect } = require('@playwright/test'); const { loadLiveEnv } = require('./load-live-env'); loadLiveEnv(); const WORKSPACE_ROOT = path.resolve(__dirname, '..', '..', '..'); const LIVE_SITE_BASE_URL = process.env.LIVE_SITE_BASE_URL || 'https://www.regalamiunsorriso.it'; const LIVE_SITE_LOGIN_URL = process.env.LIVE_SITE_LOGIN_URL || `${LIVE_SITE_BASE_URL}/login_clienti-it.html`; const LIVE_SITE_RACE_URL = process.env.LIVE_SITE_RACE_URL || `${LIVE_SITE_BASE_URL}/42%20HALF%20MARATHON%20FIRENZE_gara-1018545---96-1.html`; const LIVE_FACEAI_BASE_URL = process.env.LIVE_FACEAI_BASE_URL || 'https://ai.regalamiunsorriso.it'; const LIVE_SITE_RESULT_URL_PATTERN = process.env.LIVE_SITE_RESULT_URL_PATTERN || `${LIVE_SITE_BASE_URL}/faceai_return.php`; const LIVE_SITE_REQUIRE_AUTH = process.env.LIVE_SITE_REQUIRE_AUTH !== '0'; const LIVE_SITE_USERNAME = process.env.LIVE_SITE_USERNAME || ''; 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_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'); 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() { fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true }); } function requireCredentials() { if (!LIVE_SITE_USERNAME || !LIVE_SITE_PASSWORD) { throw new Error('LIVE_SITE_USERNAME and LIVE_SITE_PASSWORD must be set before running the live-site Playwright suite.'); } } async function dismissCookieBanner(page) { const cookieButton = page.getByRole('button', { name: /^(Accetto|Accept)$/i }); if (await cookieButton.count()) { await cookieButton.first().click({ timeout: 5000 }).catch(() => {}); return; } const fallbackButton = page.locator('.cc-btn, .cc-dismiss, .cc-allow').filter({ hasText: /Accetto|Accept/i }); if (await fallbackButton.count()) { await fallbackButton.first().click({ timeout: 5000 }).catch(() => {}); } } function loginSubmitLocator(page) { return page.locator('a.btn').filter({ hasText: /Accedi|Sign in/i }).first(); } function buildLoginFormData() { requireCredentials(); return { login: LIVE_SITE_USERNAME, pwd: LIVE_SITE_PASSWORD, cmdIU: 'check', act: '', thePage: '' }; } async function gotoWithRetry(page, url, options = {}, maxAttempts = 3) { let lastError = null; for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { try { await page.goto(url, options); return; } catch (error) { lastError = error; if (!/ERR_ABORTED/i.test(String(error)) || attempt === maxAttempts) { throw error; } } } throw lastError; } async function performLiveLogin(page) { if (!LIVE_SITE_REQUIRE_AUTH) { return; } requireCredentials(); await gotoWithRetry(page, LIVE_SITE_LOGIN_URL, { waitUntil: 'commit' }); await dismissCookieBanner(page); await page.locator('#login').fill(LIVE_SITE_USERNAME); await page.locator('#pwd').fill(LIVE_SITE_PASSWORD); await loginSubmitLocator(page).click(); await waitForLoggedInUi(page); } async function performLiveLoginRequest(requestContext) { if (!LIVE_SITE_REQUIRE_AUTH) { return; } const response = await requestContext.post(`${LIVE_SITE_BASE_URL}/Logon.abl`, { form: buildLoginFormData(), failOnStatusCode: false }); const finalUrl = response.url(); const bodyText = await response.text(); if (!response.ok()) { throw new Error(`Live 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(`Live login request appears to have remained on the login page at ${finalUrl}`); } } async function waitForLoggedInUi(page) { const accountMenu = page.locator('#navbarDropdownMenuLink'); const accountLink = page.locator('a[href*="dettaglio_clienti"]'); const logoutLink = page.locator('a[href*="user_logout"]'); await expect.poll(async () => { if (await accountMenu.count() && await accountMenu.first().isVisible().catch(() => false)) { return 'account-menu'; } if (await accountLink.count() && await accountLink.first().isVisible().catch(() => false)) { return 'account-link'; } if (await logoutLink.count() && await logoutLink.first().isVisible().catch(() => false)) { return 'logout-link'; } return ''; }, { timeout: 30 * 1000, message: 'Expected the logged-in account UI to appear after authenticating.' }).not.toBe(''); } async function expectRacePageLoaded(page) { await expect(page.locator('form[onsubmit="return searching()"]')).toBeVisible(); await expect(page.locator('#id_gara')).toHaveValue(/\d+/); await expect(page.locator('script[src*="_js/rus-ecom-240621.js"]')).toHaveCount(1); } async function ensureLiveAuthenticatedRacePage(page) { await gotoWithRetry(page, LIVE_SITE_RACE_URL, { waitUntil: 'commit' }); await dismissCookieBanner(page); if (!LIVE_SITE_REQUIRE_AUTH) { await expectRacePageLoaded(page); return; } try { await waitForLoggedInUi(page); } catch (error) { await performLiveLoginRequest(page.context().request); await gotoWithRetry(page, LIVE_SITE_RACE_URL, { waitUntil: 'commit' }); await dismissCookieBanner(page); await waitForLoggedInUi(page); } await expectRacePageLoaded(page); } function requirePortraitFixture() { if (!fs.existsSync(LIVE_SITE_PORTRAIT_PATH)) { throw new Error(`LIVE_SITE_PORTRAIT_PATH does not exist: ${LIVE_SITE_PORTRAIT_PATH}`); } } module.exports = { AUTH_FILE, LIVE_FACEAI_BASE_URL, LIVE_EXPECTED_RACE_STORAGE, LIVE_SITE_BASE_URL, LIVE_SITE_LOGIN_URL, LIVE_SITE_PASSWORD, LIVE_SITE_PORTRAIT_PATH, LIVE_SITE_REQUIRE_AUTH, LIVE_SITE_EXPECT_RACE_DATA_AVAILABLE, LIVE_SITE_EXPECT_UNAVAILABLE_REASON_CODE, LIVE_SITE_RACE_ID, LIVE_SITE_RACE_URL, LIVE_SITE_RESULT_URL_PATTERN, LIVE_SITE_RUN_UPLOAD_FLOW, LIVE_SITE_SAMPLE_PHOTO_IDS, LIVE_SITE_USERNAME, dismissCookieBanner, ensureLiveAuthenticatedRacePage, ensureAuthDirectory, expectRacePageLoaded, hasExpectedRaceStorage, loginSubmitLocator, parseRaceIdFromUrl, performLiveLogin, performLiveLoginRequest, requirePortraitFixture, requireCredentials, waitForLoggedInUi };