108 lines
5.1 KiB
TypeScript
108 lines
5.1 KiB
TypeScript
import { expect, test, type APIRequestContext } from "@playwright/test";
|
|
|
|
async function resetDemoData(request: APIRequestContext) {
|
|
await request.post("/api/v1/demo/login", { data: { role: "operations_manager" } });
|
|
await request.post("/api/v1/demo/reset");
|
|
}
|
|
|
|
// 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, request }) => {
|
|
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();
|
|
await expect(page).toHaveURL(/\/dashboard$/);
|
|
});
|
|
|
|
test("control-centre shell exposes landmarks, persisted readiness and active navigation", async ({ page }) => {
|
|
await expect(page.getByRole("navigation", { name: "Primary navigation" })).toBeVisible();
|
|
await expect(page.getByRole("main")).toBeVisible();
|
|
await expect(page.getByRole("heading", { name: "Fleet readiness" })).toBeVisible();
|
|
await expect(page.locator(".metric-cell dd").first()).not.toHaveText("");
|
|
await expect(page.getByRole("link", { name: "Overview" }).first()).toHaveAttribute("aria-current", "page");
|
|
});
|
|
|
|
test("global search supports its keyboard shortcut and finds a vehicle by reference", async ({ page }) => {
|
|
const search = page.getByRole("combobox", { name: "Search Fleet Ops" });
|
|
await expect(async () => {
|
|
await page.keyboard.press("Control+k");
|
|
await expect(search).toBeFocused();
|
|
}).toPass({ timeout: 5_000 });
|
|
await search.fill("MO-024");
|
|
const result = page.getByRole("option", { name: /MO-024/ });
|
|
await expect(result).toBeVisible();
|
|
await result.click();
|
|
await expect(page).toHaveURL(/\/vehicles\/MO-024$/);
|
|
await expect(page.getByRole("heading", { name: "MO-024" })).toBeVisible();
|
|
});
|
|
|
|
test("global search supports arrow-key navigation and Enter to select", async ({ page }) => {
|
|
const search = page.getByRole("combobox", { name: "Search Fleet Ops" });
|
|
await search.fill("fleet");
|
|
await expect(page.getByRole("option", { name: /Fleet/ })).toBeVisible();
|
|
await search.press("ArrowDown");
|
|
await search.press("Enter");
|
|
await expect(page).toHaveURL(/\/vehicles$/);
|
|
});
|
|
|
|
test("global search shows a no-results state and closes on Escape", async ({ page }) => {
|
|
const search = page.getByRole("combobox", { name: "Search Fleet Ops" });
|
|
await search.fill("zzz-nothing-matches-zzz");
|
|
await expect(page.getByText(/No matches for/)).toBeVisible();
|
|
await search.press("Escape");
|
|
await expect(page.getByRole("listbox")).not.toBeVisible();
|
|
});
|
|
|
|
test("global search finds a booking and a data-quality issue by reference", async ({ page }) => {
|
|
const search = page.getByRole("combobox", { name: "Search Fleet Ops" });
|
|
await search.fill("BK-DEMO-RETURN");
|
|
const bookingResult = page.getByRole("option", { name: /BK-DEMO-RETURN/ });
|
|
await expect(bookingResult).toBeVisible();
|
|
await bookingResult.click();
|
|
await expect(page).toHaveURL(/\/bookings\/BK-DEMO-RETURN$/);
|
|
|
|
await search.fill("DQ-DEMO-OVERLAP");
|
|
const issueResult = page.getByRole("option", { name: /DQ-DEMO-OVERLAP/ });
|
|
await expect(issueResult).toBeVisible();
|
|
await issueResult.click();
|
|
await expect(page).toHaveURL(/\/data-quality\/DQ-DEMO-OVERLAP$/);
|
|
});
|
|
|
|
test("return review separates capture from irreversible commit", async ({ page }) => {
|
|
await page.goto("/bookings/BK-DEMO-RETURN");
|
|
await page.getByLabel("End odometer (km)").fill("60000");
|
|
await page.getByLabel("Fuel level (%)").fill("65");
|
|
|
|
let commitRequests = 0;
|
|
let previewRequests = 0;
|
|
page.on("request", (request) => {
|
|
if (request.method() !== "POST") return;
|
|
if (request.url().endsWith("/return-preview")) previewRequests += 1;
|
|
else if (request.url().endsWith("/return")) commitRequests += 1;
|
|
});
|
|
|
|
await page.getByRole("button", { name: "Review return" }).click();
|
|
await expect(page.getByText(/Expected fleet state/)).toBeVisible();
|
|
expect(commitRequests).toBe(0);
|
|
// The review step is server-evaluated (not client-guessed), so exactly one non-mutating
|
|
// preview call is expected before any commit.
|
|
expect(previewRequests).toBe(1);
|
|
await expect(page.getByText("Queue automation after the local commit")).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Edit details" }).click();
|
|
await expect(page.getByLabel("End odometer (km)")).toHaveValue("60000");
|
|
});
|
|
|
|
for (const width of [390, 768, 1280, 1440]) {
|
|
test(`responsive shell has no horizontal overflow at ${width}px`, async ({ page }) => {
|
|
await page.setViewportSize({ width, height: width < 700 ? 844 : 900 });
|
|
await page.goto("/dashboard");
|
|
await expect(page.getByRole("heading", { name: "Attention queue" })).toBeVisible();
|
|
const dimensions = await page.evaluate(() => ({ scroll: document.documentElement.scrollWidth, client: document.documentElement.clientWidth }));
|
|
expect(dimensions.scroll).toBeLessThanOrEqual(dimensions.client + 1);
|
|
if (width < 960) await expect(page.getByRole("navigation", { name: "Mobile navigation" })).toBeVisible();
|
|
});
|
|
}
|