Skip to main content

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:
  • module-testing-strategy — module-level testing strategy and coverage requirements across unit, integration, RLS, and E2E.
  • playwright-testing — E2E test organization, auth fixtures, flake reduction.
  • tdd-workflow — TDD red/green/refactor loop and when to apply it.

1. How to run each test type

Test typeCommandNotes
All (Vitest)npm testAll 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.
Unitnpm run test:unittests/unit/
Unit (watch)npm run test:unit:watchRe-runs on file changes
Unit (Vitest UI)npm run test:uiBrowser UI for running/debugging Vitest tests
Integrationnpm run test:integrationtests/integration/
RLSnpm run test:rlsFull 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:rlsFull RLS suite / 100% coverage gate
RLS (smoke)npm run test:rls:smokeFast DB-connected smoke over pf_profiles + pf_organizations RLS suites
Baseline confidence gatenpm run test:baselineRuns unit + integration + RLS smoke + E2E smoke + audit completeness/enforcement
Baseline smoke gatenpm run test:baseline:smokeFast confidence gate: RLS smoke + E2E smoke + audit completeness/enforcement
E2E (all)npm run test:e2eFull Playwright suite (all projects)
E2E (UI)npm run test:e2e:uiPlaywright UI mode
E2E (auth folder)npm run test:e2e:authtests/e2e/auth/ only
E2E (HR folder)npm run test:e2e:hrtests/e2e/hr/ only
E2E (FA folder)npm run test:e2e:fatests/e2e/fa/ only (Playwright .spec.ts only)
E2E (CE folder)npm run test:e2e:cetests/e2e/ce/ only
E2E (FW folder)npm run test:e2e:fwtests/e2e/fw/ only
E2E (GR folder)npm run test:e2e:grtests/e2e/gr/ only (root-level GR specs)
E2E (platform)npm run test:e2e:platformtests/e2e/platform/ only
E2E (smoke)npm run test:e2e:smokeDeterministic auth/security route smoke (tests/e2e/smoke/auth-security-smoke.spec.ts, Chromium only, list reporter)
RLS coveragenpm run check-rls-coverageReport or enforce RLS test coverage; use --missing-only, --min-coverage N, --json
Test completeness auditnpm run test:audit:completenessGenerates 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:quickUnit tests for changed files only
Coveragenpm run test:coverageVitest coverage report
Edge function (Deno) testsnpm run test:functionsDeno 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 typeLocalCI
UnitNoneSKIP_SUPABASE_TESTS=true (unit job)
IntegrationOptional: VITE_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEYSKIP_SUPABASE_TESTS=true (integration job runs mocked)
RLSRequired: VITE_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, VITE_SUPABASE_PUBLISHABLE_KEYSame (secrets); RLS job runs only when not a fork
E2EFor 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 testingFor 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 variableCI valueMeaning
MAX_COMPLETED_SPECS_MISSING_FROM_MATRIX0Completed specs must all be present in matrix
MAX_COMPLETED_SPECS_WITH_UNIT_OR_INTEGRATION_GAPS0No completed spec can have Unit/Integration = Partial/No
MAX_PLACEHOLDER_ASSERTIONS0No expect(true).toBe(true) placeholders
MAX_EXPLICIT_SKIP_COUNT0No .skip in active test suites
MAX_E2E_WITHOUT_SHARED_AUTH0All non-exempt Playwright specs adopt shared auth pattern
MAX_POLICY_DRIFT_SIGNALS0No detected RLS policy drift signals
MAX_LIKELY_MISSING_CHECKLIST_ITEMS0No likely-missing checklist backlog for completed specs
MAX_DEFAULT_EXCLUDED_RLS_CLUSTERS0Default test:rls cannot exclude legacy clusters
MAX_E2E_SMOKE_SKIP_DIRECTIVES0Smoke spec files cannot contain skip/fixme directives
MIN_E2E_SMOKE_SPEC_FILES1At least one executable smoke spec must exist
MIN_RLS_COVERAGE100Audit-reported RLS table coverage minimum
ENFORCE_RUNTIME_SUPABASE_HEALTHfalseEnable strict runtime Supabase gates (URL/key alignment + REST reachability floor)
MIN_RUNTIME_REST_REACHABLE0Runtime 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

  1. Start local Supabase
    npx supabase start
    
    (Or supabase start if the CLI is on PATH.)
  2. Apply migrations and base seeds
    npx supabase db reset
    
    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:
    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

  1. 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
    
  2. 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.
  3. 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.
  4. 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.
  5. Build the app (Playwright’s webServer will run npm run preview on :4173, which requires dist/):
    npm run build
    

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.