shoc-frontend-new/e2e/work-orders/work-orders.visual.spec.ts
2026-09-16 23:28:41 -03:00

289 lines
8.4 KiB
TypeScript

import { expect, test, type Page, type Route } from "@playwright/test";
const FROZEN_NOW = "2026-08-19T15:00:00.000Z";
const WEEK_START = "2026-08-17";
const WEEK_END = "2026-08-21";
const TEST_USER = {
token: "wo-visual-token",
expiration: "2030-01-01T00:00:00.000Z",
email: "admin@seahavenind.com",
userRoles: "Admin",
phoneNumber: "5551234567",
fullname: "Work Order Visual Admin",
id: "1",
};
const DISPATCHERS = [{ id: "1", name: "Pat Dispatcher" }];
const LOCATIONS = [
{
id: 10,
name: "STL-01 · St. Louis, MO",
siteCode: "STL-01",
city: "St. Louis",
state: "MO",
code: "STL-01",
},
];
const VENDORS = [
{
id: 101,
companyName: "Gateway Plumbing",
contactName: "Adam Whyte",
tradeSpecialties: "Plumbing",
address: "1 Market St",
},
];
const scheduledRow = {
id: 1,
woNumber: "WO-501",
workOrderType: 2,
siteCode: "STL-01",
locationName: "St. Louis HQ",
locationId: 10,
lifecycleStatusLabel: "Scheduled",
scheduledDate: "2026-08-19",
dispatcherId: "1",
dispatcherName: "Pat Dispatcher",
pm: "HVAC",
vendorId: 101,
vendorName: "Gateway Plumbing",
techName: "Adam Whyte",
title: "Quarterly HVAC PM",
dueDate: "2026-08-21",
rowVersion: "rv-visual",
};
const unassignedRow = {
...scheduledRow,
id: 2,
woNumber: "WO-502",
scheduledDate: null,
lifecycleStatusLabel: "Pending",
title: "Boiler leak follow-up",
};
async function fulfillJson(route: Route, body: unknown, status = 200) {
await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) });
}
function isBoardList(pathname: string) {
return pathname.endsWith("/workorders/board") || pathname.endsWith("/api/workorders/board");
}
function isBoardSearch(pathname: string) {
return pathname.includes("/workorders/board/search");
}
function isBoardDetail(pathname: string) {
return /\/workorders\/\d+\/detail$/.test(pathname);
}
function isAccountList(pathname: string) {
return pathname.includes("/Account/GetAccountList");
}
async function mockWorkOrderApi(page: Page, mode: "default" | "empty" | "error" = "default") {
await page.clock.setFixedTime(new Date(FROZEN_NOW));
await page.addInitScript((user) => localStorage.setItem("auth", JSON.stringify(user)), TEST_USER);
await page.route("**/api/**", async (route) => {
const url = new URL(route.request().url());
const pathname = url.pathname;
if (!pathname.startsWith("/api/")) {
await route.continue();
return;
}
if (isBoardSearch(pathname)) {
const items = mode === "default" ? [unassignedRow] : [];
await fulfillJson(route, { items, totalCount: items.length, page: 0, pageSize: 50 });
return;
}
if (isBoardList(pathname)) {
if (mode === "error") {
await fulfillJson(route, { message: "Work order board unavailable" }, 500);
return;
}
const scheduled = mode === "empty" ? [] : [scheduledRow];
const unscheduled = mode === "empty" ? [] : [unassignedRow];
await fulfillJson(route, {
weekStart: WEEK_START,
weekEnd: WEEK_END,
counts: { returned: scheduled.length + unscheduled.length, total: scheduled.length + 1 },
unscheduled,
scheduled,
});
return;
}
if (isBoardDetail(pathname)) {
await fulfillJson(route, {
data: {
info: scheduledRow,
comments: [],
audit: [],
media: [],
completion: {},
},
});
return;
}
if (pathname.includes("/workorders/lookups/dispatchers")) {
await fulfillJson(route, { data: DISPATCHERS });
return;
}
if (pathname.includes("/Contact/GetLocations") || pathname.includes("/locations/sites")) {
await fulfillJson(route, { data: LOCATIONS });
return;
}
if (pathname.includes("/vendors/Dropdown") || pathname.endsWith("/vendors/dropdown")) {
await fulfillJson(route, { data: VENDORS });
return;
}
if (pathname.includes("/DropdownOptions/ByCategory/Problem")) {
await fulfillJson(route, {
data: [
{ id: 1, value: "HVAC", category: "Problem" },
{ id: 2, value: "Plumbing", category: "Problem" },
],
});
return;
}
if (pathname.includes("/api/User")) {
await fulfillJson(route, { data: DISPATCHERS });
return;
}
if (isAccountList(pathname)) {
await fulfillJson(route, {
items: [
{
id: 1,
name: "Amazon",
number: "AMZ-1",
industry: "Retail",
website: "",
primaryContact: "",
phoneNumber: "",
owner: "",
},
],
totalCount: 1,
page: 1,
pageSize: 50,
});
return;
}
await fulfillJson(route, { data: [] });
});
}
async function openWorkOrderPage(page: Page, mode: "default" | "empty" | "error" = "default") {
await mockWorkOrderApi(page, mode);
await page.goto("/workorders");
await page.evaluate(() => document.fonts.ready);
await page.addStyleTag({
content: `
*, *::before, *::after { transition: none !important; animation: none !important; }
.MuiDrawer-paper { transform: none !important; }
.Toastify__toast-container { display: none !important; }
.tsqd-parent-container { display: none !important; }
`,
});
}
async function expectWorkOrderPageReady(page: Page) {
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
timeout: 90_000,
});
}
async function expectStableScreenshot(page: Page, name: string) {
await page.waitForTimeout(250);
await page.evaluate(
() =>
new Promise<void>((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
),
);
await page.screenshot({ animations: "disabled", caret: "hide" });
await expect(page).toHaveScreenshot(name);
}
test.describe("Work Orders deterministic pixel regression", () => {
test.setTimeout(120_000);
test("list", async ({ page }) => {
await openWorkOrderPage(page);
await expectWorkOrderPageReady(page);
await expect(page.getByText("WO-501").first()).toBeVisible();
await expectStableScreenshot(page, "wo-list.png");
});
test("filters", async ({ page }) => {
await openWorkOrderPage(page);
await expectWorkOrderPageReady(page);
await page.getByRole("button", { name: "Advanced Filters" }).click();
await expect(page.getByRole("dialog", { name: "Advanced Filters" })).toBeVisible();
await expectStableScreenshot(page, "wo-filters.png");
});
test("new", async ({ page }) => {
await openWorkOrderPage(page);
await expectWorkOrderPageReady(page);
await page.getByRole("button", { name: "New WO" }).click();
await expect(page.getByRole("heading", { name: "Type & schedule" })).toBeVisible();
await expectStableScreenshot(page, "wo-new.png");
});
test("detail", async ({ page }) => {
await openWorkOrderPage(page);
await expectWorkOrderPageReady(page);
const row = page.locator("#wo-row-1");
await row.hover();
await row.getByRole("button", { name: "View details" }).click();
await expect(page.getByRole("heading", { name: "#WO-501" })).toBeVisible();
await expectStableScreenshot(page, "wo-detail.png");
});
test("empty", async ({ page }) => {
await openWorkOrderPage(page, "empty");
await expectWorkOrderPageReady(page);
await page.getByLabel("Search work orders").fill("zz");
await expect(page.getByText("No work orders match your search")).toBeVisible();
await expectStableScreenshot(page, "wo-empty.png");
});
test("error", async ({ page }) => {
await openWorkOrderPage(page, "error");
await expectWorkOrderPageReady(page);
const alert = page.getByRole("main").getByRole("alert");
await expect(alert).toBeVisible();
await expect(alert).toContainText(/server error/i);
await expectStableScreenshot(page, "wo-error.png");
});
test("mobile", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await openWorkOrderPage(page);
await expectWorkOrderPageReady(page);
await expect(page.getByText("WO-501").first()).toBeVisible();
await expectStableScreenshot(page, "wo-mobile.png");
await page.getByRole("button", { name: "Toggle navigation" }).click();
await expect(page.getByRole("navigation", { name: "Primary navigation" })).toBeVisible();
await expectStableScreenshot(page, "wo-mobile-navigation.png");
});
});