Skip to main content
Last Updated: 2026-05-13 This document is the single reference for how to run each test type, required environment variables, E2E authentication, and debugging. See also: SPEC_TEST_COVERAGE.md (spec–test matrix), TESTING_IMPROVEMENTS.md (historical review findings and follow-up backlog), TEST_ORGANIZATION_STANDARDS.md (naming and structure), tests/README.md (suite overview), EDGE_AND_API_TESTING.md (Edge Functions and API local testing, debugging, troubleshooting). To test and verify Sentry configuration, see Sentry verification runbook. Canonical skill patterns (Cursor IDE): When writing or migrating tests, follow these skill SOPs — they encode the patterns that this guide formalizes:

1. How to run each test type

Single file or pattern:

2. Environment variables

Local .env.local (git-ignored) example for RLS and E2E:

2a. E2E conventions (Playwright vs Vitest)

  • Playwright runs only *.spec.ts (and .spec.tsx) in tests/e2e/ (see playwright.config.ts testMatch). Use npm run test:e2e or npm run test:e2e:{core} to run these.
  • Vitest can run *.test.ts under tests/e2e/ (e.g. financial-transactions.test.ts, leave-accruals.test.ts); these are integration-style E2E with Supabase and are not run by Playwright. Run them with npm test -- tests/e2e/.
  • Some spec files are in Playwright’s testIgnore (e.g. fa/budget-scenarios.test.ts, ce/activities.test.ts) and run as Vitest or are documented in the spec–test matrix.
  • Critical flows (auth, multi-tenant, financial, workflow approvals, leave accruals) are listed in SPEC_TEST_COVERAGE.md § Critical flows.

3. E2E authentication

3.1 Shared auth fixture and setup project

  • Fixture: tests/e2e/fixtures/auth.fixture.ts provides loginAsTestUser(page, role), TEST_USERS, and helpers. Use for per-test or per-spec login when you need a specific role (e.g. staff, manager).
  • Global setup: tests/e2e/global-setup.auth.ts only creates tests/e2e/.auth/. It does not log in (so it does not require the app to be up).
  • Setup project: After the web server is ready, the Playwright project e2e-setup-auth runs tests/e2e/setup-auth.spec.ts, which logs in as admin and saves browser state to tests/e2e/.auth/admin.json. The chromium-authenticated project depends on this, so auth runs after the server is reachable.
  • Authenticated project: Playwright config defines a project chromium-authenticated that uses storageState: 'tests/e2e/.auth/admin.json'. Specs that use test.use({ project: 'chromium-authenticated' }) start already logged in as admin and do not need to call login in each test (unless switching to another role).

3.2 When to use which

  • Need admin (or default) for whole spec: Use test.use({ project: 'chromium-authenticated' }) in the describe. No beforeEach login.
  • Need a different role (e.g. staff/employee) in one test: Import loginAsTestUser from the fixture and call await loginAsTestUser(page, 'staff') at the start of that test.
  • Public/unauthenticated flows (login page, registration, public form): Do not use the authenticated project; run in the default (no storageState) project.

3.3 Env vars for E2E auth

  • Same as in the fixture: TEST_USER_ADMIN_EMAIL, TEST_USER_ADMIN_PASSWORD, TEST_ORG_SLUG, and optionally TEST_USER_STAFF_*, TEST_USER_MANAGER_*, TEST_USER_VIEWER_*.
  • Default (no env): The fixture uses seed users (admin@test-org-alpha.example, password password, org slug test-org-alpha). For this to work, seed data must be applied after npx supabase db reset (see Run all tests locally).
  • For CI, set GitHub Actions secrets if you want authenticated E2E to pass.

3.4 Auth state file

  • Path: tests/e2e/.auth/admin.json (created by global setup).
  • Git: Add tests/e2e/.auth/ to .gitignore so auth state is not committed.

4. Debugging

4.1 Vitest (unit, integration, RLS)

  • UI: Run npm run test:ui to open Vitest’s browser UI. Use it to run and debug tests by file or by test.
  • VS Code / Cursor: Use the Vitest extension to run and debug from the editor (Run/Debug on the test or describe block).

4.2 Playwright (E2E)

  • Report: After a run, open the HTML report: npx playwright show-report (uses playwright-report/ by default).
  • Trace: On failure or first retry, a trace is recorded (config: trace: 'on-first-retry'). Open it via the report or with npx playwright show-trace path/to/trace.zip.
  • UI: Run npm run test:e2e:ui for Playwright’s UI mode to step through and debug tests.
  • CI: In GitHub Actions, the workflow uploads the playwright-report/ (and optionally test-results/) as artifacts. Download the artifact and open the report or trace locally.

4.3 Edge Functions (debugging and troubleshooting)

  • Breakpoints: Run npm run supabase:functions:serve:inspect, then open Chrome → chrome://inspect → Configure 127.0.0.1:8083 → Open dedicated DevTools for Node. Trigger a function to pause and step. See EDGE_AND_API_TESTING.md.
  • Troubleshooting: 401/403, import errors, file writes, hanging requests — see EDGE_AND_API_TESTING.md.

5. CI summary

  • Build workflow (PR + push): .github/workflows/build.yml runs format check, typecheck, lint, npm test with SKIP_SUPABASE_TESTS=true (non-Supabase Vitest suites), RLS coverage check (npm run check-rls-coverage --min-coverage 100), build (npm run build), installs Playwright Chromium, runs baseline smoke gate (npm run test:baseline:smoke — Playwright smoke + RLS smoke + audit completeness/enforce), and uploads audit artifacts.
  • E2E (full) workflow (opt-in): .github/workflows/e2e-full.yml runs the full Playwright suite (npx playwright test --project=chromium) on:
    • workflow_dispatch (manual: gh workflow run e2e-full.yml)
    • Nightly schedule (07:00 UTC)
    • Pull requests labeled e2e-full
    Required secrets: E2E_VITE_SUPABASE_URL, E2E_VITE_SUPABASE_PUBLISHABLE_KEY, TEST_USER_ADMIN_EMAIL, TEST_USER_ADMIN_PASSWORD, TEST_ORG_SLUG. Optional role-specific secrets: TEST_USER_STAFF_*, TEST_USER_MANAGER_*, TEST_USER_VIEWER_*. Uploads playwright-report/ and test-results/ artifacts on completion / failure.
  • SKIP_SUPABASE_TESTS=true behavior: CI test job excludes Supabase-dependent suites (tests/rls/**, tests/integration/**) and DB-backed FM unit tests (fleet-maintenance-triggers, fleet-mpg-calculation) via Vitest config. This keeps the job deterministic without Supabase credentials while baseline smoke/audit gates still run.
  • RLS: Default command npm run test:rls runs the full RLS baseline (legacy clusters enabled) via the Supabase-aware full runner. RLS coverage is enforced in build workflow at 100%.
  • E2E: Default build workflow runs deterministic Chromium smoke only (npm run test:e2e:smoke). For broader E2E coverage in CI, add a dedicated job that sets TEST_USER_ADMIN_EMAIL, TEST_USER_ADMIN_PASSWORD, and Supabase env vars (from GitHub secrets), then runs npm run build && npm run test:e2e.

6. Quick reference

  • Run unit tests for a path: npm run test:unit -- tests/unit/hr/
  • Run RLS tests (full baseline): npm run test:rls (ensure Supabase env is set, or wrapper will auto-skip DB-dependent specs).
  • Full RLS run: npm run test:rls
  • Run RLS smoke: npm run test:rls:smoke
  • Run baseline confidence gate: npm run test:baseline
  • Run baseline smoke gate: npm run test:baseline:smoke
  • Run E2E smoke locally: npm run test:e2e:smoke
  • Open Vitest UI: npm run test:ui
  • Open Playwright report after E2E: npx playwright show-report

6.1 Test audit gate defaults (CI)

npm run test:audit:enforce reads thresholds from environment variables. In CI build workflow we enforce:

6.2 Common test failures and fixes

Patterns we hit during the 2026-04 dev/test stability sweep — keep these in mind when adding or migrating tests:
  • Failed to resolve import "@/cores/{core}/hooks/{useFoo}" — HR hooks were reorganized into subfolders (ats/, oversight/, employees/, payroll/, scheduling/, credentialing/, onboarding/, benefits/). Update the import path in the test (and any vi.mock(...) / await import(...) calls) to the new location.
  • Failed to resolve import "npm:@sentry/deno" (or other npm: specifiers) — A unit test is reaching into supabase/functions/_shared/*.ts. Don’t do that. Either:
    • Extract the pure logic into src/shared/lib/* and have the Edge Function import it from there, OR
    • Rely on the Vitest alias safety-net in vitest.config.ts that maps npm:@sentry/deno*tests/mocks/deno-sentry-shim.ts. (The shim re-exports the surface as no-ops.)
  • useNavigation must be used within NavigationProvider / useLocation() may be used only in the context of a <Router> / No QueryClient set, use QueryClientProvider — Component test renders a real component without provider context. Wrap with TestProviders from @/tests/utils/providers (which already provides MemoryRouter, QueryClientProvider, OrganizationProvider):
    If your test wraps its own router (e.g. MemoryRouter with <Routes>), pass noRouter to TestProviders to avoid React Router’s “cannot render a Router inside another Router” invariant.
  • Cannot find module '@/...' from a require() call — Tests run as ESM under Vitest 4; replace require(...) with a static import and a vi.mock(...) declaration at the top of the file.
  • Supabase chainable mock returns undefined — When mocking supabase.from(...).select(...).eq(...).range(...), build the chain so it is both chainable and thenable (the real PostgrestFilterBuilder is). See tests/unit/platform/audit/useAuditLogsList.test.ts for the canonical pattern (createChainableMock).
  • Fetch / Response stubbing in MCP / edge-shared tests — Use new Response(JSON.stringify({...}), { status }) instead of POJO { ok, status, text: () => Promise.resolve('') }. Node’s undici fetch implementation calls clone() and other Response methods that POJO stubs don’t have.
  • Route or constant drift — Don’t reach for it.skip(). Read the source, decide whether the source or the test is authoritative (source usually wins), and align. Many recent failures were just constants that moved (e.g. DEFAULT_SLA_THRESHOLD_MINUTES: 240 → 120, INTAKE_STATUSES length, /hr/benefits/enrollment → /hr/my-benefits/enroll).

7. Run all tests locally

Use this checklist to run every test type (unit, integration, RLS, E2E) on your machine.

Prerequisites

  • Node 20
  • Supabase CLI (or npx supabase from project root)
  • Docker (for local Supabase)

One-time local setup

  1. Start local Supabase
    (Or supabase start if the CLI is on PATH.)
  2. Apply migrations and base seeds
    This applies all migrations and runs base seeds (organizations, sites, departments, users). Test users (e.g. admin@test-org-alpha.example, password password) are created and used by E2E. See supabase/seeds/README.md and SUPABASE_CLI_LOCAL_WORKFLOW.md.
  3. Start Edge Functions (optional, for integration tests that invoke functions)
    In a separate terminal, run npm run supabase:functions:serve. See EDGE_AND_API_TESTING.md.
  4. Set Supabase env for RLS and E2E Copy .env.example to .env.local (or set env in your shell) and set:
    • VITE_SUPABASE_URL — from npx supabase status (API URL) when using local Supabase
    • VITE_SUPABASE_PUBLISHABLE_KEY — anon key from npx supabase status
    • SUPABASE_SERVICE_ROLE_KEY — service_role key from npx supabase status
    For a linked remote (e.g. staging), use that project’s URL and keys instead.

Optional E2E overrides

If you use custom test users instead of seed users, set:
  • TEST_USER_ADMIN_EMAIL, TEST_USER_ADMIN_PASSWORD, TEST_ORG_SLUG
  • And, if needed: TEST_USER_STAFF_*, TEST_USER_MANAGER_*, TEST_USER_VIEWER_*

Commands

  • Vitest (unit + integration + RLS): npm test
  • RLS baseline (legacy clusters included by default): npm run test:rls
  • Edge function (Deno) tests: npm run test:functions (see EDGE_AND_API_TESTING.md).
  • E2E: npm run build && npm run test:e2e
    Or npm run test:e2e:smoke for a shorter run (deterministic auth/security route smoke).

Troubleshooting

  • E2E auth fails: Confirm base seed was applied (npx supabase db reset runs base seeds). Fixture defaults use admin@test-org-alpha.example / password / test-org-alpha. If using custom users, set the env vars and ensure those users exist in the DB.
  • RLS tests fail: Confirm VITE_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, and VITE_SUPABASE_PUBLISHABLE_KEY are set and that local Supabase is running (or the linked project is reachable).
  • Runtime env mismatch in cloud/dev shells: If injected runtime vars differ from repo .env, load project-local values before DB-integrated tests:
    Then verify refs are aligned with npm run test:audit:completeness (Environment Consistency section).

8. Run E2E in Cursor Cloud (or any Docker-less environment)

When running E2E in environments without Docker (e.g. Cursor Cloud VMs), local Supabase is unavailable. Use a remote Supabase project (dev/staging) instead:

One-time setup

  1. Install Playwright browsers (browsers are not pre-installed on cloud VMs):
  2. Provision Supabase env vars. Provide the project URL + anon key in .env or .env.local:
    The build (npm run build) requires both vars; @julr/vite-plugin-validate-env enforces this.
  3. Provision E2E test user credentials (a seeded admin who can log in via password):
    Default fixture values target the local seed user (admin@test-org-alpha.example / password / test-org-alpha); override with the env vars above when targeting a remote project.
  4. Verify Supabase reachability + admin login (cheap pre-flight; mirrors setup-auth.spec.ts):
    REST should return 200/401; the token endpoint should return a JSON body with access_token.
  5. Build the app (Playwright’s webServer will run npm run preview on :4173, which requires dist/):

Running

  • Smoke (~10 s): npm run test:e2e:smoke
  • Full (chromium-only): npx playwright test --project=chromium
  • Per folder: npm run test:e2e:hr / :fa / :ce / :fw / :gr / :platform etc.
  • Bulk triage helper: bash scripts/test-analysis/run-e2e-triage.sh runs every per-folder set in sequence and writes results to reports/e2e-triage/{folder}.txt for analysis.

Notes

  • Docker is NOT required for these flows; only npx supabase start / db reset need Docker. Schema changes still need to be tested through the full local Supabase workflow.
  • tests/e2e/.auth/admin.json is git-ignored and re-created by the e2e-setup-auth Playwright project on each run.
  • Specs that require seeded data (e.g. CE-UX-05 lead-conversion-wizard expects a “Test LeadE2E” lead) skip themselves when the data is absent. Track these via the Skipped column in reports/e2e-triage/SUMMARY.md.

9. AI / interactive browser testing

When an AI agent (or a human) drives the app in a real browser — exploratory verification, debugging, or confirming a flow before claiming “E2E done” — follow the conventions below. Playwright *.spec.ts files remain the source of truth for regression; the interactive browser session is an additional check, not a replacement. For the automated auto-heal verdict loop, see BROWSER_AUTOHEAL.md.

9.1 Credential policy

Do not store plaintext credentials in docs or prompts. Use environment variables:
  • TEST_USER_ADMIN_EMAIL, TEST_USER_ADMIN_PASSWORD
  • TEST_USER_STAFF_EMAIL, TEST_USER_STAFF_PASSWORD
  • TEST_USER_MANAGER_EMAIL, TEST_USER_MANAGER_PASSWORD
  • TEST_USER_VIEWER_EMAIL, TEST_USER_VIEWER_PASSWORD
If these are not set, stop and configure them before testing authentication flows (see §2 for the .env.local example and §3.3 for the fixture defaults). Security notes: use test accounts only; never commit or document raw credentials; never include PHI/PII in prompts, screenshots, or logs; keep tenant isolation and RLS behavior validated during tests.

9.2 Standard browser testing workflow

Login flow: open the app preview or dev URL (http://localhost:8080). If already authenticated, continue; if at /auth, sign in using TEST_USER_ADMIN_EMAIL / TEST_USER_ADMIN_PASSWORD, verify the redirect to an authenticated page, then proceed to the target workflow.
  1. Form submission and validation
  2. CRUD lifecycle
  3. Navigation and breadcrumbs
  4. Permission-protected routes
  5. Mobile/responsive layout
  6. Cross-module journey transitions

9.4 Troubleshooting

  • Login fails: confirm env vars are present in the running environment; verify the test users exist in the target Supabase project; check the browser console and network logs for auth errors.
  • No organization access: verify role assignments in pf_user_role_assignments; confirm org/site records exist; verify permissions and RLS policies.

9.5 HR workflows (browser verification)

HR has two complementary modes: Playwright specs are the source of truth. AI may additionally use the browser MCP to verify a flow manually. HR workflow routes:
  • Self-service: /hr/me, /hr/me/profile, /hr/me/onboarding, /hr/me/pay, /hr/my-performance
  • Admin: /hr/employees, /hr/employees/new, /hr/leave, /hr/leave/new, /hr/ats/candidates, /hr/performance (and sub-routes), /hr/employee-relations (incidents, disciplinary, grievances, investigations, my-cases), /hr/benefits/plans, /hr/benefits/enrollments, /hr/analytics
For the full HR browser-testing SOP (when to use, auth, consistency), see the hr-e2e-browser-testing skill. Run the Playwright HR suite with run-e2e-core --core hr; for interactive verification use verify-core-workflow-browser --core hr. New HR E2E specs follow specs/_templates/tests/E2E_HR_WORKFLOW_TEMPLATE.ts.