Files
atomic-design-poc/e2e/brief-v2.spec.ts
T
ehoandClaude Sonnet 5 c4dd846fbb
CI / frontend (push) Successful in 2m12s
CI / backend (push) Successful in 1m37s
CI / e2e (push) Successful in 3m33s
CI / storybook-a11y (push) Successful in 8m23s
CI / semgrep (push) Successful in 1m4s
CI / api-client-drift (push) Successful in 1m52s
fix(ci): unbreak backend format, storybook-a11y, and e2e jobs
- backend: dotnet format the WP-51 migration (2-space indent, no BOM)
  to match .editorconfig — dotnet format --verify-no-changes was failing.
- storybook: stub FeatureFlagStore (WP-47) in shell/site-header stories
  alongside AccessStore, fixing NG0201 no-provider errors; bump the
  storybook-a11y container's memory cap 4g→6g (build-storybook +
  compodoc measured ~5.8GB peak RSS, leaving too little headroom).
- backend: fix a startup-breaking bug in the new (WP-52) POST
  /zgw/notificaties handler — it took ZgwOptions as a minimal-API
  parameter, which isn't registered in DI, so ASP.NET's endpoint-table
  build threw on every request once the route was registered (incl.
  /swagger, which is why Playwright's webServer health check timed
  out). Close over the existing `zgw` local instead.
- e2e: brief-v2.spec.ts's "Voorbeeld" button locator was ambiguous
  once a second "Voorbeeld met testwaarden" button existed (Playwright
  name matching is substring-based) — added `exact: true`. Also fixed
  the sent-letter preview flow to match app-letter-composer's actual
  behavior (single click → fetch, no in-page dialog, unlike
  app-behandel-scherm's), and fixed a watermark assertion that checked
  for the always-present `.preview-watermark` CSS class name instead
  of the conditionally-rendered "VOORBEELD" marker text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 07:18:07 +02:00

105 lines
5.5 KiB
TypeScript

import { expect, test } from '@playwright/test';
// 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
// appearance (admin edits + publishes the org template, drafter's canvas reflects it).
// Preview assertions are content-type/body-level (text/html + watermark marker), not
// pixel, per WP-28's decision.
//
// The backend persists to SQLite (WP-22) and is shared across runs: `/brief/reset`
// covers the letter, but org templates have no reset endpoint, so this test restores
// 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 expect(page).toHaveURL(/\/dashboard$/);
// --- Compose (drafter) ---
await page.goto('/brief?role=drafter');
await page.getByRole('button', { name: 'Opnieuw beginnen (demo)' }).click();
await expect(page.getByRole('heading', { name: 'Aanvraag herregistratie' })).toBeVisible();
const submitButton = page.getByRole('button', { name: 'Indienen ter beoordeling' });
await expect(submitButton).toBeDisabled();
await page.locator('label[for="besluit-positief"]').click();
await expect(page.getByText(/standaardtekst\(en\) toegevoegd/)).toBeVisible();
await expect(submitButton).toBeEnabled();
// Wait for the debounced draft save before navigating away.
await expect(page.getByText('Concept opgeslagen')).toBeVisible({ timeout: 10_000 });
// --- Preview: draft is watermarked ---
await page.getByRole('button', { name: 'Voorbeeld', exact: true }).click();
const [draftPreview] = await Promise.all([
page.waitForResponse((r) => r.url().includes('/api/v1/brief/preview'), { timeout: 10_000 }),
page.getByRole('button', { name: 'Openen als document (PDF)' }).click(),
]);
expect(draftPreview.headers()['content-type']).toContain('text/html');
// The `.preview-watermark` CSS rule ships in every preview (draft or sent) — only the
// "VOORBEELD" marker div (LetterHtml.Render's `watermark` param) is actually conditional.
expect(await draftPreview.text()).toContain('>VOORBEELD<');
await page.getByRole('button', { name: 'Sluiten' }).click();
// --- Submit → approve → send (role change = full navigation, per WP-33 stickiness) ---
await submitButton.click();
await expect(page.getByText('De brief wacht op beoordeling door een collega.')).toBeVisible();
await page.goto('/brief?role=approver');
await page.getByRole('button', { name: 'Goedkeuren' }).click({ timeout: 10_000 });
await page.getByRole('button', { name: 'Versturen' }).click({ timeout: 10_000 });
await expect(page.getByText('De brief is verzonden.')).toBeVisible();
// --- Preview: sent letter serves its frozen, unwatermarked archive ---
// Sent = !canEdit → app-letter-composer, whose "Voorbeeld" click goes straight to
// store.previewLetter() (fetch + window.open), unlike the drafter's behandel-scherm
// dialog above where "Voorbeeld" only opens a local modal and a second click inside it
// triggers the fetch.
const [sentPreview] = await Promise.all([
page.waitForResponse((r) => r.url().includes('/api/v1/brief/preview'), { timeout: 10_000 }),
page.getByRole('button', { name: 'Voorbeeld', exact: true }).click(),
]);
expect(sentPreview.headers()['content-type']).toContain('text/html');
expect(await sentPreview.text()).not.toContain('>VOORBEELD<');
// --- Admin republishes the appearance ---
await page.goto('/brief/huisstijl?role=admin');
const orgNameInput = page.getByLabel('Organisatienaam');
const before = await page.getByText(/Gepubliceerde versie:/).textContent();
const beforeVersion = Number(before?.match(/\d+/)?.[0]);
const unique = `BIG-register E2E ${Date.now()}`;
await orgNameInput.fill(unique);
await expect(page.getByText('Concept opgeslagen')).toBeVisible({ timeout: 10_000 });
await page.getByRole('button', { name: 'Publiceren' }).click();
await expect(page.getByText(/Dit raakt \d+ nog niet verzonden brieven/)).toBeVisible();
await page.getByRole('button', { name: 'Bevestigen' }).click();
await expect(page.getByText(`Gepubliceerde versie: ${beforeVersion + 1}`)).toBeVisible({
timeout: 10_000,
});
// --- Drafter's canvas reflects the new appearance on a fresh letter ---
await page.goto('/brief?role=drafter');
await page.getByRole('button', { name: 'Opnieuw beginnen (demo)' }).click();
await page.getByRole('button', { name: 'Voorbeeld', exact: true }).click();
await expect(page.locator('dialog')).toContainText(unique);
await page.getByRole('button', { name: 'Sluiten' }).click();
// --- Restore: put the org template's appearance back the way this test found it ---
await page.goto('/brief/huisstijl?role=admin');
await page
.locator('.history-row', { hasText: new RegExp(`Versie ${beforeVersion} ·`) })
.getByRole('button', { name: 'Terugzetten in concept' })
.click();
await expect(orgNameInput).not.toHaveValue(unique);
await page.getByRole('button', { name: 'Publiceren' }).click();
await page.getByRole('button', { name: 'Bevestigen' }).click();
await expect(page.getByText(`Gepubliceerde versie: ${beforeVersion + 2}`)).toBeVisible({
timeout: 10_000,
});
});