import { expect, test, type Page } from "@playwright/test"; test.describe.configure({ mode: "serial" }); async function loginAsOperationsManager(page: Page) { await page.goto("/login"); await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); await expect(page).toHaveURL(/\/dashboard$/); } test("90-second recruiter entry exposes three verifiable engineering highlights", async ({ page }) => { await page.goto("/login"); await page.getByRole("button", { name: "Bekijk de highlights in 90 seconden" }).click(); await expect(page).toHaveURL(/\/highlights$/); await expect(page.getByRole("heading", { name: "De sterkste keuzes in 90 seconden" })).toBeVisible(); await expect(page.locator(".highlight-card")).toHaveCount(3); await expect(page.getByText("Transactie · idempotency · outbox · degradatie")).toBeVisible(); await expect(page.getByText("RAGcore · citations · veilige degradatie")).toBeVisible(); }); test("Engineering Story exposes architecture, reliability and honest integration evidence", async ({ page }) => { await loginAsOperationsManager(page); await page.goto("/about"); await expect(page.getByRole("heading", { name: "Controle, betrouwbaarheid en uitlegbaarheid" }).first()).toBeVisible(); await expect(page.getByText("Commit eerst, orkestreer daarna")).toBeVisible(); await expect(page.getByText("AI moet bewijs tonen")).toBeVisible(); await expect(page.locator(".architecture-step", { hasText: "PostgreSQL + audittrail" }).getByRole("button")).toBeVisible(); const outboxStep = page.locator(".architecture-step", { hasText: "Betrouwbare outbox" }).getByRole("button"); await outboxStep.click(); await expect(outboxStep).toHaveAttribute("aria-pressed", "true"); await expect(page.locator("#architecture-active-detail")).toContainText("Post-commit aflevering"); const architectureTypeMinimums = [ [".architecture-zones > span", 12], [".architecture-step button > small", 13], [".architecture-zone-tag", 12], [".architecture-commit-note", 12], [".architecture-interaction-hint", 13], [".architecture-active-copy p", 13], [".architecture-active-evidence dt", 12], [".architecture-active-evidence dd", 13], ] as const; for (const [selector, minimumPixels] of architectureTypeMinimums) { const fontSize = await page.locator(selector).first().evaluate((element) => Number.parseFloat(window.getComputedStyle(element).fontSize)); expect(fontSize, `${selector} font-size`).toBeGreaterThanOrEqual(minimumPixels); } await expect(page.getByText(/nog niet live gekoppeld/)).toHaveCount(0); await expect(page.getByText(/unknown procedures/)).toHaveCount(0); }); test("dashboard load failure offers an in-place retry and recovers", async ({ page }) => { await loginAsOperationsManager(page); let dashboardAttempts = 0; let allowDashboardRecovery = false; await page.route("**/api/v1/dashboard", async (route) => { dashboardAttempts += 1; if (!allowDashboardRecovery) { await route.fulfill({ status: 503, contentType: "application/json", body: "{}" }); return; } await route.continue(); }); await page.reload(); const retryButton = page.getByRole("button", { name: "Opnieuw proberen" }); await expect(retryButton).toBeVisible(); allowDashboardRecovery = true; await retryButton.click(); await expect(page.getByRole("heading", { name: "Wagenparkstatus" })).toBeVisible(); expect(dashboardAttempts).toBeGreaterThanOrEqual(2); }); test("dashboard header stacks a full-width touch target on mobile", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await loginAsOperationsManager(page); const header = page.locator(".page-header"); const action = page.locator(".page-actions .button"); const [headerBox, actionBox] = await Promise.all([header.boundingBox(), action.boundingBox()]); expect(headerBox).not.toBeNull(); expect(actionBox).not.toBeNull(); expect(actionBox!.width).toBeGreaterThanOrEqual(headerBox!.width - 1); expect(Math.round(actionBox!.height)).toBeGreaterThanOrEqual(44); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual( await page.evaluate(() => document.documentElement.clientWidth + 1), ); }); test("mobile recruiter surfaces remain readable without horizontal overflow", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await page.goto("/login"); await page.getByRole("button", { name: "Bekijk de highlights in 90 seconden" }).click(); await expect(page).toHaveURL(/\/highlights$/); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth); expect(overflow).toBeLessThanOrEqual(1); await expect(page.locator(".highlight-card").first()).toBeVisible(); }); test("remaining attention items use a compact, legible queue action", async ({ page, request }) => { await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } }); await request.post("/api/v1/demo/reset"); await loginAsOperationsManager(page); const queueAction = page.locator(".queue-more a"); await expect(queueAction).toContainText("Nog 2 aandachtspunten"); await expect(queueAction).toContainText("Open de volledige werklijst"); const actionBox = await queueAction.boundingBox(); const arrowBox = await queueAction.locator(".queue-more-arrow svg").boundingBox(); expect(actionBox?.height).toBeLessThanOrEqual(70); expect(arrowBox?.width).toBeLessThanOrEqual(16); }); test("Knowledge Hub renders independently verified RAGcore index evidence", async ({ page }) => { await loginAsOperationsManager(page); await page.route("**/api/v1/knowledge/status**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ provider: "ragcore", available: true, detail: "11/11 managed sources verified", tenant: "northstar-mobility-demo", workspace: "mobilityops", collection: "internal-procedures", document_count: 11, source_document_count: 11, reported_synced_document_count: 33, reported_failed_document_count: 0, last_sync_at: "2026-08-10T16:00:00Z", statistics_state: "verified", }), }); }); await page.goto("/knowledge"); await expect(page.getByText("11 procedures geïndexeerd")).toBeVisible(); await expect(page.getByText("exact gevonden en actief gepubliceerd")).toBeVisible(); });