143 lines
6.4 KiB
TypeScript
143 lines
6.4 KiB
TypeScript
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();
|
|
});
|