Two separate ways the flows could not deliver what they promised. The card read polled for up to seventy seconds and escaped a missing figure by reloading the page — which re-mounts the list and issues the very batched compliance call it was waiting out, so each poll spent one more of the allowance it was short of. Meanwhile the budget is thirty seconds tripled by `test.slow()`, so one ride-out consumed the whole test and the run died on a Playwright timeout rather than on the "the card never painted an adherence figure" message the note promised. The inner poll was also wrapped in a second one with a thirty-second budget, and `expect.poll` does not abort an in-flight callback, so a seventy-second call ran to completion inside it. With the journey on its own account there is nothing to ride out: the read now waits twenty seconds for the attribute and fails with the diagnosis attached. The nesting is gone too — the step that waits for the card to re-read its schedule polls the attribute directly. The other one is the day boundary. A clock-anchored slot is inside its own on-time window by construction, so the TAKE is always today's; the dashboard assertion is not, because it reads the latest day and a day's expected count carries no now-cap. Cross midnight between the take and the final read and the latest day is a fresh one with two doses scheduled and none taken, which answers 0 where 50 is asserted. The flow is a couple of minutes long, so it declines to start in the last ten minutes of the day and says why.
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.