import { defineConfig, devices } from '@playwright/test' import path from 'path' const LOGGED_OUT = { storageState: undefined } // Script lives in AK-Docs/03.Testing/05.Scripts/{repo}/ but binary evidence (screenshots, // video, trace.zip, results.json) must land in AK-Docs/03.Testing/04.Evidence/ instead — // that folder is gitignored (binaries, never committed) while this script folder is not. // See execute-flow SKILL.md "Cấu trúc thư mục đầu ra". The AI sets EVIDENCE_DIR per run // (03.Testing/04.Evidence/{repo}/{featureDir}/run-{N}) when invoking `npx playwright test`. const evidenceDir = process.env.EVIDENCE_DIR ? path.resolve(process.env.EVIDENCE_DIR) : path.resolve(__dirname, 'test-results') // Chromium is the default and the only browser routinely validated for evidence quality — // WebKit/Firefox render fonts differently (esp. CJK on Linux, missing system font fallback), // which is a known source of blank-glyph/low-quality evidence screenshots. Only override via // PW_BROWSER when the tester has explicitly confirmed they want a non-Chromium run (see // execute-flow SKILL.md pre-flight check item 5). const PW_DEVICE_BY_BROWSER = { chromium: devices['Desktop Chrome'], webkit: devices['Desktop Safari'], firefox: devices['Desktop Firefox'], } const browserName = (process.env.PW_BROWSER || 'chromium').toLowerCase() const browserDevice = PW_DEVICE_BY_BROWSER[browserName] if (!browserDevice) { throw new Error( `PW_BROWSER="${browserName}" không hợp lệ. Chỉ hỗ trợ: ${Object.keys(PW_DEVICE_BY_BROWSER).join(', ')}.` ) } export default defineConfig({ testDir: '.', outputDir: evidenceDir, fullyParallel: false, forbidOnly: !!process.env.CI, retries: 1, // absorb transient timing flakes before reporting FAIL — see "DOM Verification Rules" workers: 1, // Default 30s is too tight for TCs that call captureStepEvidence() many times in one test (e.g. // asserting sort direction across every sortable column) — each call's waitForUiSettled() adds // real, deliberate wait time (see evidence-helper.ts) in exchange for not screenshotting mid- // spinner/mid-animation. A loop-heavy TC that was already close to 30s (~26s observed on // MFG113's 14-capture sort test) can then time out on infra grounds alone, with no app defect. timeout: 60_000, reporter: [ ['json', { outputFile: path.join(evidenceDir, 'results.json') }], ['html', { outputFolder: path.join(evidenceDir, 'html-report'), open: 'never' }], ['list'], ], use: { baseURL: process.env.BASE_URL || 'http://localhost:3000', trace: 'retain-on-failure', // Manual per-step screenshots come from captureStepEvidence() in evidence-helper.ts — // this stays 'only-on-failure' so it doesn't duplicate/clash with those step-NN-*.png files. screenshot: 'only-on-failure', video: 'retain-on-failure', headless: !process.env.HEADED, // Higher device scale factor = sharper text in evidence, especially Kanji/Katakana. deviceScaleFactor: 2, // Set to the app's actual UI locale so date/number formatting and font fallback match // what the browser renders in production — mismatched locale is a common cause of // "screenshot color/rendering doesn't match what I see in my own browser" reports. locale: process.env.UI_LOCALE || 'ja-JP', viewport: { width: 1440, height: 900 }, // Stops CSS/JS animations from being mid-transition when a screenshot fires. reducedMotion: 'reduce', }, projects: [ // Uncomment nếu cần auth setup: // { name: 'setup', testMatch: /auth\.setup\.ts/ }, { name: browserName, use: { ...browserDevice, // storageState: '.auth/user.json', // uncomment nếu cần auth }, // dependencies: ['setup'], // uncomment nếu cần auth }, ], }) export { LOGGED_OUT }