From a7bf228ca8f128c0f68a498a4046579bfd1a4d12 Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Tue, 18 Aug 2026 15:31:12 +0200 Subject: [PATCH] test(e2e): shared Actors/SeedRefs/loginAs, kill duplicated magic strings (WP-70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The demo BSN, password, and DigiD login sequence were copy-pasted verbatim into all three specs; the diploma id #diploma-d1 was coupled to SeedData.cs's ordering by comment only, with no compile-time check if the seed shape changed. e2e/support/actors.ts names both: Actors.zorgverlener + loginAs() for the login sequence, SeedRefs.diplomaZonderPolicyVragen for the seed coupling (with the "why d1" reasoning attached to the name, not scattered across specs). Zero assertions changed — pure extract-and-rename of test setup. e2e test-isolation (the shared mutable backend) is a documented follow-up, not fixed here — see ADR-0006. Co-Authored-By: Claude Sonnet 5 --- e2e/brief-v2.spec.ts | 6 ++---- e2e/error-state.spec.ts | 5 ++--- e2e/smoke.spec.ts | 12 +++++------ e2e/support/actors.ts | 48 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 e2e/support/actors.ts diff --git a/e2e/brief-v2.spec.ts b/e2e/brief-v2.spec.ts index 854d9cb..74dab99 100644 --- a/e2e/brief-v2.spec.ts +++ b/e2e/brief-v2.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import { Actors, loginAs } from './support/actors'; // One flow through both Brief v2 axes on the real FE+backend (WP-19 conventions): // content (drafter composes via the besluit panel → approver approves → sends) and @@ -11,10 +12,7 @@ import { expect, test } from '@playwright/test'; // the org-template draft it edits (step 8) and never asserts an absolute version // number — only that it increased by exactly one. test('drafter composes → approver sends; admin republishes appearance', async ({ page }) => { - await page.goto('/login'); - await page.getByLabel('BSN').fill('123456782'); - await page.getByLabel('Wachtwoord').fill('demo'); - await page.getByRole('button', { name: 'Inloggen met DigiD' }).click(); + await loginAs(page, Actors.zorgverlener); await expect(page).toHaveURL(/\/dashboard$/); // --- Compose (drafter) --- diff --git a/e2e/error-state.spec.ts b/e2e/error-state.spec.ts index 9797d0a..f75a502 100644 --- a/e2e/error-state.spec.ts +++ b/e2e/error-state.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import { Actors, loginAs } from './support/actors'; // The dev-only `?scenario=error` toggle forces the scenario.interceptor to fail // the request WITHOUT ever reaching the real HTTP transport (it substitutes a @@ -11,9 +12,7 @@ import { expect, test } from '@playwright/test'; // genuinely re-runs and genuinely fails the same way — that's what's asserted // here: a real reload cycle, not a no-op button. test('dashboard error state renders, retry re-fetches (and fails again)', async ({ page }) => { - await page.goto('/login'); - await page.getByLabel('BSN').fill('123456782'); - await page.getByRole('button', { name: 'Inloggen met DigiD' }).click(); + await loginAs(page, Actors.zorgverlener); await expect(page).toHaveURL(/\/dashboard$/); await page.goto('/dashboard?scenario=error'); diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 5b10f45..9c4343d 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import { Actors, loginAs, SeedRefs } from './support/actors'; // One happy-path flow through the real FE+backend: log in, land on the real // dashboard, run the registratie wizard's minimum required path (a DUO diploma @@ -11,10 +12,7 @@ import { expect, test } from '@playwright/test'; // application on the dashboard, which this test doesn't assert against, but a // stricter future test might. test('login → dashboard → registratie wizard → submitted', async ({ page }) => { - await page.goto('/login'); - await page.getByLabel('BSN').fill('123456782'); - await page.getByLabel('Wachtwoord').fill('demo'); - await page.getByRole('button', { name: 'Inloggen met DigiD' }).click(); + await loginAs(page, Actors.zorgverlener); await expect(page).toHaveURL(/\/dashboard$/); await expect(page.getByRole('heading', { level: 1, name: 'Mijn overzicht' })).toBeVisible(); @@ -35,8 +33,10 @@ test('login → dashboard → registratie wizard → submitted', async ({ page } // Step 2 — beroep: the first DUO diploma (Geneeskunde, non-English) carries zero // policy questions, so the only required document is identiteit. - await expect(page.locator('#diploma-d1')).toBeVisible({ timeout: 10_000 }); - await page.locator('label[for="diploma-d1"]').click(); + await expect(page.locator(`#diploma-${SeedRefs.diplomaZonderPolicyVragen}`)).toBeVisible({ + timeout: 10_000, + }); + await page.locator(`label[for="diploma-${SeedRefs.diplomaZonderPolicyVragen}"]`).click(); await expect(page.getByText('Beroep (afgeleid uit diploma)')).toBeVisible(); await page.locator('#identiteit-file').setInputFiles({ diff --git a/e2e/support/actors.ts b/e2e/support/actors.ts new file mode 100644 index 0000000..8b9886d --- /dev/null +++ b/e2e/support/actors.ts @@ -0,0 +1,48 @@ +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 seeded by the backend. Today there is exactly one seeded + * citizen (`backend/src/BigRegister.Api/Data/SeedData.cs`'s `Person`/`Registration`, + * whose BSN also matches `DocumentStore.DemoOwner`) — named for the ROLE it plays + * in a spec, not its BSN, so a spec reads as "log in as the zorgverlener", not + * "log in as 123456782". + */ +export const Actors = { + zorgverlener: { bsn: '123456782', wachtwoord: 'demo' }, +} as const satisfies Record; + +/** The shared DigiD-style mock login sequence every e2e spec starts from. */ +export async function loginAs(page: Page, actor: Actor): Promise { + await page.goto('/login'); + 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;