mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
The visual suite mocks `**/api/**` and nothing else, so the address map preview's iframe reached maps.google.com for real. Whether that frame paints, and what it paints, depends on the network and on what Google serves that minute — which is why `vendor-edit` failed in CI at 18178 differing pixels while passing in a container that could not reach Google. Regenerating the baseline would not have fixed it; it would have moved the flake. Aborting the request pins the frame to a blank box, so the shot measures our layout and nothing else. The committed baselines are unchanged by this — they were already correct — and a second container run with no --update passes 16/16, which is the evidence the shot is now stable rather than merely green once.
268 lines
8.6 KiB
TypeScript
268 lines
8.6 KiB
TypeScript
import { expect, test, type Page, type Route } from "@playwright/test";
|
||
|
||
const TEST_USER = {
|
||
token: "vendor-visual-token",
|
||
expiration: "2030-01-01T00:00:00.000Z",
|
||
email: "admin@seahavenind.com",
|
||
userRoles: "Admin",
|
||
phoneNumber: "5551234567",
|
||
fullname: "Vendor Visual Admin",
|
||
id: "1",
|
||
};
|
||
|
||
const activeVendors = [
|
||
{
|
||
Id: 1,
|
||
CompanyId: 101,
|
||
CompanyName: "Gateway Plumbing",
|
||
ContactName: "Adam Whyte",
|
||
Email: "dispatch@gateway.test",
|
||
Phone: "314-555-0198",
|
||
CompanyPhone: "314-555-0100",
|
||
Address: "1 Market St",
|
||
City: "St. Louis",
|
||
State: "MO",
|
||
Zip: "63101",
|
||
TradeSpecialties: "Plumbing, Backflow Preventers",
|
||
GoogleMapsUrl: "https://maps.google.com/gateway",
|
||
Notes: "Preferred after-hours vendor",
|
||
TotalJobs: 72,
|
||
IsActive: true,
|
||
PreferredContact: "Email",
|
||
},
|
||
{
|
||
Id: 2,
|
||
CompanyId: 102,
|
||
CompanyName: "Metro HVAC",
|
||
ContactName: "Maya Chen",
|
||
Email: "maya@metro.test",
|
||
Phone: "314-555-0112",
|
||
CompanyPhone: "314-555-0110",
|
||
Address: "2 Pine St",
|
||
City: "Clayton",
|
||
State: "MO",
|
||
Zip: "63105",
|
||
TradeSpecialties: "HVAC",
|
||
GoogleMapsUrl: "",
|
||
Notes: "",
|
||
TotalJobs: 18,
|
||
IsActive: true,
|
||
},
|
||
];
|
||
|
||
const inactiveVendors = [
|
||
{
|
||
...activeVendors[0],
|
||
Id: 3,
|
||
CompanyId: 103,
|
||
CompanyName: "Drain Cleaning Bros",
|
||
ContactName: "Drew Banks",
|
||
TotalJobs: 151,
|
||
IsActive: false,
|
||
},
|
||
];
|
||
|
||
async function fulfillJson(route: Route, body: unknown, status = 200) {
|
||
await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) });
|
||
}
|
||
|
||
async function mockVendorApi(page: Page, mode: "default" | "empty" | "error" = "default") {
|
||
await page.clock.setFixedTime(new Date("2026-08-10T12:00:00.000Z"));
|
||
await page.addInitScript((user) => localStorage.setItem("auth", JSON.stringify(user)), TEST_USER);
|
||
|
||
// The vendor address map preview embeds maps.google.com in an iframe. Its
|
||
// content depends on the network, on timing, and on whatever Google serves
|
||
// that minute, none of which belongs in a test with "deterministic pixel
|
||
// regression" in its name. Aborting the request pins the frame to a blank
|
||
// box, so the shot measures our layout and nothing else.
|
||
await page.route(/https:\/\/(www\.)?google\.com\/maps/, (route) => route.abort());
|
||
|
||
await page.route("**/api/**", async (route) => {
|
||
const request = route.request();
|
||
const url = new URL(request.url());
|
||
|
||
if (!url.pathname.startsWith("/api/")) {
|
||
await route.continue();
|
||
return;
|
||
}
|
||
|
||
if (url.pathname.includes("/DropdownOptions/ByCategory/Trade")) {
|
||
await fulfillJson(route, {
|
||
data: [
|
||
{ id: 1, value: "Plumbing" },
|
||
{ id: 2, value: "HVAC" },
|
||
],
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (url.pathname.endsWith("/api/Vendor/facets")) {
|
||
await fulfillJson(route, {
|
||
data: {
|
||
companies: activeVendors.map((vendor) => ({
|
||
companyId: vendor.CompanyId,
|
||
name: vendor.CompanyName,
|
||
})),
|
||
trades: ["Plumbing", "Backflow Preventers", "HVAC"],
|
||
locations: [
|
||
{ city: "St. Louis", state: "MO", label: "St. Louis, MO" },
|
||
{ city: "Clayton", state: "MO", label: "Clayton, MO" },
|
||
],
|
||
jobBuckets: [
|
||
{ id: "under-50", label: "Under 50" },
|
||
{ id: "50-99", label: "50–99" },
|
||
{ id: "100-149", label: "100–149" },
|
||
{ id: "150-plus", label: "150+" },
|
||
],
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (url.pathname.endsWith("/api/Vendor/GetVendorList")) {
|
||
const isCount = url.searchParams.get("pageSize") === "1";
|
||
if (mode === "error" && !isCount) {
|
||
await fulfillJson(route, { message: "Vendor directory unavailable" }, 500);
|
||
return;
|
||
}
|
||
const source =
|
||
mode === "empty"
|
||
? []
|
||
: url.searchParams.get("isActive") === "false"
|
||
? inactiveVendors
|
||
: activeVendors;
|
||
await fulfillJson(route, {
|
||
Data: isCount ? source.slice(0, 1) : source,
|
||
TotalCount: source.length,
|
||
PageNumber: 1,
|
||
PageSize: Number(url.searchParams.get("pageSize") ?? 12),
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (url.pathname.startsWith("/api/vendor-company-roster")) {
|
||
await fulfillJson(route, {
|
||
companyId: 101,
|
||
rowVersion: "rv-visual",
|
||
name: "Gateway Plumbing",
|
||
companyPhone: "314-555-0100",
|
||
email: "dispatch@gateway.test",
|
||
address: "1 Market St",
|
||
city: "St. Louis",
|
||
state: "MO",
|
||
zip: "63101",
|
||
googleMapsUrl: "https://maps.google.com/gateway",
|
||
notes: "Preferred after-hours vendor",
|
||
technicians: [
|
||
{
|
||
id: 1,
|
||
contactName: "Adam Whyte",
|
||
phone: "314-555-0198",
|
||
email: "dispatch@gateway.test",
|
||
preferredContact: "Email",
|
||
tradeSpecialties: "Plumbing, Backflow Preventers",
|
||
isActive: true,
|
||
totalJobs: 72,
|
||
},
|
||
],
|
||
});
|
||
return;
|
||
}
|
||
|
||
await fulfillJson(route, { data: {} });
|
||
});
|
||
}
|
||
|
||
async function openVendorPage(page: Page, mode: "default" | "empty" | "error" = "default") {
|
||
await mockVendorApi(page, mode);
|
||
await page.goto("/vendors");
|
||
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 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("Vendor deterministic pixel regression", () => {
|
||
test("list", async ({ page }) => {
|
||
await openVendorPage(page);
|
||
await expect(page.getByText("Gateway Plumbing")).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-list.png");
|
||
});
|
||
|
||
test("filter", async ({ page }) => {
|
||
await openVendorPage(page);
|
||
await page.getByRole("button", { name: "Open vendor filters" }).click();
|
||
await expect(page.getByRole("heading", { name: "Filters" })).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-filter.png");
|
||
});
|
||
|
||
test("add", async ({ page }) => {
|
||
await openVendorPage(page);
|
||
await page.getByRole("button", { name: "Add Vendor", exact: true }).click();
|
||
await expect(page.getByRole("dialog", { name: /Add Vendor/ })).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-add.png");
|
||
});
|
||
|
||
test("detail", async ({ page }) => {
|
||
await openVendorPage(page);
|
||
await page.getByRole("button", { name: "View vendor Gateway Plumbing" }).click();
|
||
await expect(page.getByRole("button", { name: "Close drawer" })).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-detail.png");
|
||
});
|
||
|
||
test("edit", async ({ page }) => {
|
||
await openVendorPage(page);
|
||
await page.getByRole("button", { name: "Edit vendor Gateway Plumbing" }).click();
|
||
await expect(page.getByRole("button", { name: "Save changes" })).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-edit.png");
|
||
});
|
||
|
||
test("inactive", async ({ page }) => {
|
||
await openVendorPage(page);
|
||
await page.getByRole("tab", { name: "Inactive (1)" }).click();
|
||
await expect(page.getByText("Drain Cleaning Bros")).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-inactive.png");
|
||
});
|
||
|
||
test("empty", async ({ page }) => {
|
||
await openVendorPage(page, "empty");
|
||
await expect(page.getByText("No active technicians found")).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-empty.png");
|
||
});
|
||
|
||
test("error", async ({ page }) => {
|
||
await openVendorPage(page, "error");
|
||
await expect(page.getByRole("main").getByRole("alert")).toContainText(
|
||
/server error|vendor directory unavailable/i,
|
||
);
|
||
await expectStableScreenshot(page, "vendor-error.png");
|
||
});
|
||
|
||
test("mobile", async ({ page }) => {
|
||
await page.setViewportSize({ width: 390, height: 844 });
|
||
await openVendorPage(page);
|
||
await expect(page.getByText("Gateway Plumbing")).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-mobile.png");
|
||
|
||
await page.getByRole("button", { name: "Toggle navigation" }).click();
|
||
await expect(page.getByRole("navigation", { name: "Primary navigation" })).toBeVisible();
|
||
await expectStableScreenshot(page, "vendor-mobile-navigation.png");
|
||
});
|
||
});
|