Testing
Run fast Vitest unit tests per package and lean Playwright end-to-end smoke checks locally, with commands, conventions, and tips for adding new specs.
The project ships two test layers: fast Vitest unit tests per package, and Playwright end-to-end tests that drive the running app in a real browser. The generated .github/workflows/ci.yml runs lint, typecheck, and unit tests on every push and PR to main; e2e ships local-only (it builds the app, which CI otherwise leaves to the deploy platform).
Unit tests (Vitest)
Run these from the repo root.
| Command | What it does |
|---|---|
pnpm test | turbo run test - run every package's Vitest suite |
pnpm --filter <pkg> test | run one package's suite |
pnpm --filter <pkg> test:watch | watch mode while developing |
Unit tests live alongside the code they cover (a package's tests/ directory) and assert behaviour and outcomes, not implementation details. Conventions:
- Write a test for every new function, bug fix, and any untested code you touch - each test should catch a real regression.
- Build fixtures with typed factory helpers using
Partial<T>from a package's exported types - never re-declare types by hand. - Put temp file paths under
os.tmpdir(); never hard-code/tmp.
End-to-end tests (Playwright)
The e2e suite lives in apps/web-next/e2e/ and uses Playwright with a single Chromium project. The specs are deliberately lean - read-only render/smoke checks of the critical journeys an anonymous visitor hits:
- marketing: landing, pricing, primary nav, theme toggle, cookie-consent banner
- auth: the
/authform renders and toggles between password and magic-link sign-in - guards: an unauthenticated
/dashboardvisit redirects to/auth
Because they never sign in or write data, they need only a booted app with a reachable database - no email server, no seeding.
Running it
| Command | What it does |
|---|---|
pnpm --filter web-next e2e | open Playwright's interactive UI mode (local) |
pnpm --filter web-next e2e:ci | install the browser, build + start the app, run headless |
pnpm e2e:ci | turbo run e2e:ci - the full headless run |
Start your local database first:
pnpm infra # start Docker services (Postgres, …) - present if you chose Docker infra at initPlaywright's webServer builds and starts a production server on port 3000 before the tests, and reuses an already-running dev server on that port - so a pnpm --filter web-next dev you already have open is reused with no rebuild.
Reports land in apps/web-next/playwright-report/ (open with pnpm --filter web-next exec playwright show-report); the report and test-results/ are git-ignored.
E2e ships local-only - the generated .github/workflows/ci.yml runs lint, typecheck, and unit tests but has no e2e job, so it doesn't rebuild the apps on every push (the deploy platform already builds them). To run e2e in CI, add a job to .github/workflows/ci.yml yourself that calls pnpm e2e:ci.
Adding a spec
apps/web-next/e2e/<journey>.spec.ts importing { expect, test } from "@playwright/test".ids, ARIA roles, and visible text - over CSS classes.expect(...).toPass() so a pre-hydration no-op click simply retries.To cover authenticated journeys, seed a verified user server-side through the auth API and inject the session cookie into Playwright's storage state - that keeps email and captcha out of the browser flow and stays deterministic.