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>
84 lines
4.3 KiB
TypeScript
84 lines
4.3 KiB
TypeScript
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·8−1·2 = 154, 154 mod 11 = 0)
|
||
* 111222333 ✓ (9·1+8·1+7·1+6·2+5·2+4·2+3·3+2·3−1·3 = 66, 66 mod 11 = 0)
|
||
* 111111110 ✓ (9+8+7+6+5+4+3+2−0 = 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;
|