Generating a report writes: the route remembers the scope and the practice name on the account row, and the export panel a later visit opens is therefore the collapsed "repeat run" state. The journey was generating on the shared fixture account, which is the account `settings-export.spec.ts` asserts the first-run state against in nine places — an expanded picker, an empty summary, a disabled button. The two files run in an unconstrained order under two workers, so the first symptom was an order-dependent red in CI; against a persistent local database the flip never cleared and the red was permanent. The journey now seeds, generates and reads back on `e2e-report-owner`, with its own cookie jar. The shared account and the whole-record fixtures also get `report_selection_json` and `last_report_practice_name` cleared on every seed, which repairs a database an earlier run already dirtied and keeps a delegate's scope choice from being left on a record owner's row. The account's date order is pinned rather than left to the locale, and its export bucket is purged beside the share-link and login buckets: the 10-per-hour ceiling is shared by every export route, keys on the actor and outlives a run by an hour, so repeated local runs would otherwise meet a 429 the spec reports as a missing artefact.
e2e/
End-to-end browser tests. Playwright 1.60 drives a production build of the app against a real PostgreSQL, with @axe-core/playwright for accessibility assertions. This is the heaviest test tier; CI runs it on every PR, and locally it is worth running when a change touches a UI flow.
Running
pnpm e2e
The Playwright config (../playwright.config.ts) builds the app (next build) and brings up Postgres before the specs run; setup/global-setup.ts seeds the auth + fixture state. The suite is not part of the fast local gate (typecheck / lint / test) — it surfaces at PR CI, so expect responsive/wizard/nav reworks to flag here first.
Layout
setup/—test.ts(the suite'stest+expectentry point),global-setup.ts(auth + DB seeding),test-helpers.ts(shared login + navigation helpers).utils/— fixtures such asmock-dashboard-snapshot.ts.*.spec.ts— one spec per flow: auth/login/redirect, dashboard, charts, the medication-wizard cadences (medications-wizard-*), insights generation + scroll restoration, doctor report, settings, mobile-viewport consistency, locale switch, accessibility (a11y.spec.ts), and thev1427-*regression set.
Conventions
Import test and expect from ./setup/test, never from @playwright/test — types (Page, Route, …) still come from the package. The entry point extends Playwright's test with an automatic fixture that drops route handlers and page listeners after every test, so a handler left in flight cannot fail the test it belonged to once the context closes. src/__tests__/e2e-route-cleanup-guard.test.ts enforces the import boundary.
Assert against stable data-* attributes (data-slot, data-display-step, root display attrs), not viewport-dependent visible text. Responsive sm:hidden UI changes break getByText on desktop while the underlying element is still present — anchoring on data attributes keeps the specs stable across breakpoints. See ../CLAUDE.md.