Files
HealthLog/playwright.config.ts
T
Marc-André Bombeck 50e630bf2d test(e2e): lift expect timeout to 10s and CI retries to 2 for loaded-runner headroom
A rotating single e2e test failed each CI run (wizard step transition,
list-refetch card provenance, disclosure toggle) while 211 passed — DOM-
settle-gated assertions sampling a mid-transition frame on a starved shared
runner. Raise the global expect timeout 5s→10s (suite-wide settle headroom)
and CI retries 1→2 (absorb the transient without masking a real break, which
fails all attempts). Also scroll the provenance card into view before reading
its attribute, since the mobile timeline can render it off-viewport.
2026-07-08 23:04:22 +02:00

112 lines
4.7 KiB
TypeScript

import { defineConfig, devices } from "@playwright/test";
/**
* Playwright configuration for the HealthLog E2E suite.
*
* The suite covers the smoke-level user paths (auth redirect, login
* form, public version endpoint, locale switch, axe-core) without
* needing seeded data — every spec either runs against an unauthed
* surface or uses route interception to stub out the API. Specs that
* need a logged-in user are kept narrow and flagged in their describe
* block; CI runs them against a worker that seeds a deterministic test
* user on startup.
*
* To run locally: `pnpm dlx playwright install --with-deps chromium`
* once, then `pnpm e2e`.
*/
export default defineConfig({
testDir: "./e2e",
testIgnore: ["setup/**"],
globalSetup: "./e2e/setup/global-setup.ts",
timeout: 30_000,
expect: { timeout: 10_000 },
fullyParallel: true,
forbidOnly: !!process.env.CI,
// Two retries on CI: under a loaded shared runner, DOM-settle-gated
// assertions (wizard step transitions, list refetch → card provenance,
// disclosure toggles) intermittently sample a mid-transition frame. A
// rotating single test failed each run while 211 passed; a second retry
// absorbs that transient contention without masking a real failure (a true
// break fails all three attempts). The default expect timeout is also
// lifted from 5s → 10s for the same settle headroom.
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI
? [["github"], ["html", { open: "never" }]]
: [["list"], ["html", { open: "never" }]],
use: {
baseURL: process.env.E2E_BASE_URL ?? "http://localhost:3000",
trace: "retain-on-failure",
screenshot: "only-on-failure",
video: "retain-on-failure",
// v1.18.6 — block the service worker in the test context. The
// production build registers `/sw.js`, whose v1.18.6 data branch
// serves allowlisted `/api/*` GET reads network-first. A worker-
// originated `fetch` is NOT subject to Playwright's `page.route`
// interception, so the SW would re-fetch the real (empty) backend
// and serve that — bypassing every per-spec `route.fulfill` mock and
// breaking read-after-write assertions (a just-created row never
// surfaced). Blocking the worker keeps the route mocks authoritative
// without weakening the shipped SW behaviour. Real users keep the
// offline data cache; only the test harness opts out.
serviceWorkers: "block",
// HealthLog ships dark mode as the default (Dracula theme) — `globals.css`
// sets `color-scheme: dark` on the root and the `<ThemeProvider>` defaults
// to "system". Playwright's stock context is `colorScheme: "light"`, which
// means axe-core was scanning a layout users never actually see (Dracula
// greens on a light card → 1.18 contrast). Forcing the test theme to dark
// matches what real users render on first paint and is the only honest
// a11y baseline for this app.
colorScheme: "dark",
},
projects: [
{
name: "chromium-desktop",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1280, height: 720 },
colorScheme: "dark",
},
},
{
name: "chromium-mobile",
use: {
// Pixel 5 — Chromium-based mobile profile so CI only needs
// `playwright install chromium` instead of also pulling
// webkit. iPhone-13 is intentionally avoided here; the
// mobile-Safari smoke is exercised by the iOS app suite.
...devices["Pixel 5"],
colorScheme: "dark",
},
},
],
// Spin up the production build for E2E. `pnpm build` is run
// separately by CI before the suite — locally, set E2E_SKIP_WEB_SERVER=1
// to point at an already-running dev server.
webServer: process.env.E2E_SKIP_WEB_SERVER
? undefined
: {
command: "pnpm exec next start --port 3000",
url: "http://localhost:3000/api/version",
timeout: 60_000,
reuseExistingServer: !process.env.CI,
stdout: "ignore",
stderr: "pipe",
env: {
...process.env,
// The dashboard RSC wrapper server-prefetches the snapshot into
// the first HTML (HydrationBoundary). Playwright's route mocks
// only see CLIENT fetches, so an SSR-embedded snapshot would
// bypass `mockDashboardSnapshot` and every dashboard spec would
// assert against the seeded account instead of its fixture.
// Disable the prefetch for the e2e server — the suite keeps the
// deterministic client-fetch path; the SSR fast path is
// verified by Lighthouse/manual passes.
DASHBOARD_SSR_PREFETCH: "false",
},
},
});