Under Node, pdf.js runs its worker in the server process and keeps it on a global, and whichever copy of pdf.js opens a document first owns that global until a restart. The local text read went through pdf-parse, which pins its own pdfjs-dist 5.4.296; the rasterizer uses the app's 6.4.299. A text read first made every later rasterization fail with "The API version does not match the Worker version" (thumbnails raster-failed, the vision read of a scan 422), and a rasterization first made every later local text read fail, which left the document out of the search index with nothing in the logs. src/lib/documents/pdfjs.ts is now the one place pdf.js is loaded and opened, with one set of options (asset directories, no eval, no system fonts). The text read walks the pages with getTextContent and lays the text out the way pdf-parse did with the options it was called with: line breaks, tabs between far-apart items, a blank line after each page, the 200-page cap. Text extracted from the PDF fixtures and from generated lab tables, encrypted files and doctor reports is identical before and after. A failed text read now emits documents.local_extract.failed with the error pdf.js threw, and annotates the index event with it, instead of returning a bare error. The report tests read PDF text through the same reader. Two tests, one per order, run a text read and a rasterization in one process; both failed before this change. Refs #1277
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.fixtures/— synthetic payloads a journey uploads, built in code rather than committed as blobs so the content stays reviewable (apple-health-export.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.