The QA system is a hermetic, account-aware release gate. It tests the real Next.js application against disposable local services, provisions explicit account and role fixtures, replaces only remote provider boundaries, and removes the generated state when the run ends.
Use this page when you need to run the gates, understand what they prove, or add coverage for a new route. For the lower-level Vitest, Playwright, Lighthouse, and audit catalog, see Testing. The complete implementation ledger and remaining work live in QATests.md at the repository root.
Hermetic local QA Phases 0–6 are complete. Phase 7, which adds representative browser-driven admin CRUD journeys, is in progress. Jobs, handlers, blog categories, and localized tags are covered; CMS/blog pages, media, and cron UI journeys remain open. Real-provider and remote-runner canaries are optional and outside the disposable local gate.
Start here
The repository requires its pinned Node 24.18.1 and pnpm 11.20.0 versions. Docker must be running because the main gate starts a local Supabase stack.
# Recommended comprehensive local gate
pnpm run qa:local:gate
# Inspect or stop the local QA stack when troubleshooting
pnpm run qa:local:status
pnpm run qa:local:stop
qa:local:gate is self-contained. Focused qa:local:* profiles use the same guarded lifecycle and are faster when you are working in one domain. Direct test:e2e:* commands generally expect an already running application and, for authenticated suites, pre-provisioned validated personas.
How the disposable gate works
Validate the target before creating privileged clients. Zod-validated QA configuration rejects production or indexable targets, hosted Supabase origins under local intent, mismatched approved origins, privileged publishable keys, live Stripe keys, and unsafe email/provider settings.
Start clean local infrastructure. The runner starts the pinned Supabase CLI stack on loopback, replays the schema and every migration, loads deterministic seed data, and starts the Next.js application on port
3777with test-only credentials.Prove the database contract first. pgTAP and database lint verify RLS, Data API grants, constraints, RPC permissions, account isolation, credit-ledger behavior, cron objects, and fresh-schema/migration consistency.
Provision account-centric personas. Setup creates explicit profiles, Accounts, Memberships, roles, access states, and isolated browser storage states. Mutable tests receive worker-owned users and Accounts so parallel projects cannot share state accidentally.
Run application contracts. Vitest checks domain behavior; Playwright drives API and browser journeys through real routes; axe-core, viewport, visual, cross-browser, PWA, performance, and reliability checks run in their assigned profiles.
Collect bounded diagnostics and clean exactly what the run owns. Failures include a reproduction command and secret-safe evidence. Privileged runs do not persist traces, screenshots, videos, raw URLs, headers, bodies, or credentials. Cleanup removes exact worker/run resources, browser state, temporary email/provider files, and the disposable local volume—even after handled failure or interruption.
Command map
Self-contained local profiles
| Command | Use it for |
|---|---|
pnpm run qa:local:gate | The default disposable gate. Runs every retained local QA profile and is the best pre-merge proof. |
pnpm run qa:local:auth-deep | Passwordless Auth, OAuth contracts, onboarding, invitations, sessions, recovery/setup, prelaunch, and authorization depth. |
pnpm run qa:local:private | Private dashboard, chat/SSE accounting and resilience, documents/RAG, referrals, affiliates, account settings, and tenant isolation. |
pnpm run qa:local:org | Organization owner/admin/member routes, APIs, mutations, billing boundaries, accessibility, and responsive behavior. |
pnpm run qa:local:admin | Platform-admin pages and APIs, CRUD/actions, validation, denials, audit rows, and error-log redaction. |
pnpm run qa:local:billing | B2C/B2B billing, checkout/portal, signed Stripe webhooks, replay/order/idempotency, refunds, disputes, trials, and credit accounting. |
pnpm run qa:local:phase6 | Production-build non-functional gate: accessibility, responsive, visual, cross-browser, Lighthouse, PWA/offline, privacy, query budgets, and bounded load. |
pnpm run qa:local:phase7 | Focused browser-driven admin CRUD journeys with persisted database and Storage postconditions. |
Run pnpm run qa:local:phase6:update-visuals only when you intend to inspect and approve every changed screenshot. The normal gate compares committed baselines and never auto-approves a visual diff.
Lower-level and already-provisioned commands
| Command family | Purpose |
|---|---|
pnpm run test | All Vitest unit and domain contracts. |
pnpm run qa:local:test-db / qa:local:lint-db | pgTAP and database lint against the local QA stack. |
pnpm run test:e2e:auth / test:e2e:oauth / test:e2e:api | Anonymous Auth, deterministic OAuth, and protected-API authorization contracts. |
pnpm run test:e2e:private / org / admin / billing / personas | Focused Playwright suites against a validated, provisioned target. |
pnpm run test:a11y / test:responsive | Public accessibility and required viewport coverage. |
pnpm run test:cross-browser / test:visual | Browser compatibility and committed snapshot comparison. |
pnpm run test:staging | Readiness probes for an explicitly configured isolated staging deployment. |
What the system proves
The automation is layered so a failure is caught as close as possible to its source:
| Layer | Tooling | Main proof |
|---|---|---|
| Domain and configuration | Vitest | Validation, redirects, permissions, calculations, serializers, provider parsing, and failure semantics. |
| Database | Supabase CLI + pgTAP | RLS, grants, constraints, triggers, RPCs, migrations, account isolation, and atomic ledger behavior. |
| API | Playwright request/API suites | Auth, role and account scope, CSRF, rate limits, Zod failures, sanitization, idempotency, headers, and safe errors. |
| Browser journeys | Playwright | Real user behavior across public, private, organization, admin, Auth, billing, CMS, and account flows. |
| Inclusive UI | axe-core + Playwright | WCAG 2.2 AA, keyboard behavior, focus, 200% reflow, reduced motion, touch targets, and five required viewport widths. |
| Release quality | Playwright + Lighthouse CI | Visual regression, Chromium/Firefox/WebKit, PWA/offline, consent boundaries, response and query budgets, and bounded load. |
Every configured locale receives navigation/render smoke coverage. Functional journeys use a deterministic default locale, with locale-sensitive assertions repeated across French and English regional variants.
Accounts, personas, and isolation
The fixtures follow the product architecture: domain data attaches to an Account, not directly to a User. A persona is valid only when its profile, Account, Membership, role, billing/access state, and active-account selection are explicit.
The core local matrix includes ordinary members, workspace admins, workspace owners, and a platform admin, plus anonymous and specialized states such as zero credits, disabled access, pending invitations, unrelated workspaces, active subscriptions, and licenses. Tests use run and worker identifiers for mutable records, seed dynamic resource IDs instead of hardcoding UUIDs, and assert both wrong-role denial and cross-account denial.
This is why the gate can run state-changing projects in parallel without weakening tenant-isolation assertions or allowing one test to depend on another test's mutation.
Route manifests prevent silent coverage drift
Two typed manifests classify every page and route handler:
tests/manifests/pages.tsrecords the surface, persona, locale, feature gate, and expected page outcome.tests/manifests/api-routes.tsrecords exported/effective methods, domain, security wrapper, access class, mutation/CSRF behavior, account scope, validation, idempotency, side effects, and expected errors.
The route-drift guard scans app/**/page.tsx and app/**/route.ts. A new or changed route fails until its manifest entry says how it is tested, redirected, feature-gated, or intentionally excluded with a reason. This turns missing QA classification into a review-time failure instead of a future production surprise.
Third-party boundaries stay deterministic
Core browser journeys do not stub the application's own API. They keep the real route, domain logic, Supabase writes, Storage behavior, security middleware, and browser UI, then replace only the remote dependency:
| Integration | Local strategy |
|---|---|
| Supabase Auth and email | Local Auth plus Mailpit and a run-scoped application-email recorder. |
| OAuth | Deterministic provider-boundary contracts for provider, locale, PKCE, consent, cancellation, and callback failures. |
| Stripe | Real SDK/signature/application flow against a secret-free loopback HTTP boundary and signed fixtures. |
| AI and embeddings | Loopback OpenRouter-compatible streaming/embedding provider with fixed usage/error frames; rejects missing ZDR, data-collection-deny, strict-parameter, attribution, or gateway-model routing. |
| Push, analytics, and Turnstile | Browser/service-worker/network contracts or official test adapters. |
Optional staging checks use dedicated test-mode projects and pre-provisioned identities. Production permits only read-only synthetic checks such as health, public availability, TLS, headers, and a non-mutating login render.
Reading failures and reports
QA output identifies the affected surface, route or domain, persona/profile, pass/fail/skip/flaky count, and—where relevant—accessibility impact, performance delta, or visual diff. Stable prefixes such as AUTH-*, ACL-*, ORG-*, ADM-*, API-*, DB-*, BILL-*, AI-*, A11Y-*, and PERF-* make failures searchable.
Retries collect evidence; they do not convert flaky behavior into a pass. Start with the focused reproduction command printed by the failing gate, then use pnpm run qa:local:status to confirm the local stack if the failure occurred during setup. Do not copy credentials, callback URLs, browser state, or provider payloads into an issue.
Adding QA for a change
- Update the page or API manifest whenever a route is added, removed, or changes its effective methods or access behavior.
- Put pure domain behavior in Vitest and database authorization/invariants in pgTAP.
- Add API contracts for validation, auth, CSRF, role, account isolation, idempotency, and documented failures.
- Add a browser journey for user-visible behavior; use accessible-name locators and assert persisted outcomes rather than implementation details.
- Give state-changing tests worker-owned Accounts and resources, with exact fail-closed cleanup.
- Run the smallest relevant focused profile while developing, then
pnpm run qa:local:gatebefore merge.
No test should use fixed sleeps, depend on another test's mutation, auto-approve a snapshot, weaken a rate limit, or target production. Expected skips and allowlists need a reason, owner, and expiry instead of becoming permanent gaps.