# WP-19 — Playwright e2e smoke

Status: todo
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 (`<app-async>`'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

- [ ] `npm run e2e` passes locally against `docker compose up` or `npm start` +
      `dotnet run` run manually.
- [ ] CI job `e2e` is green and runs on every PR alongside the existing jobs.
- [ ] The happy-path spec exercises a real wizard submit against the real backend
      (not mocked) and asserts on the resulting UI state.
- [ ] The error-path spec exercises `<app-async>`'s error slot + retry via the real
      `?scenario=error` toggle, not a mocked HTTP response.

## Verification

`npm run e2e` locally; then push a branch and confirm the new `e2e` CI job appears
and passes. Cross-check that a deliberately broken flow (e.g. temporarily rename a
required form field) fails the e2e spec, proving it isn't a no-op.

## Out of scope

Full e2e coverage of every wizard/flow; visual regression testing; cross-browser
matrix (chromium only, matching the existing a11y job); load/performance testing.

## Risks

The `?scenario=` interceptor is dev-only (`isDevMode()` gated, per
`app.config.ts`) — confirm the e2e target build runs in dev mode (it does via `ng
serve`/`npm start`; a production `ng build` would need the toggle unavailable,
which is correct and should be asserted, not worked around). Backend
in-memory stores mean e2e runs against a fresh seed each restart — don't assert on
data that a previous test run could have mutated; restart the backend per CI run.
