import { expect, test, type Page } from "@playwright/test"; import type { AutomationRun } from "../src/api/types"; const apiErrorBody = JSON.stringify({ error: { code: "TEST_UNAVAILABLE", message: "Injected test failure", correlation_id: "test-correlation" }, }); async function loginAsManager(page: Page) { await page.goto("/login"); await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); await expect(page).toHaveURL(/\/dashboard$/); } async function failFirstManifestRequest(page: Page) { let requestCount = 0; await page.route("**/api/v1/demo/manifest", async (route) => { requestCount += 1; if (requestCount === 1) { await route.fulfill({ status: 503, contentType: "application/json", body: apiErrorBody }); return; } await route.continue(); }); } test("session validation does not wait for a stalled system-status request", async ({ page }) => { let releaseStatus = () => undefined; const statusGate = new Promise((resolve) => { releaseStatus = resolve; }); let sessionRequested = false; await page.route("**/api/v1/system/status", async (route) => { await statusGate; await route.fulfill({ status: 503, contentType: "application/json", body: apiErrorBody }); }); await page.route("**/api/v1/auth/session", async (route) => { sessionRequested = true; await route.fulfill({ status: 401, contentType: "application/json", body: apiErrorBody }); }); try { await page.goto("/login"); await expect(page.getByRole("button", { name: "Verken als Operationsmanager" })).toBeEnabled({ timeout: 2_000, }); expect(sessionRequested).toBe(true); } finally { releaseStatus(); } }); test("guided demo stays gated while its manifest loads, then exposes an explicit retry", async ({ page }) => { let releaseManifest = () => undefined; const manifestGate = new Promise((resolve) => { releaseManifest = resolve; }); let markManifestRequested = () => undefined; const manifestRequested = new Promise((resolve) => { markManifestRequested = resolve; }); let manifestAttempts = 0; await page.route("**/api/v1/demo/manifest", async (route) => { manifestAttempts += 1; if (manifestAttempts === 1) { markManifestRequested(); await manifestGate; await route.fulfill({ status: 503, contentType: "application/json", body: apiErrorBody }); return; } await route.continue(); }); try { await page.goto("/login"); await manifestRequested; const guidedDemo = page.getByRole("button", { name: "Start begeleide demo" }); await expect(guidedDemo).toBeDisabled(); await expect(page.getByText("Begeleide demo voorbereiden…")).toBeVisible(); releaseManifest(); await expect(page.getByText("De begeleide demo-informatie kon niet geladen worden.")).toBeVisible(); await expect(guidedDemo).toBeDisabled(); await page.getByRole("button", { name: "Demo-informatie opnieuw laden" }).click(); await expect(guidedDemo).toBeEnabled(); expect(manifestAttempts).toBeGreaterThanOrEqual(2); } finally { releaseManifest(); } }); test("guided demo does not continue until the manifest reports it ready", async ({ page }) => { let returnNotReady = true; await page.route("**/api/v1/demo/manifest", async (route) => { if (!returnNotReady) { await route.continue(); return; } const response = await route.fetch(); const manifest = await response.json() as Record; await route.fulfill({ response, json: { ...manifest, guide_available: false } }); }); await page.goto("/login"); const guidedDemo = page.getByRole("button", { name: "Start begeleide demo" }); await expect(guidedDemo).toBeDisabled(); await expect(page.getByText("De begeleide demo is momenteel niet klaar om te starten.")).toBeVisible(); await guidedDemo.evaluate((button) => { button.removeAttribute("disabled"); (button as HTMLButtonElement).click(); }); await expect(page).toHaveURL(/\/login$/); returnNotReady = false; await page.getByRole("button", { name: "Demo-informatie opnieuw laden" }).click(); await expect(guidedDemo).toBeEnabled(); }); test("blocked sessionStorage cannot break login or logout", async ({ page }) => { await page.addInitScript(() => { Object.defineProperties(Storage.prototype, { setItem: { configurable: true, value: () => { throw new DOMException("Storage is blocked", "SecurityError"); }, }, removeItem: { configurable: true, value: () => { throw new DOMException("Storage is blocked", "SecurityError"); }, }, }); }); await loginAsManager(page); await expect(page.getByText("Amelie De Ridder").first()).toBeVisible(); await page.locator(".operator-menu > summary").click(); await page.getByRole("button", { name: "Wissel van rol" }).click(); await expect(page).toHaveURL(/\/login$/); }); test("scenario manifest failure is visible and retryable", async ({ page }) => { await failFirstManifestRequest(page); await loginAsManager(page); await page.locator('a[href="/scenarios"]').first().click(); await expect(page).toHaveURL(/\/scenarios$/); await expect(page.getByText("De demonstratiescenario's konden niet geladen worden.")).toBeVisible(); await page.getByRole("button", { name: "Opnieuw proberen" }).click(); await expect(page.locator(".scenario-card")).toHaveCount(5); }); test("engineering manifest failure is visible and retryable", async ({ page }) => { await failFirstManifestRequest(page); await loginAsManager(page); await page.locator('a[href="/about"]').first().click(); await expect(page).toHaveURL(/\/about$/); await expect(page.getByText("De demo-informatie kon niet geladen worden.")).toBeVisible(); await page.getByRole("button", { name: "Opnieuw proberen" }).click(); await expect(page.getByRole("heading", { name: "Controle, betrouwbaarheid en uitlegbaarheid" })).toBeVisible(); }); test("the latest automation filter wins when an older response completes last", async ({ page }) => { test.setTimeout(45_000); let releaseOldResponse = () => undefined; const oldResponseGate = new Promise((resolve) => { releaseOldResponse = resolve; }); let markOldRequestStarted = () => undefined; const oldRequestStarted = new Promise((resolve) => { markOldRequestStarted = resolve; }); let markOldResponseAttempted = () => undefined; const oldResponseAttempted = new Promise((resolve) => { markOldResponseAttempted = resolve; }); const run = (status: "failed" | "pending", aggregateRef: string): AutomationRun => ({ event_id: status === "failed" ? "11111111-1111-4111-8111-111111111111" : "22222222-2222-4222-8222-222222222222", event_type: "vehicle.returned.v1", aggregate_ref: aggregateRef, status, attempts: status === "failed" ? 2 : 0, last_error: null, last_error_code: null, is_demo_scenario: false, occurred_at: "2026-08-23T10:00:00Z", }); await page.route(/\/api\/v1\/workflows(?:\?.*)?$/, async (route) => { const requestedStatus = new URL(route.request().url()).searchParams.get("status"); if (requestedStatus === "failed") { markOldRequestStarted(); await oldResponseGate; try { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([run("failed", "BK-RACE-OLD")]), }); } catch { // Chromium cancels the superseded fetch. The delayed route can therefore be // closed before Playwright gets to fulfil it; either outcome proves the same // contract as long as it cannot overwrite the newer response. } finally { markOldResponseAttempted(); } return; } if (requestedStatus === "pending") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([run("pending", "BK-RACE-NEW")]), }); return; } await route.continue(); }); try { await loginAsManager(page); await page.goto("/automation"); await expect(page.getByRole("heading", { name: "Automatiseringsopdrachten" })).toBeVisible(); const statusFilter = page.getByRole("combobox", { name: "Status", exact: true }); await statusFilter.selectOption("failed"); await oldRequestStarted; await statusFilter.selectOption("pending"); await expect(page.getByText("BK-RACE-NEW", { exact: true })).toBeVisible(); releaseOldResponse(); await oldResponseAttempted; await expect(page.getByText("BK-RACE-NEW", { exact: true })).toBeVisible(); await expect(page.getByText("BK-RACE-OLD", { exact: true })).toHaveCount(0); } finally { releaseOldResponse(); } });