From 5f77dae587b2629ff8df67abb044d2865e883047 Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Mon, 20 Jul 2026 11:22:07 +0200 Subject: [PATCH] test(e2e): provide documents on the live self-service tab after the zaak opens (refs #102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The process only parks at WachtOpDocumenten once the OpenZaak worker has opened the zaak, so providing documents immediately after submit raced the wait and no-op'd. Check the openbaar INGEDIEND row on a second page (proving the zaak is open, hence the process is at the wait) while the self-service tab keeps its submitted state, then provide documents there — unblocking the werkbak. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/e2e/registration.spec.ts | 81 +++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/tests/e2e/registration.spec.ts b/tests/e2e/registration.spec.ts index 3b91bde..b508352 100644 --- a/tests/e2e/registration.spec.ts +++ b/tests/e2e/registration.spec.ts @@ -1,11 +1,15 @@ import { expect, test } from '@playwright/test'; -// Walking-skeleton happy path (S-08d + S-09 + S-09b + S-12): a zorgprofessional logs in via mock -// DigiD and submits through the self-service portal → BFF → domain; the entry appears in the openbaar -// register as INGEDIEND; a behandelaar then logs in to the behandel portal, finds the registration in -// the werkbak, and approves it (goedkeuren); the decision completes the Flowable Beoordelen task and -// flows via the ACL → NRC → event-subscriber → projection, and the openbaar register shows INGESCHREVEN. -test('DigiD submit → public INGEDIEND → behandelaar goedkeurt → public INGESCHREVEN', async ({ page }) => { +// Walking-skeleton happy path (S-08d + S-09 + S-09b + S-12 + S-10a): a zorgprofessional logs in via +// mock DigiD and submits through the self-service portal → BFF → domain; the entry appears in the +// openbaar register as INGEDIEND; the citizen supplies the documents the process is waiting for +// (S-10a); a behandelaar then logs in to the behandel portal, finds the registration in the werkbak, +// and approves it (goedkeuren); the decision completes the Flowable Beoordelen task and flows via the +// ACL → NRC → event-subscriber → projection, and the openbaar register shows INGESCHREVEN. +test('DigiD submit → public INGEDIEND → documenten → behandelaar goedkeurt → public INGESCHREVEN', async ({ + page, + context, +}) => { // Visiting the guarded page redirects to the Keycloak (mock DigiD) login. await page.goto('/'); @@ -25,49 +29,54 @@ test('DigiD submit → public INGEDIEND → behandelaar goedkeurt → public ING const reference = (await confirmation.textContent())?.match(/Referentie:\s*([0-9a-fA-F-]+)/)?.[1]; expect(reference, 'the confirmation shows a registration reference').toBeTruthy(); - // Provide the documents the registration is waiting for (S-10a): the process parks at - // WachtOpDocumenten right after the zaak is opened, so it only reaches the behandelaar's werkbak once - // the documents are supplied. (S-10b turns this into a real file upload; here it is the trigger.) - await page.getByRole('button', { name: /documenten aanleveren/i }).click(); - await expect(page.getByText(/documenten zijn aangeleverd/i)).toBeVisible(); - // The openbaar register (anonymous, its own origin) shows the submitted entry once the projection - // catches up. The projection updates asynchronously (NRC → event-subscriber), and the register loads - // on open, so reload until *this* submission's row appears. We poll on the reference cell (not a - // generic INGEDIEND cell): the shared verify stack already holds INGEDIEND rows from earlier checks, - // so a status-only poll would short-circuit on a stale row before our row is projected. - await page.goto('http://openbaar/'); - await expect(page.getByRole('heading', { name: /Openbaar BIG-register/i })).toBeVisible(); + // catches up. We check it on a SEPARATE page so the self-service tab keeps its (in-memory) submitted + // state — the "Documenten aanleveren" action below acts on that same session. The projection updates + // asynchronously (NRC → event-subscriber), so reload until *this* submission's row appears. We poll + // on the reference cell (not a generic INGEDIEND cell): the shared verify stack already holds + // INGEDIEND rows from earlier checks, so a status-only poll would short-circuit on a stale row. + const staff = await context.newPage(); + await staff.goto('http://openbaar/'); + await expect(staff.getByRole('heading', { name: /Openbaar BIG-register/i })).toBeVisible(); // #78: the reference shown in the public register must be the exact one the citizen saw on the // submit confirmation — no mismatch between the two portals. await expect .poll(async () => { - await page.reload(); - return page.getByRole('cell', { name: reference }).count(); + await staff.reload(); + return staff.getByRole('cell', { name: reference }).count(); }, { timeout: 30_000, intervals: [1_000, 2_000, 3_000, 5_000] }) .toBeGreaterThan(0); - await expect(page.getByRole('row', { name: reference }).getByRole('cell', { name: 'INGEDIEND' })) + await expect(staff.getByRole('row', { name: reference }).getByRole('cell', { name: 'INGEDIEND' })) .toBeVisible(); - // A behandelaar picks the registration up in the behandel-portal werkbak and approves it - // (goedkeuren) — the S-12 flow that replaces the temporary admin endpoint. Navigating here switches - // to the medewerker realm (a different Keycloak realm than the citizen's digid session). - await page.goto('http://behandel/'); - await page.locator('#username').fill('merel-behandelaar'); - await page.locator('#password').fill('test123'); - await page.locator('#kc-login').click(); + // Provide the documents the registration is waiting for (S-10a), on the still-open self-service tab. + // The process parks at WachtOpDocumenten only after the zaak is opened; the INGEDIEND row above proves + // the zaak exists — so the OpenZaak worker has completed and the process is now at the wait — which is + // why we supply the documents here rather than right after submit, when the trigger would race the + // wait and no-op. (S-10b turns this into a real file upload; here it is the trigger that unblocks + // beoordeling.) + await page.getByRole('button', { name: /documenten aanleveren/i }).click(); + await expect(page.getByText(/documenten zijn aangeleverd/i)).toBeVisible(); - await expect(page.getByRole('heading', { name: /Werkbak/i })).toBeVisible(); + // A behandelaar picks the registration up in the behandel-portal werkbak and approves it (goedkeuren) + // — the S-12 flow that replaces the temporary admin endpoint. The staff tab switches to the + // medewerker realm (a different Keycloak realm than the citizen's digid session). + await staff.goto('http://behandel/'); + await staff.locator('#username').fill('merel-behandelaar'); + await staff.locator('#password').fill('test123'); + await staff.locator('#kc-login').click(); - // The registration parks at the Beoordelen user task only after the worker has opened its zaak, so + await expect(staff.getByRole('heading', { name: /Werkbak/i })).toBeVisible(); + + // The registration reaches the Beoordelen user task only after its documents are provided (above), so // it appears in the werkbak asynchronously — reload until this reference's row shows up. Target the // decide button by reference (not a generic "Goedkeuren"): the shared verify stack holds other open // tasks, so a positional match could act on someone else's registration. - const goedkeuren = page.getByRole('button', { name: `Goedkeuren ${reference}` }); + const goedkeuren = staff.getByRole('button', { name: `Goedkeuren ${reference}` }); await expect .poll(async () => { - await page.reload(); + await staff.reload(); return goedkeuren.count(); }, { timeout: 30_000, intervals: [1_000, 2_000, 3_000, 5_000] }) .toBeGreaterThan(0); @@ -75,7 +84,7 @@ test('DigiD submit → public INGEDIEND → behandelaar goedkeurt → public ING // Click and wait for the decide POST to finish (204) BEFORE leaving the page. `click()` only // dispatches the request; navigating away immediately cancels it in flight (nginx logs a 499) and // the decision never reaches the domain — so the registration would stay INGEDIEND. - const decided = page.waitForResponse( + const decided = staff.waitForResponse( (r) => r.url().includes(`/behandel/registrations/${reference}/decide`) && r.request().method() === 'POST', @@ -85,11 +94,11 @@ test('DigiD submit → public INGEDIEND → behandelaar goedkeurt → public ING // The approval flows back to the projection; back on the openbaar register *our* row (matched by // its reference) now shows INGESCHREVEN. - await page.goto('http://openbaar/'); + await staff.goto('http://openbaar/'); await expect .poll(async () => { - await page.reload(); - return page.getByRole('row', { name: reference }).getByRole('cell', { name: 'INGESCHREVEN' }).count(); + await staff.reload(); + return staff.getByRole('row', { name: reference }).getByRole('cell', { name: 'INGESCHREVEN' }).count(); }, { timeout: 30_000, intervals: [1_000, 2_000, 3_000, 5_000] }) .toBeGreaterThan(0); });