feat: add testing infrastructure — Vitest, Playwright, CI, git hooks
Some checks are pending
Test / vitest (push) Waiting to run
Test / playwright (push) Blocked by required conditions
Test / visual (push) Blocked by required conditions

Add comprehensive testing covering 420 unit/handler tests across 24 Vitest
files, 9 Playwright E2E specs, accessibility scans, and visual regression.
Includes GitHub Actions CI, Husky pre-push hook, and TESTING.md docs.
This commit is contained in:
Jennie Robinson Faber 2026-04-04 16:07:21 +01:00
parent 036af95e00
commit 1e30ba23cd
35 changed files with 3637 additions and 5 deletions

90
TESTING.md Normal file
View file

@ -0,0 +1,90 @@
# Testing
## Quick Reference
```bash
npm test # Vitest watch mode
npm run test:run # Vitest single run (used by pre-push hook)
npm run test:e2e # Playwright E2E tests
npm run test:e2e:ui # Playwright with interactive UI
npm run test:a11y # Accessibility scans (axe-core)
npm run test:visual # Visual regression screenshots
npm run test:all # Vitest + Playwright together
```
## Vitest (Unit / Handler Tests)
Tests live in `tests/` mirroring the source structure. Two vitest projects:
- **server** (`tests/server/`) — Node environment, `setup.js` stubs Nitro auto-imports
- **client** (`tests/client/`) — jsdom environment for composables
### Test patterns
**Behavioral tests** mock models/services with `vi.mock()`, import the handler, and call it with `createMockEvent()`:
```js
vi.mock('../../../server/models/member.js', () => ({ default: { findOne: vi.fn() } }))
import handler from '../../../server/api/some/route.js'
import { createMockEvent } from '../helpers/createMockEvent.js'
```
**Source inspection tests** use `readFileSync` to verify structural properties (auth guards, import order) without executing handlers.
### Nitro auto-imports in tests
Handlers use Nitro auto-imports for `requireAuth`, `requireAdmin`, `validateBody`, and schemas. These are stubbed as globals in `tests/server/setup.js`. Individual tests can configure their behavior with `.mockResolvedValue()` etc.
## Playwright (E2E Tests)
Tests live in `e2e/`. Requires a running dev server and MongoDB.
### Auth helpers
`e2e/helpers/auth.js` provides `loginAsAdmin(page)` and `loginAsMember(page, email)` using the dev login endpoints. These set real JWT cookies.
`e2e/helpers/fixtures.js` extends Playwright's `test` with `adminPage` and `memberPage` fixtures.
### Running locally
```bash
# Start dev server (Playwright config does this automatically)
npm run dev
# Run tests
npm run test:e2e
# Interactive mode
npm run test:e2e:ui
```
### Visual regression
Baselines stored in `e2e/__screenshots__/`. Generate or update:
```bash
npm run test:visual:update
```
Note: Linux CI and macOS local produce different renders. Generate baselines in CI with `--update-snapshots`, then commit.
## Pre-Push Hook
Husky runs `npm run test:run` (Vitest only) before `git push`. All mocked, runs in ~1s. Playwright is not included — too slow and requires MongoDB.
## CI (GitHub Actions)
`.github/workflows/test.yml` defines three jobs:
1. **vitest** — runs on every push/PR to main
2. **playwright** — runs after vitest passes, uses MongoDB service container
3. **visual** — runs after vitest, `continue-on-error: true` during stabilization
CI uses dummy secrets (`JWT_SECRET: ci-test-jwt-secret`). No real Helcim/Resend/Slack tokens — those paths are mocked at Vitest level, and E2E skips the Helcim iframe.
## Known Limitations
- **Helcim iframe** — cannot be tested in E2E (cross-origin). Payment flows are tested at the Vitest handler level. E2E tests stop before the iframe opens.
- **Email delivery** — mocked at Vitest level. E2E verifies form submission, not actual email.
- **Slack invitations** — mocked at Vitest level.
- **Visual baseline OS drift** — generate baselines in CI, not locally.