Files
atomic-design-poc/playwright.config.ts
ehoandClaude Sonnet 5 42f7bd651d test(e2e): isolate runs and identities without a new backend endpoint (WP-74)
The three specs shared one mutable backend and said so in their own
comments ("Restart the backend between CI runs"). WP-70 recorded the fix as
a dev-only seed endpoint; it isn't needed. The DB path already routes
through IConfiguration, so playwright.config's webServer hands the backend a
throwaway SQLite file per invocation — the same trick TestWebApplicationFactory
already uses, with zero backend change. And StubIdentityProvider already
honoured X-Subject; the only gap was that nothing sent it. That matters
because the backend has no IsDevelopment() gate anywhere, so a seed endpoint
would have had to invent the codebase's first environment gate.

subjectInterceptor mirrors the existing roleInterceptor and is wired into the
same isDevMode()-only list. Interceptors alone were not enough: the raw XHR
upload and the hand-written letter-preview fetch bypass Angular's chain (as
CLAUDE.md documents), so both now stamp X-Subject explicitly — without that,
every uploaded document still landed under DemoOwner.

reuseExistingServer stays on: flipping it would break local runs for anyone
already serving the docker stack. Each run gets a unique DB filename and
global-setup sweeps only prior runs' leftovers — deleting a fixed path
mid-run risks SQLite silently recreating an empty, unmigrated file under
fullyParallel.

Verified: e2e passes twice back-to-back with no backend restart, and
X-Subject was observed on a real request, not merely wired.

brief-v2.spec.ts keeps the shared identity for now — see the KNOWN GAP note;
a backend staleness bug makes /brief/preview return a sent letter with the
draft watermark for any non-DemoOwner BSN. actors.ts reserves the actor for
whoever fixes it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:32:07 +02:00

88 lines
5.0 KiB
TypeScript

import { defineConfig } from '@playwright/test';
import * as os from 'node:os';
import * as path from 'node:path';
// Smoke-level e2e (WP-19): one happy path, one degraded path, against the REAL
// backend — proving the FE+BE seam, not replacing component/unit tests.
const baseURL = process.env['E2E_BASE_URL'] ?? 'http://localhost:4200';
// WP-74: give the backend a THROWAWAY SQLite file per `npm run e2e` invocation
// instead of its default `bigregister.db`, so two separate runs never see each
// other's leftover Applications/Documents/Briefs (`Program.cs:44-46` already reads
// `ConnectionStrings__AppDb` from `IConfiguration` — zero backend change; the same
// trick `TestWebApplicationFactory.cs` uses per xUnit test class). `Program.cs:93`
// self-migrates a fresh file on startup, so a brand-new path is immediately usable.
//
// UNIQUE filename per invocation (pid + timestamp), not a single fixed name: a fixed
// name that gets deleted WHILE the backend still has it open only stays safe if
// Microsoft.Data.Sqlite's connection pool never needs to open a fresh native handle
// by path after the delete — under `fullyParallel: true` (several workers hitting
// the backend concurrently) a pool miss is a real, if intermittent, risk, and it
// would silently recreate an empty, unmigrated file mid-run (every query after that
// would 500 on "no such table"). A unique path sidesteps the whole question: nothing
// ever deletes the file THIS run is actively using. The tradeoff is litter in the OS
// temp dir across many runs, since Playwright has no webServer teardown hook to
// delete it once the run ends — `globalSetup` (below) sweeps prior runs' files at
// the start of each new run instead. That's safe regardless of ordering: it only
// ever touches OTHER runs' paths, whose processes have already exited.
const dbPath = path.join(os.tmpdir(), `big-register-e2e-${process.pid}-${Date.now()}.db`);
// Shared with `e2e/global-setup.ts`, which runs in this SAME node process (it's
// loaded and invoked by the Playwright runner right after `webServer` comes up, not
// spawned separately) — so this assignment is visible there without a second import.
process.env['E2E_DB_PATH'] = dbPath;
export default defineConfig({
testDir: './e2e',
timeout: 30_000,
fullyParallel: true,
retries: process.env['CI'] ? 1 : 0,
reporter: process.env['CI'] ? 'github' : 'list',
// Sweeps `big-register-e2e-*.db*` files left behind by EARLIER invocations (never
// this run's own `dbPath` — see its comment above). Runs after `webServer` is
// already up (Playwright always starts webServer before globalSetup — there's no
// config knob to reverse that), which is exactly why it must never touch the
// current run's own file.
globalSetup: './e2e/global-setup.ts',
use: {
baseURL,
trace: 'on-first-retry',
},
projects: [{ name: 'chromium', use: { browserName: 'chromium' } }],
// Playwright owns both servers' lifecycle — start, wait-for-ready, tear down — in the
// one `npm run e2e` process, so `npm run e2e` is self-contained locally AND in CI.
// Do NOT background them as separate CI steps: a process started with `&` in one
// Actions `run:` step is killed when that step's shell exits, so a later `wait-on`
// step hangs forever on servers that are already gone (the 2-hour e2e hang).
// `reuseExistingServer` locally lets you run against an already-running app (incl. the
// docker stack on 4200/5000); CI always starts fresh. Backend gets a longer timeout —
// `dotnet run` cold-restores+builds before it listens.
webServer: [
{
command: 'dotnet run --project backend/src/BigRegister.Api --urls http://localhost:5000',
url: 'http://localhost:5000/swagger',
// WP-74 caveat, read before "fixing" this to `false`: this stays `!CI` (reuse
// locally) on purpose, matching the FE server below and the docker-stack note
// above it — turning reuse off would make `npm run e2e` hard-fail with
// "port already in use" for anyone who already has the docker stack (or a
// plain `dotnet run`) up on :5000, a real local-workflow regression for a POC
// convenience feature. The tradeoff this buys: `env` below (the throwaway DB)
// ONLY applies when Playwright itself spawns the process — reusing an
// already-running backend silently falls back to THAT process's own DB
// (typically the shared dev `bigregister.db`), so the isolation this WP adds
// is real in CI (`reuseExistingServer` is always `false` there) and in the
// common local case of "nothing was already running on :5000", but not if you
// deliberately point e2e at an already-running shared backend — that was
// already shared state before this WP and still is.
reuseExistingServer: !process.env['CI'],
timeout: 180_000,
env: { ConnectionStrings__AppDb: `Data Source=${dbPath}` },
},
{
command: 'npm start',
url: baseURL,
reuseExistingServer: !process.env['CI'],
timeout: 120_000,
},
],
});