Files
MobilityOps/frontend/e2e/demo.spec.ts
T
NuklearRabbitandClaude Sonnet 5 337f8716bb polish: rebrand to Fleet Ops, add trilingual i18n, adaptive demo guide, and UX overhaul
Rebrands the product from MobilityOps to Fleet Ops across the UI, backend defaults and
knowledge base, and makes nl-BE/en-GB/fr-BE full first-class languages: i18next with
eager-bundled per-namespace resources, a persisted accessible language switcher (topbar
and mobile drawer), locale-aware date/number formatting, and a coverage test that fails
the build on any missing or empty translation key.

Backend dynamic content (demo scenarios, blocked-reason text, integration status) moves
from fixed English/Dutch prose to stable message codes + params so the frontend can
localize it; the demo knowledge base gains a fully translated NL/EN/FR procedure corpus
(11 documents each) with per-language retrieval and localized evidence-state messages.

The Demo Guide becomes breakpoint-adaptive: a docked rail on extra-wide desktop, a
floating panel that auto-collapses to a persistent, closable progress chip on standard
desktop/tablet, and a collapsed/half/full bottom sheet on mobile -- with scroll+focus+
highlight on "go to this step", Escape handling, and reduced-motion support.

The Data Quality Workbench gets accessible choice-card decisions with a clear primary/
secondary/tertiary action hierarchy; the Automation ledger groups repeated successes and
uses meaningful short refs; the Audit trail groups events by correlation id with human
action labels and readable before/after diffs. Attention Queue, Today's movements,
Vehicles, Bookings and Data Quality rows are fully clickable (stretched-link pattern)
with independent secondary links, keyboard support and mobile touch targets.

Fixes a topbar overflow on mobile caused by the new language switcher (moved into the
mobile drawer at <=960px) and two dangling aria-labelledby references introduced this
session. Updates all affected Playwright specs for the new nl-BE default and the new
Audit/DemoGuide DOM structure, and adds new i18n-coverage, demo-guide-adaptive and
clickable-rows specs. 131 backend tests, Ruff and mypy, and 71 Playwright tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:33:22 +02:00

98 lines
4.7 KiB
TypeScript

import { expect, test, type APIRequestContext } from "@playwright/test";
async function resetDemoData(request: APIRequestContext) {
const login = await request.post("/api/v1/demo/login", {
data: { role: "operations_manager" },
});
expect(login.ok()).toBeTruthy();
const reset = await request.post("/api/v1/demo/reset");
expect(reset.ok()).toBeTruthy();
}
test.describe.configure({ mode: "serial" });
test("five-minute demo script end to end", async ({ page, request }) => {
// Reset via a throwaway API session so the UI test starts from the deterministic seed
// regardless of what earlier test runs mutated (S1 return, S2 merge, etc.).
await resetDemoData(request);
await test.step("1. login as Operations Manager", async () => {
await page.goto("/login");
await expect(page.getByText(/Synthetische demo/)).toBeVisible();
await page.getByRole("button", { name: "Verken als Operations Manager" }).click();
await expect(page).toHaveURL(/\/dashboard$/);
});
await test.step("2. verify dashboard metrics are loaded", async () => {
await expect(page.getByRole("heading", { name: "Wagenparkstatus" })).toBeVisible();
const metricValues = page.locator(".metric-cell dd");
await expect(metricValues.first()).toBeVisible();
const values = await metricValues.allTextContents();
expect(values.length).toBeGreaterThan(0);
expect(values.some((v) => Number(v) > 0)).toBeTruthy();
});
await test.step("3. open active demo booking", async () => {
await page.goto("/bookings/BK-DEMO-RETURN");
await expect(page.getByRole("heading", { name: "BK-DEMO-RETURN" })).toBeVisible();
await expect(page.getByText("actief", { exact: true })).toBeVisible();
});
await test.step("4. register an odometer-regression return (S1)", async () => {
const vehicleOdometerText = await page
.locator(".detail-grid div", { hasText: "Startkilometerstand" })
.locator("dd")
.textContent();
const startOdometer = parseInt((vehicleOdometerText ?? "0").replace(/\D/g, ""), 10);
const lowReading = Math.max(0, startOdometer - 500);
await page.getByLabel("Eindkilometerstand (km)").fill(String(lowReading));
await page.getByLabel("Brandstofniveau (%)").fill("55");
await page.getByRole("button", { name: "Retour nakijken" }).click();
await expect(page.getByRole("heading", { name: "Retourimpact nakijken" })).toBeVisible();
await page.getByRole("button", { name: "Retour bevestigen" }).click();
await expect(page.getByRole("heading", { name: "Retour geregistreerd" })).toBeVisible();
});
await test.step("5. verify quality issue and queued automation event", async () => {
await expect(page.getByText(/DQ-RET-|Geen aangemaakt/)).toBeVisible();
await expect(page.getByText(/Klaargezet voor verwerking \(/)).toBeVisible();
});
await test.step("6. resolve the duplicate customer scenario (S2)", async () => {
await page.goto("/data-quality/DQ-DEMO-DUPLICATE");
await expect(page.getByRole("heading", { name: "Vergelijken en samenvoegen" })).toBeVisible();
await page.getByRole("button", { name: /Samenvoegen met CUS-0012/ }).click();
await page.getByRole("button", { name: "Ja, samenvoegen" }).click();
await expect(page.locator(".badge.status-resolved")).toBeVisible();
});
await test.step("7. ask the damage question and inspect citations (S6)", async () => {
await page.goto("/knowledge");
await page
.getByPlaceholder(/Wat moet ik doen wanneer een voertuig beschadigd terugkomt/)
.fill("Wat moet ik doen wanneer een voertuig terugkomt met schade?");
await page.getByRole("button", { name: "Vraag stellen" }).click();
await expect(page.getByText("Onderbouwd met geciteerde procedures")).toBeVisible();
await expect(page.getByText("Procedure schadeafhandeling").first()).toBeVisible();
});
await test.step("8. inspect audit entries", async () => {
await page.goto("/audit");
await page.getByLabel("Actie").fill("return_registered");
await expect(page.locator(".audit-group-list li").first()).toBeVisible();
await expect(page.getByText("Voertuigretour geregistreerd").first()).toBeVisible();
});
await test.step("9. verify responsive navigation at mobile width", async () => {
await page.setViewportSize({ width: 360, height: 800 });
await page.goto("/dashboard");
await expect(page.getByText(/Synthetische demo/).first()).toBeVisible();
await expect(page.getByRole("link", { name: "Overzicht" }).first()).toBeVisible();
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1);
});
});