ghostguild-org/TESTING.md
Jennie Robinson Faber 1e30ba23cd
Some checks are pending
Test / vitest (push) Waiting to run
Test / playwright (push) Blocked by required conditions
Test / visual (push) Blocked by required conditions
feat: add testing infrastructure — Vitest, Playwright, CI, git hooks
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.
2026-04-04 16:07:21 +01:00

3.2 KiB

Testing

Quick Reference

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():

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

# 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:

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.