test(e2e): shared Actors/SeedRefs/loginAs, kill duplicated magic strings (WP-70)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
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):
|
// 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
|
// 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
|
// the org-template draft it edits (step 8) and never asserts an absolute version
|
||||||
// number — only that it increased by exactly one.
|
// number — only that it increased by exactly one.
|
||||||
test('drafter composes → approver sends; admin republishes appearance', async ({ page }) => {
|
test('drafter composes → approver sends; admin republishes appearance', async ({ page }) => {
|
||||||
await page.goto('/login');
|
await loginAs(page, Actors.zorgverlener);
|
||||||
await page.getByLabel('BSN').fill('123456782');
|
|
||||||
await page.getByLabel('Wachtwoord').fill('demo');
|
|
||||||
await page.getByRole('button', { name: 'Inloggen met DigiD' }).click();
|
|
||||||
await expect(page).toHaveURL(/\/dashboard$/);
|
await expect(page).toHaveURL(/\/dashboard$/);
|
||||||
|
|
||||||
// --- Compose (drafter) ---
|
// --- Compose (drafter) ---
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
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 dev-only `?scenario=error` toggle forces the scenario.interceptor to fail
|
||||||
// the request WITHOUT ever reaching the real HTTP transport (it substitutes a
|
// 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
|
// genuinely re-runs and genuinely fails the same way — that's what's asserted
|
||||||
// here: a real reload cycle, not a no-op button.
|
// here: a real reload cycle, not a no-op button.
|
||||||
test('dashboard error state renders, retry re-fetches (and fails again)', async ({ page }) => {
|
test('dashboard error state renders, retry re-fetches (and fails again)', async ({ page }) => {
|
||||||
await page.goto('/login');
|
await loginAs(page, Actors.zorgverlener);
|
||||||
await page.getByLabel('BSN').fill('123456782');
|
|
||||||
await page.getByRole('button', { name: 'Inloggen met DigiD' }).click();
|
|
||||||
await expect(page).toHaveURL(/\/dashboard$/);
|
await expect(page).toHaveURL(/\/dashboard$/);
|
||||||
|
|
||||||
await page.goto('/dashboard?scenario=error');
|
await page.goto('/dashboard?scenario=error');
|
||||||
|
|||||||
+6
-6
@@ -1,4 +1,5 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
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
|
// 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
|
// 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
|
// application on the dashboard, which this test doesn't assert against, but a
|
||||||
// stricter future test might.
|
// stricter future test might.
|
||||||
test('login → dashboard → registratie wizard → submitted', async ({ page }) => {
|
test('login → dashboard → registratie wizard → submitted', async ({ page }) => {
|
||||||
await page.goto('/login');
|
await loginAs(page, Actors.zorgverlener);
|
||||||
await page.getByLabel('BSN').fill('123456782');
|
|
||||||
await page.getByLabel('Wachtwoord').fill('demo');
|
|
||||||
await page.getByRole('button', { name: 'Inloggen met DigiD' }).click();
|
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/dashboard$/);
|
await expect(page).toHaveURL(/\/dashboard$/);
|
||||||
await expect(page.getByRole('heading', { level: 1, name: 'Mijn overzicht' })).toBeVisible();
|
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
|
// Step 2 — beroep: the first DUO diploma (Geneeskunde, non-English) carries zero
|
||||||
// policy questions, so the only required document is identiteit.
|
// policy questions, so the only required document is identiteit.
|
||||||
await expect(page.locator('#diploma-d1')).toBeVisible({ timeout: 10_000 });
|
await expect(page.locator(`#diploma-${SeedRefs.diplomaZonderPolicyVragen}`)).toBeVisible({
|
||||||
await page.locator('label[for="diploma-d1"]').click();
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
await page.locator(`label[for="diploma-${SeedRefs.diplomaZonderPolicyVragen}"]`).click();
|
||||||
await expect(page.getByText('Beroep (afgeleid uit diploma)')).toBeVisible();
|
await expect(page.getByText('Beroep (afgeleid uit diploma)')).toBeVisible();
|
||||||
|
|
||||||
await page.locator('#identiteit-file').setInputFiles({
|
await page.locator('#identiteit-file').setInputFiles({
|
||||||
|
|||||||
@@ -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<string, Actor>;
|
||||||
|
|
||||||
|
/** The shared DigiD-style mock login sequence every e2e spec starts from. */
|
||||||
|
export async function loginAs(page: Page, actor: Actor): Promise<void> {
|
||||||
|
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;
|
||||||
Reference in New Issue
Block a user