Documentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
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
| Test type | Command | Notes |
|---|
| All (Vitest) | npm test | All Vitest suites except E2E. When SKIP_SUPABASE_TESTS=true, excludes Supabase-dependent RLS/integration suites and DB-backed FM unit tests via vitest.config.ts. |
| Unit | npm run test:unit | tests/unit/ |
| Unit (watch) | npm run test:unit:watch | Re-runs on file changes |
| Unit (Vitest UI) | npm run test:ui | Browser UI for running/debugging Vitest tests |
| Integration | npm run test:integration | tests/integration/ |
| RLS | npm run test:rls | Full tests/rls/ baseline (legacy clusters included); Supabase-aware wrapper auto-sets SKIP_SUPABASE_TESTS=true if env/REST checks fail |
| RLS (full suite) | npm run test:rls | Full RLS suite / 100% coverage gate |
| RLS (smoke) | npm run test:rls:smoke | Fast DB-connected smoke over pf_profiles + pf_organizations RLS suites |
| Baseline confidence gate | npm run test:baseline | Runs unit + integration + RLS smoke + E2E smoke + audit completeness/enforcement |
| Baseline smoke gate | npm run test:baseline:smoke | Fast confidence gate: RLS smoke + E2E smoke + audit completeness/enforcement |
| E2E (all) | npm run test:e2e | Full Playwright suite (all projects) |
| E2E (UI) | npm run test:e2e:ui | Playwright UI mode |
| E2E (auth folder) | npm run test:e2e:auth | tests/e2e/auth/ only |
| E2E (HR folder) | npm run test:e2e:hr | tests/e2e/hr/ only |
| E2E (FA folder) | npm run test:e2e:fa | tests/e2e/fa/ only (Playwright .spec.ts only) |
| E2E (CE folder) | npm run test:e2e:ce | tests/e2e/ce/ only |
| E2E (FW folder) | npm run test:e2e:fw | tests/e2e/fw/ only |
| E2E (GR folder) | npm run test:e2e:gr | tests/e2e/gr/ only (root-level GR specs) |
| E2E (platform) | npm run test:e2e:platform | tests/e2e/platform/ only |
| E2E (smoke) | npm run test:e2e:smoke | Deterministic auth/security route smoke (tests/e2e/smoke/auth-security-smoke.spec.ts, Chromium only, list reporter) |
| RLS coverage | npm run check-rls-coverage | Report or enforce RLS test coverage; use --missing-only, --min-coverage N, --json |
| Test completeness audit | npm run test:audit:completeness | Generates reports/audits/TEST_COMPLETENESS_AUDIT.{md,json} with spec/matrix gaps, RLS coverage rollup, placeholder/skip counts, Supabase env consistency, and E2E auth adoption |
| Quick (changed) | npm run test:quick | Unit tests for changed files only |
| Coverage | npm run test:coverage | Vitest coverage report |
| Edge function (Deno) tests | npm run test:functions | Deno tests for Edge Functions. Requires local Supabase + functions serve for HTTP tests; see EDGE_AND_API_TESTING.md. |
Single file or pattern:
npm test -- tests/unit/hr/useEmployees.test.ts
npm test -- tests/rls/hr-employees.rls.test.ts
npx playwright test tests/e2e/hr/compensation-management.spec.ts
2. Environment variables
| Test type | Local | CI |
|---|
| Unit | None | SKIP_SUPABASE_TESTS=true (unit job) |
| Integration | Optional: VITE_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | SKIP_SUPABASE_TESTS=true (integration job runs mocked) |
| RLS | Required: VITE_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, VITE_SUPABASE_PUBLISHABLE_KEY | Same (secrets); RLS job runs only when not a fork |
| E2E | For authenticated E2E, apply seed data first (see Run all tests locally); fixture defaults match supabase/seeds/base/02_users.sql (admin@test-org-alpha.example / password / test-org-alpha). Override with TEST_USER_ADMIN_EMAIL, TEST_USER_ADMIN_PASSWORD, TEST_ORG_SLUG (and role-specific vars if needed). | E2E workflows do not set test user secrets by default; set in GitHub secrets if you need authenticated E2E in CI. |
| Edge/API local testing | For integration tests or Deno tests that invoke Edge Functions locally: VITE_SUPABASE_URL=http://localhost:54321, anon key from npx supabase status. Run npm run supabase:functions:serve in a separate terminal. | — |
Local .env.local (git-ignored) example for RLS and E2E:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Optional: E2E test users (override fixture fallbacks)
TEST_USER_ADMIN_EMAIL=admin@your-test-org.com
TEST_USER_ADMIN_PASSWORD=YourTestPassword123!
TEST_ORG_SLUG=your-org-slug
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.spec.ts, ce/activities.spec.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:
| Gate variable | CI value | Meaning |
|---|
MAX_COMPLETED_SPECS_MISSING_FROM_MATRIX | 0 | Completed specs must all be present in matrix |
MAX_COMPLETED_SPECS_WITH_UNIT_OR_INTEGRATION_GAPS | 0 | No completed spec can have Unit/Integration = Partial/No |
MAX_PLACEHOLDER_ASSERTIONS | 0 | No expect(true).toBe(true) placeholders |
MAX_EXPLICIT_SKIP_COUNT | 0 | No .skip in active test suites |
MAX_E2E_WITHOUT_SHARED_AUTH | 0 | All non-exempt Playwright specs adopt shared auth pattern |
MAX_POLICY_DRIFT_SIGNALS | 0 | No detected RLS policy drift signals |
MAX_LIKELY_MISSING_CHECKLIST_ITEMS | 0 | No likely-missing checklist backlog for completed specs |
MAX_DEFAULT_EXCLUDED_RLS_CLUSTERS | 0 | Default test:rls cannot exclude legacy clusters |
MAX_E2E_SMOKE_SKIP_DIRECTIVES | 0 | Smoke spec files cannot contain skip/fixme directives |
MIN_E2E_SMOKE_SPEC_FILES | 1 | At least one executable smoke spec must exist |
MIN_RLS_COVERAGE | 100 | Audit-reported RLS table coverage minimum |
ENFORCE_RUNTIME_SUPABASE_HEALTH | false | Enable strict runtime Supabase gates (URL/key alignment + REST reachability floor) |
MIN_RUNTIME_REST_REACHABLE | 0 | Runtime Supabase REST reachability floor (applied only when ENFORCE_RUNTIME_SUPABASE_HEALTH=true) |
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):
import { render } from '@testing-library/react';
import { TestProviders } from '@/tests/utils/providers';
render(
<TestProviders route="/hr">
<DesktopHeader user={user} onSignOut={vi.fn()} />
</TestProviders>,
);
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
-
Start local Supabase
(Or
supabase start if the CLI is on PATH.)
-
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.
-
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.
-
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:
set -a && source ".env" && set +a
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
-
Install Playwright browsers (browsers are not pre-installed on cloud VMs):
npx playwright install chromium # add --with-deps if root, otherwise system deps may already be present
-
Provision Supabase env vars. Provide the project URL + anon key in
.env or .env.local:
VITE_SUPABASE_URL=https://<ref>.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=<anon key>
The build (npm run build) requires both vars; @julr/vite-plugin-validate-env enforces this.
-
Provision E2E test user credentials (a seeded admin who can log in via password):
TEST_USER_ADMIN_EMAIL=<admin email seeded in target project>
TEST_USER_ADMIN_PASSWORD=<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.
-
Verify Supabase reachability + admin login (cheap pre-flight; mirrors
setup-auth.spec.ts):
set -a && source .env && set +a
curl -sS -o /dev/null -w "REST: %{http_code}\n" \
"$VITE_SUPABASE_URL/rest/v1/" -H "apikey: $VITE_SUPABASE_PUBLISHABLE_KEY"
curl -sS -X POST "$VITE_SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $VITE_SUPABASE_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$TEST_USER_ADMIN_EMAIL\",\"password\":\"$TEST_USER_ADMIN_PASSWORD\"}"
REST should return 200/401; the token endpoint should return a JSON body with access_token.
-
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.