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>
88 lines
5.0 KiB
TypeScript
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,
|
|
},
|
|
],
|
|
});
|