Unit tests (Vitest)
Unit tests run with Vitest across both packages:API route test patterns
API route tests mock@verity/backend/schema and @verity/backend/db. Adding new schema imports to routes requires updating mock factories.
Mock db.transaction by defining the mock db inside the vi.mock factory to avoid hoisting issues:
E2E tests (Playwright)
E2E tests are written with Playwright and live infrontend/e2e/.
Running tests
.env.local has DATABASE_URL and SUPABASE_SERVICE_KEY set so teardown can clean up test data.
Configuration
Two Playwright projects infrontend/playwright.config.ts:
| Project | Session state | Use case |
|---|---|---|
auth | No auth | Auth flow tests (signup, login) |
authenticated | Pre-authenticated | Dashboard and protected route tests |
Test user management
Tests create isolatede2e-{timestamp}-{label}@test.verity.local users and orgs, which are deleted from the database after every run by global teardown.
Supabase pooler workaround
Transaction-mode pooler doesn’t guarantee read-after-write consistency across connections. E2E tests usetoPass() retry loops with page reloads at intervals [2000, 3000, 4000, 5000].
For negative assertions (“should NOT be visible”), put them inside the toPass() retry loop with page reload — the pooler may serve stale reads from a different connection.
Multi-step flows
Usetest.describe.serial with shared Page in beforeAll for flows that require sequential test execution.
Tips
- Create resources via API in
beforeAll, test UI interactions against them - Use
getByRole('heading', { name: '...' })to disambiguate when nav links and page headings share text - Use
page.locator('select:has(option[value="submitted"])')to target exam status select when multiple selects coexist
CI
CI runs on every PR and push tomain via .github/workflows/e2e.yml.