M15: synchronize contracts and acceptance

This commit is contained in:
NuklearRabbit
2026-08-10 05:39:19 +02:00
parent 58fb515337
commit 2ee8b2d82b
38 changed files with 5091 additions and 1727 deletions
+35 -15
View File
@@ -5,12 +5,20 @@ async function resetDemoData(request: APIRequestContext) {
await request.post("/api/v1/demo/reset");
}
async function switchRole(page: Page) {
await page.locator(".operator-menu > summary").click();
await page.getByRole("button", { name: "Switch role" }).click();
}
test.describe.configure({ mode: "serial" });
// This file's assertions were authored against the English UI copy; nl-BE is now the
// app's default for a fresh session, so force English explicitly rather than rewriting
// every assertion (the equivalent Dutch/French coverage lives in the i18n-specific specs).
test.beforeEach(async ({ page }) => {
test.beforeEach(async ({ page, request }) => {
// Keep reset's Set-Cookie deletion in the standalone request fixture. Sharing the
// browser cookie jar here can race that deletion against the UI login below.
await resetDemoData(request);
await page.addInitScript(() => localStorage.setItem("fleetops.language", "en-GB"));
await page.goto("/login");
await page.getByRole("button", { name: "Explore as Operations Manager" }).click();
@@ -44,7 +52,13 @@ test("vehicles page: status filter and attention-only checkbox both work", async
expect(statuses.every((s) => s.includes("maintenance"))).toBeTruthy();
await page.getByLabel("Status").selectOption("");
await page.getByLabel("Attention only").check();
await expect(page).not.toHaveURL(/status=maintenance/);
await expect(page.getByLabel("Attention only")).not.toBeChecked();
// Updating the URL replaces this controlled input. Click it, then assert against the
// newly-rendered control rather than asking Playwright to verify the detached node.
await page.getByLabel("Attention only").click();
await expect(page).toHaveURL(/attention_only=true/);
await expect(page.getByLabel("Attention only")).toBeChecked();
await expect(page.locator(".data-table tbody tr").first()).toBeVisible();
const attentionCells = await page.locator(".data-table tbody tr td:nth-child(6)").allTextContents();
expect(attentionCells.every((c) => c.includes("Needs attention"))).toBeTruthy();
@@ -158,14 +172,16 @@ test("data quality page: status and rule-type filters work", async ({ page }) =>
test("data quality issue detail: defer and reject buttons work", async ({ page, request }) => {
await resetDemoData(request);
await page.goto("/data-quality?status=open&rule_type=missing_required_field");
await page.goto("/data-quality");
await page
.getByRole("combobox", { name: "Rule type", exact: true })
.selectOption("missing_required_field");
await expect(page.getByRole("combobox", { name: "Rule type", exact: true })).toHaveValue(
"missing_required_field",
);
const firstLink = page.locator(".data-table tbody tr").first().locator("a");
const ref = await firstLink.textContent();
await expect(firstLink).toBeVisible();
const ref = (await firstLink.textContent())?.trim() ?? "";
expect(ref).not.toBe("");
await firstLink.click();
await expect(page.getByRole("heading", { name: ref ?? "" })).toBeVisible();
await expect(page).toHaveURL(new RegExp(`/data-quality/${ref}$`));
await expect(page.getByRole("heading", { name: ref })).toBeVisible();
await page.getByRole("button", { name: "Defer" }).click();
await expect(page.locator(".badge.status-deferred")).toBeVisible();
});
@@ -193,8 +209,12 @@ test("data quality: resolving a booking overlap blocks one booking", async ({ pa
await page.getByRole("button", { name: /^Block BK-DEMO-OVERLAP-A$/ }).click();
await expect(page.getByText("Resolved").first()).toBeVisible();
const booking = await page.request.get("/api/v1/bookings/BK-DEMO-OVERLAP-A");
expect((await booking.json()).status).toBe("blocked");
await expect
.poll(async () => {
const booking = await page.request.get("/api/v1/bookings/BK-DEMO-OVERLAP-A");
return (await booking.json()).status;
})
.toBe("blocked");
});
test("data quality: applying the recommended status resolves a vehicle conflict", async ({
@@ -308,12 +328,12 @@ test("knowledge page: form submits and clears input", async ({ page }) => {
const input = page.getByPlaceholder(/What must I do when a vehicle returns with damage/);
await input.fill("What must I do when a vehicle returns with damage?");
await page.getByRole("button", { name: "Ask" }).click();
await expect(page.getByText("Grounded in cited procedures")).toBeVisible();
await expect(page.getByText("Grounded in cited procedures")).toBeVisible({ timeout: 15_000 });
await expect(input).toHaveValue("");
});
test("switch role button logs out and returns to login", async ({ page }) => {
await page.getByRole("button", { name: "Switch role" }).click();
await switchRole(page);
await expect(page).toHaveURL(/\/login$/);
});
@@ -327,7 +347,7 @@ test("session survives a page refresh and restores the correct role", async ({ p
test("logout invalidates the server session so a refresh returns to login", async ({ page }) => {
await page.goto("/dashboard");
await page.getByRole("button", { name: "Switch role" }).click();
await switchRole(page);
await expect(page).toHaveURL(/\/login$/);
// Directly re-requesting a protected route after logout must not restore access from a
@@ -348,7 +368,7 @@ test("direct navigation to a protected route without a session redirects to logi
test("rental employee role has a restricted nav and cannot reach manager-only pages", async ({
page,
}) => {
await page.getByRole("button", { name: "Switch role" }).click();
await switchRole(page);
await page.getByRole("button", { name: "Explore as Rental Employee" }).click();
await expect(page).toHaveURL(/\/dashboard$/);
@@ -400,7 +420,7 @@ test("automation page shows the aggregate n8n integration status, not just the l
test("rental employee direct API access to manager-only endpoints is rejected", async ({
page,
}) => {
await page.getByRole("button", { name: "Switch role" }).click();
await switchRole(page);
await page.getByRole("button", { name: "Explore as Rental Employee" }).click();
await expect(page).toHaveURL(/\/dashboard$/);