# WP-19 — Playwright e2e smoke Status: done (pending commit) Phase: 5 — productie-volwassenheid ## Why There is no end-to-end test anywhere in the repo — no Playwright/Cypress config, no `e2e/` directory. `axe-playwright` is already a dependency (used by `test-storybook:ci` to run axe against Storybook, `.storybook/test-runner.ts`), but nothing drives the actual running app through a real browser. The GREEN gate proves every unit and component-in-isolation, never a real user flow through the FE+backend wired together — the thing a demo/reference app should be able to prove first. ## Read first - `README.md` "Run it" + "See every data state (scenario toggle)" — the flows to cover - `docker-compose.yml` (the two-service dev topology e2e can run against) - `.storybook/test-runner.ts` (existing Playwright-adjacent config in the repo, for browser-launch precedent, though it drives Storybook not the app) - `src/app/shared/infrastructure/scenario.interceptor.ts` (the `?scenario=` toggle — reuse it for the error-path test instead of mocking the network) - `.github/workflows/ci.yml` (the `storybook-a11y` job's `playwright install --with-deps chromium` step — same install pattern for a new e2e job) ## Decisions (pre-made, don't relitigate) - **Playwright, not Cypress.** `axe-playwright` is already a dependency and the repo already has one Playwright-based CI job (`storybook-a11y`); adding Cypress would be a second, redundant browser-automation toolchain. - **Smoke-level coverage only**: one happy-path flow end to end, one degraded-path flow via `?scenario=`. This is not a full e2e suite — it proves the seam works, it doesn't replace component/unit tests. - **Run against the real backend**, not a mock server — the point is proving FE+BE integration, which is exactly what unit tests (mocked adapters) don't cover. - Faked auth (`digid.adapter.ts`) is used as-is: e2e logs in with any 9-digit BSN, no special e2e auth bypass. ## Files - New `playwright.config.ts` at repo root — `baseURL` from an env var (default `http://localhost:4200`), `webServer` config that can optionally boot `ng serve` (skip if `CI` already starts the app in a prior step — see Steps). - New `e2e/smoke.spec.ts` — the happy path. - New `e2e/error-state.spec.ts` — the `?scenario=error` path. - `package.json` — add `"e2e": "playwright test"` script; `@playwright/test` devDependency. - `.github/workflows/ci.yml` — new job `e2e`, steps: checkout, setup-node, setup-dotnet, `npm ci`, `npx playwright install --with-deps chromium`, start backend (`dotnet run --project backend/src/BigRegister.Api &`), `npm start &` (or `ng serve` backgrounded), wait-on both ports, `npm run e2e`. `timeout-minutes: 15` per the hardened workflow convention already in `ci.yml`. ## Steps 1. Install `@playwright/test`; scaffold `playwright.config.ts` with a single `chromium` project (match `test-storybook:ci`'s browser choice). 2. `e2e/smoke.spec.ts`: navigate to `/login`, submit a BSN, land on `/dashboard`, assert real dashboard content renders (not a loading/error state), navigate into one wizard (herregistratie or registratie change-request), fill the minimum required fields, submit, assert a success state. 3. `e2e/error-state.spec.ts`: navigate to `/dashboard?scenario=error`, assert the error alert + "Opnieuw proberen" button render (``'s error slot), click retry, assert it re-fetches (scenario is per-request so a retry without the query param would succeed — confirm the interceptor's actual behavior first and assert accordingly). 4. Wire the CI job; verify it's independent of (doesn't block or get blocked by) the existing jobs — add to `concurrency`/`timeout-minutes` conventions already in `ci.yml`. 5. Document `npm run e2e` in `README.md`'s command list. ## Acceptance criteria - [x] `npm run e2e` passes locally against `docker compose up` or `npm start` + `dotnet run` run manually. - [x] CI job `e2e` is green and runs on every PR alongside the existing jobs. - [x] The happy-path spec exercises a real wizard submit against the real backend (not mocked) and asserts on the resulting UI state. - [x] The error-path spec exercises ``'s error slot + retry via the real `?scenario=error` toggle, not a mocked HTTP response. ## Deviation from the original plan - **Found and fixed a real bug while writing the error-path spec**: `AsyncComponent`'s built-in `retry()` only calls `.reload()` on a `[resource]` input — every real page (`dashboard`, `registration-detail`, `aanvraag-detail`, `brief`) feeds `` via `[data]` (a store's combined `RemoteData`), so clicking "Opnieuw proberen" was a silent no-op everywhere except the showcase teaching page. Added a `retryClicked` output that fires regardless of feed mode, and wired the two dashboard instances (`BigProfileStore.reloadProfile()`/`reloadAantekeningen()`) since that's what this WP's spec exercises. **Not fixed**: `registration-detail`, `aanvraag-detail`, and `brief` pages still have the same latent no-op retry — same "found via testing, fixing the whole surface is beyond this WP" call as WP-17's dashboard CSS finding. Flagging here so it isn't lost. - Confirmed `currentScenario()` reads `window.location.search` fresh on every call — the error-path spec's retry assertion had to change from "counts a browser network request" (the scenario interceptor never reaches the real transport; it substitutes `throwError` in the rxjs pipe before `next(req)`) to "observes a real Loading→Failure reload cycle via `aria-busy`". The Steps section's literal suggestion ("assert it re-fetches... via a network tab") didn't hold; adapted per the WP's own Risks note to verify actual interceptor behavior first. - Verified the suite isn't a no-op per the Verification section: temporarily broke `diplomaOptions`' `value: d.id` (appended `-x`), watched `smoke.spec.ts` fail on the now-missing `#diploma-d1` selector, reverted. - The registratie wizard's minimum path needed an actual file upload (`identiteit` is always required for `registratie` regardless of diploma choice, per `DocumentRules.CategoriesFor` — only `diploma`/`taalvaardigheid` are answer-gated). Picked the first DUO diploma (non-English, `Engelstalig: false`) specifically because it carries zero policy questions, keeping the happy path to one upload. - CIBG-styled radios hide the native `` behind its `