Files
atomic-design-poc/e2e/support/actors.ts
T
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

84 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Page } from '@playwright/test';
/**
* A demo identity the fake DigiD login accepts. Auth is faked (CLAUDE.md) — the
* form only ever emits `bsn` on submit, so `wachtwoord` is never actually checked;
* it's filled in anyway because the field is marked required in the UI.
*/
export interface Actor {
readonly bsn: string;
readonly wachtwoord: string;
}
/**
* The demo identities e2e specs log in as. The BRP/registration data every one of
* them sees on the dashboard comes from static `SeedData` and is identity-independent
* (only `DocumentStore.DemoOwner`'s owner-keyed Applications/Documents/Briefs differ
* per BSN — see `subject.interceptor.ts`), so any elfproef-valid BSN works here; these
* are just distinct, not otherwise special.
*
* Named for the ROLE the identity plays in a spec, not its BSN, so a spec reads as
* "log in as the zorgverlener", not "log in as 123456782". **A spec that mutates
* owner-keyed state (creates a Concept, composes a brief, …) should use its own
* BSN** (WP-74) — `subject.interceptor.ts` stamps it as `X-Subject`, so two specs
* sharing a BSN would collide on the same backend rows across the same run and
* across reruns. A read-only spec (nothing created/submitted) can keep using
* `zorgverlener`.
*
* `briefOpsteller` is the one exception, currently unused: `brief-v2.spec.ts` stays
* on `zorgverlener` despite mutating state, because giving it its own BSN tripped a
* real backend bug (a stale/watermarked `GET /brief/preview` response for the SENT
* letter under a non-`DemoOwner` owner — see that spec's header comment and
* `letter-preview.adapter.ts`'s "KNOWN GAP" note). Kept defined, not deleted, so
* whoever fixes that backend issue has the identity ready to switch the spec onto.
*
* Every BSN below passed the elfproef (`libs/shared/src/kernel/bsn.ts`'s checksum) —
* required, or `DigidAdapter.authenticate` rejects it and login never completes:
* 123456782 ✓ (9·1+8·2+7·3+6·4+5·5+4·6+3·7+2·81·2 = 154, 154 mod 11 = 0)
* 111222333 ✓ (9·1+8·1+7·1+6·2+5·2+4·2+3·3+2·31·3 = 66, 66 mod 11 = 0)
* 111111110 ✓ (9+8+7+6+5+4+3+20 = 44, 44 mod 11 = 0)
*/
export const Actors = {
/** The one seeded citizen (`SeedData.cs`'s `Person`/`Registration`) — read-only
specs, and (for now — see above) `brief-v2.spec.ts` too. */
zorgverlener: { bsn: '123456782', wachtwoord: 'demo' },
/** `smoke.spec.ts`'s own identity — it creates+submits a registratie-aanvraag. */
registratieAanvrager: { bsn: '111222333', wachtwoord: 'demo' },
/** Reserved for `brief-v2.spec.ts` once the backend bug above is fixed — not
currently used by any spec. */
briefOpsteller: { bsn: '111111110', wachtwoord: 'demo' },
} as const satisfies Record<string, Actor>;
/**
* The shared DigiD-style mock login sequence every e2e spec starts from. Navigating
* to `/login?subject=<bsn>` (rather than plain `/login`) primes `subject.interceptor.ts`'s
* sticky sessionStorage the same way `?role=` primes `roleInterceptor` (WP-33) — the
* BSN typed into the form and the one the interceptor stamps as `X-Subject` are the
* same value by construction, they just can't share a single read (see
* `subject.interceptor.ts`'s doc comment for why not).
*/
export async function loginAs(page: Page, actor: Actor): Promise<void> {
await page.goto(`/login?subject=${actor.bsn}`);
await page.getByLabel('BSN').fill(actor.bsn);
await page.getByLabel('Wachtwoord').fill(actor.wachtwoord);
await page.getByRole('button', { name: 'Inloggen met DigiD' }).click();
}
/**
* Seeded fixtures specs depend on by id, named by
* `backend/src/BigRegister.Api/Data/SeedData.cs`'s seed order — reseeding that
* file in a different order silently breaks these with no compile error, so give
* the raw id a name instead of leaving it a bare literal in each spec.
*/
export const SeedRefs = {
/**
* The first DUO diploma (id "d1": Geneeskunde, Universiteit Leiden, non-English).
* Chosen deliberately, not arbitrarily: it's the one seeded diploma that carries
* zero policy questions, so it drives the wizard down its minimum required path
* — the only required upload is identiteit.
*/
diplomaZonderPolicyVragen: 'd1',
/** The seeded registration's BIG-nummer (`SeedData.Registration`). */
bigNummer: '19012345601',
} as const;