diff --git a/playwright.config.ts b/playwright.config.ts index 9ee537c5..76937456 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -24,12 +24,31 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: 1, + // The shared quality.yml Playwright job is `timeout-minutes: 45`, and a job + // cancelled by that cap produces NO verdict: Playwright never prints its + // tally, the `if: failure()` trace upload never fires, and the + // `if: always()` report upload does not run on a cancelled job either — the + // run you most need to read is the one that leaves nothing behind, and it + // still renders as "fail" in `gh pr checks` while carrying no information. + // Runs cancelled at ~45m16s have been observed in this fleet. Measured + // overhead before `Run Playwright tests` starts is 2.0-2.4 min and the + // uploads after it take seconds, so 38m keeps ~7 min of margin while + // guaranteeing both a tally and the artifacts that explain it. + globalTimeout: 38 * 60_000, reporter: 'list', outputDir: 'test-results', use: { baseURL: resolveBaseUrl(), storageState: path.resolve(__dirname, 'tests/e2e/.auth/admin.json'), - trace: 'on-first-retry', + // `on-first-retry` writes a trace only when a retry actually happens, so + // the trace artifact is a function of `retries`. Off CI `retries` is 0 + // above, so a local failure has never produced a trace at all; on CI it + // traces the SECOND attempt only, which means the failure that does not + // reproduce — the one actually worth a trace — leaves no record of the + // attempt that failed. `retain-on-failure` traces every attempt and + // keeps the ones that failed: strictly more informative, and + // independent of the retry count. + trace: 'retain-on-failure', screenshot: 'only-on-failure', }, projects: [ diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 3e913471..11487b10 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -71,6 +71,17 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: 1, + // The shared quality.yml Playwright job is `timeout-minutes: 45`, and a job + // cancelled by that cap produces NO verdict: Playwright never prints its + // tally, the `if: failure()` trace upload never fires, and the + // `if: always()` report upload does not run on a cancelled job either — the + // run you most need to read is the one that leaves nothing behind, and it + // still renders as "fail" in `gh pr checks` while carrying no information. + // Runs cancelled at ~45m16s have been observed in this fleet. Measured + // overhead before `Run Playwright tests` starts is 2.0-2.4 min and the + // uploads after it take seconds, so 38m keeps ~7 min of margin while + // guaranteeing both a tally and the artifacts that explain it. + globalTimeout: 38 * 60_000, reporter: [ ['html', { open: 'never', outputFolder: path.join(APP_ROOT, 'playwright-report') }], ['list'], @@ -80,7 +91,15 @@ export default defineConfig({ use: { baseURL: BASE_URL, storageState: path.resolve(__dirname, '.auth', 'admin.json'), - trace: 'on-first-retry', + // `on-first-retry` writes a trace only when a retry actually happens, so + // the trace artifact is a function of `retries`. Off CI `retries` is 0 + // above, so a local failure has never produced a trace at all; on CI it + // traces the SECOND attempt only, which means the failure that does not + // reproduce — the one actually worth a trace — leaves no record of the + // attempt that failed. `retain-on-failure` traces every attempt and + // keeps the ones that failed: strictly more informative, and + // independent of the retry count. + trace: 'retain-on-failure', screenshot: 'only-on-failure', },