From 490e7347b044e7f44d0fc2843abc6ee916b526ac Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Wed, 1 Jul 2026 14:08:10 +0200 Subject: [PATCH] test(e2e): walking-skeleton Playwright happy path + verify-e2e lane (refs #68) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/e2e Playwright spec drives DigiD login (jan-burger/test123) → submit → confirmation against the compose-served portal. run-e2e-check.sh runs it inside the compose network (node container, browser installed at runtime) so the token issuer (keycloak:8080) matches the BFF authority (ADR-0010). Wired as verify-e2e (Makefile + verify chain + a verify-stack CI step). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/ci.yaml | 2 ++ .gitignore | 5 +++++ Makefile | 8 +++++++- infra/run-e2e-check.sh | 25 +++++++++++++++++++++++++ tests/e2e/package.json | 8 ++++++++ tests/e2e/playwright.config.ts | 16 ++++++++++++++++ tests/e2e/registration.spec.ts | 21 +++++++++++++++++++++ 7 files changed, 84 insertions(+), 1 deletion(-) create mode 100755 infra/run-e2e-check.sh create mode 100644 tests/e2e/package.json create mode 100644 tests/e2e/playwright.config.ts create mode 100644 tests/e2e/registration.spec.ts diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index cedfa6a..4e3a624 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -124,6 +124,8 @@ jobs: run: make verify-domain - name: BFF → Keycloak + domain + projection run: make verify-bff + - name: Self-service e2e (Playwright, login → submit → success) + run: make verify-e2e # Log dump must precede teardown (which removes the containers). - name: Dump container logs on failure if: failure() diff --git a/.gitignore b/.gitignore index c206ae3..30b841d 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,8 @@ vite.config.*.timestamp* vitest.config.*.timestamp* .angular + +# Playwright e2e (installed/generated in-container or on local runs) +tests/e2e/node_modules/ +tests/e2e/test-results/ +tests/e2e/playwright-report/ diff --git a/Makefile b/Makefile index 5e08b27..77be727 100644 --- a/Makefile +++ b/Makefile @@ -153,6 +153,11 @@ verify-domain: verify-bff: bash infra/run-bff-check.sh +## verify-e2e: walking-skeleton Playwright e2e (S-08d) against the up stack — DigiD login → +## submit → confirmation, driven inside the compose network. +verify-e2e: + bash infra/run-e2e-check.sh + ## verify: local mirror of the CI verify-stack job — full stack up once, all checks, ## tear down (always). For fast single-concern local iteration use `integration` ## (oz-only) or `verify-notifications` (oz+nrc) instead. @@ -165,7 +170,8 @@ verify: && bash infra/run-notification-check.sh \ && bash infra/run-projection-check.sh \ && bash infra/run-domain-check.sh \ - && bash infra/run-bff-check.sh || rc=$$?; \ + && bash infra/run-bff-check.sh \ + && bash infra/run-e2e-check.sh || rc=$$?; \ docker compose -f $(COMPOSE) down --volumes >/dev/null 2>&1; \ docker volume rm -f $(CFG_VOLS) >/dev/null 2>&1; \ exit $$rc' diff --git a/infra/run-e2e-check.sh b/infra/run-e2e-check.sh new file mode 100755 index 0000000..56a230b --- /dev/null +++ b/infra/run-e2e-check.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Walking-skeleton e2e (S-08d) against an ALREADY-RUNNING full stack: drive the self-service portal +# in a real browser through mock-DigiD login → submit → confirmation (login → BFF → domain). +# +# Runs Playwright INSIDE the compose network (a node container on `cg`), so the browser reaches +# Keycloak by service name (keycloak:8080) — the same authority the BFF validates against, so the +# token issuer matches (ADR-0010). The spec is copied into the container (docker cp), not mounted, +# so it leaves no root-owned files on the host. The caller owns stack bring-up + teardown. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +root="$(cd "$here/.." && pwd)" + +ss="$(docker ps -q --filter 'name=self-service' | head -1)" +[ -n "$ss" ] || { echo "ERROR: no running self-service container — bring the stack up first" >&2; exit 1; } +net="$(docker inspect -f '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' "$ss" | head -1)" +echo ">> running Playwright e2e on network $net against http://self-service" + +cid="$(docker create --network "$net" -w /e2e --ipc=host \ + -e SELF_SERVICE_URL=http://self-service \ + node:24 sh -c 'npm install --no-audit --no-fund && npx playwright install --with-deps chromium && npx playwright test')" +trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT +docker cp "$root/tests/e2e/." "$cid:/e2e" >/dev/null +docker start -a "$cid" diff --git a/tests/e2e/package.json b/tests/e2e/package.json new file mode 100644 index 0000000..1732cc1 --- /dev/null +++ b/tests/e2e/package.json @@ -0,0 +1,8 @@ +{ + "name": "e2e", + "private": true, + "description": "Playwright walking-skeleton e2e (S-08d) — run inside the compose network by infra/run-e2e-check.sh.", + "devDependencies": { + "@playwright/test": "1.61.1" + } +} diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts new file mode 100644 index 0000000..45a448f --- /dev/null +++ b/tests/e2e/playwright.config.ts @@ -0,0 +1,16 @@ +import { defineConfig, devices } from '@playwright/test'; + +// The e2e runs inside the compose network (infra/run-e2e-check.sh); baseURL defaults to the +// self-service service. Keep timeouts generous — the first navigation triggers the DigiD flow. +export default defineConfig({ + testDir: '.', + timeout: 90_000, + expect: { timeout: 15_000 }, + retries: 1, + reporter: [['list']], + use: { + baseURL: process.env.SELF_SERVICE_URL ?? 'http://self-service', + trace: 'on-first-retry', + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], +}); diff --git a/tests/e2e/registration.spec.ts b/tests/e2e/registration.spec.ts new file mode 100644 index 0000000..e446bdb --- /dev/null +++ b/tests/e2e/registration.spec.ts @@ -0,0 +1,21 @@ +import { expect, test } from '@playwright/test'; + +// Walking-skeleton happy path (S-08d): a zorgprofessional logs in via mock DigiD and submits a +// registration through the self-service portal → BFF → domain. The confirmation shows the reference. +test('DigiD login → submit → confirmation', async ({ page }) => { + // Visiting the guarded page redirects to the Keycloak (mock DigiD) login. + await page.goto('/'); + + // Keycloak's default login form (stable ids across themes). + await page.locator('#username').fill('jan-burger'); + await page.locator('#password').fill('test123'); + await page.locator('#kc-login').click(); + + // Back on the portal, authenticated. + await expect(page.getByRole('heading', { name: /Zelfservice/i })).toBeVisible(); + + await page.getByRole('button', { name: /indienen/i }).click(); + + // The BFF accepted it and the page shows the confirmation. + await expect(page.getByText(/ontvangen/i)).toBeVisible(); +});