mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 23:13:12 +00:00
289 lines
8.4 KiB
TypeScript
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");
|
|
});
|
|
});
|