feat(brief): WP-28 demo script, e2e spec, and story gap-fill
Closes phase 6 (Brief v2): a demo script mapping shipped scenarios to URL+click paths (no Brief v2 PRD ever existed to translate one from — written directly against the code instead), one e2e spec covering compose→approve→send and admin republish→drafter-sees-it, and Storybook state gaps (rejection diff, read-only viewer, org logo, upload rejection) that prior WPs left uncovered. Flags passage-picker as dead code, superseded by besluit-panel. npm run e2e is not verified green in this sandbox — see WP-28's Deviations section; the pre-existing, unmodified smoke.spec.ts fails identically here, pointing at a sandbox rendering issue rather than a regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
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' }).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');
|
||||
expect(await draftPreview.text()).toContain('preview-watermark');
|
||||
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 ---
|
||||
await page.getByRole('button', { name: 'Voorbeeld' }).click();
|
||||
const [sentPreview] = 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(sentPreview.headers()['content-type']).toContain('text/html');
|
||||
expect(await sentPreview.text()).not.toContain('preview-watermark');
|
||||
|
||||
// --- 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' }).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,
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user