Skip to content

Deterministic Route Contract E2E Design

Goal

Gate pull requests with one Chromium-only production test that proves dynamic pages preserve exact HTTP, authentication, method, and upstream failure contracts without real credentials or external services.

Architecture

The gate builds Next.js once with both public service URLs pointing at a local loopback adapter. Before Playwright starts, a preparation script copies public/ and .next/static/ into Next's standalone output. A dedicated Playwright config then starts that adapter and the generated .next/standalone/frontend/server.js, selects only tests/route-contract-e2e, uses one worker with no retries, and blocks service workers. This keeps the test independent of the real-auth setup and prevents the application service worker from manufacturing a false-positive 503.

The adapter implements only the Supabase auth/REST and backend endpoints used by the selected route matrix. Scenario IDs and synthetic session cookies select the response deterministically. A control endpoint resets and returns a sanitized request log, so every status attributed to an upstream response must also prove the exact upstream method/path and call count. The adapter binds only to 127.0.0.1 and never reads repository secrets.

Contract Matrix

Area Request Expected wire result Additional proof
Dynamic owner pages authenticated known chat, collection, document, schema, extraction 200 route-specific response content and exactly one matching adapter read
Dynamic missing pages authenticated chat, collection, document, schema, extraction misses 404 route-specific adapter read, not a generic Next miss
Upstream preservation authenticated extraction scenarios returning 404, 422, 429, 503 same status exactly one matching adapter call
Unknown route authenticated GET /__route-contract-missing 404 zero domain adapter reads
Unknown method authenticated POST and DELETE to extraction detail 405 and exact Allow zero domain adapter reads
Anonymous page protected dynamic page without a cookie 307 to /auth/login with exact next no domain adapter read
Anonymous BFF extraction BFF without a cookie 401 JSON; HEAD has an empty body no domain adapter read
Invalid session protected page/BFF with the invalid synthetic session 307 / 401 auth adapter called, no domain read
Auth outage protected page/BFF with the outage synthetic session 503 auth adapter called, zero domain reads

Playwright request context uses maxRedirects: 0 whenever the wire redirect or method response is under test. Browser navigation is retained for dynamic page responses and asserts both the main-resource status and final URL. Expected 404 and 503 cases cannot pass merely because Next has no handler or a network is down: adapter logs and response content are part of the assertion.

Files

  • frontend/tests/route-contract-e2e/stub-services.mjs: loopback Supabase/backend adapter, scenario fixtures, readiness/control endpoints, sanitized request log.
  • frontend/tests/route-contract-e2e/route-status.spec.ts: exact browser and HTTP contract matrix.
  • frontend/playwright.route-contract.config.ts: isolated single-project lifecycle and safety settings.
  • frontend/scripts/prepare-route-contract-standalone.mjs: stages static and public assets beside the generated standalone server.
  • frontend/tests/unit/test-harness/route-contract-harness.test.ts: locks the standalone and lint lifecycle in a focused Jest contract.
  • frontend/package.json: explicit preparation, standalone start, lint, and test commands for the route-contract project.
  • .github/workflows/ci.yml: a bounded PR job that installs, builds with loopback URLs, runs the project, and uploads diagnostics on failure.

CI Lifecycle

The job runs in the repository-pinned Playwright image, installs with npm ci, builds with fixed loopback public URLs, stages the standalone runtime through the npm pre-script, and then executes the dedicated test script. Playwright owns both web servers and terminates them on completion. The checked-in harness sources are part of npm run validate. The job has a hard timeout and uploads the Playwright report, test results, and sanitized adapter/server logs on failure. The existing UI smoke job and manual full browser matrix remain unchanged.

Non-goals

  • No real Supabase user or TEST_USER_* secret.
  • No multi-browser coverage.
  • No browser request interception for middleware or server component fetches.
  • No replacement of the deeper production Jest contracts.