fa9aa5cff1
Implements the automated Playwright E2E suite as the pre-UAT gate following the UAT failures identified in GRO-299. Creates 5 test files in apps/web/e2e/: - portal-auth.spec.ts: verifies client portal auth (client name shown, not "Hi, Guest") - portal-data.spec.ts: verifies portal sections render without auth gates - admin-services.spec.ts: asserts no duplicate service names in admin/services and booking wizard - admin-reports.spec.ts: verifies reports page shows non-zero data for last 60 days - console-health.spec.ts: asserts no 404s for favicon/PWA assets and no JS exceptions Also adds: - apps/web/e2e/ with Playwright config targeting groombook.dev.farh.net - Shared fixtures with storageState-based auth via dev login selector - test:e2e npm script in apps/web/package.json - web-e2e CI job targeting PRs (runs after deploy-dev) Note: Tests 1 & 2 (portal auth/data) depend on GRO-300 being deployed. Tests 3-5 run against current dev state. Co-Authored-By: Paperclip <noreply@paperclip.ing>
121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
import { test, expect } from "./fixtures.js";
|
|
|
|
/**
|
|
* E2E test: Baseline Console Health (GRO-306)
|
|
*
|
|
* Verifies baseline console health on initial page load for both
|
|
* admin and portal views:
|
|
* - No 404s for favicon or PWA assets
|
|
* - No uncaught JS exceptions on initial render
|
|
*
|
|
* This test runs against current dev state (no GRO-300 dependency).
|
|
*/
|
|
test.describe("Baseline Console Health", () => {
|
|
test("admin page has no console errors on initial load", async ({
|
|
staffPage,
|
|
}) => {
|
|
const errors: string[] = [];
|
|
const failedRequests: string[] = [];
|
|
|
|
staffPage.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
staffPage.on("requestfailed", (request) => {
|
|
const url = request.url();
|
|
// Only care about asset failures, not API failures (which may be expected in dev)
|
|
if (
|
|
url.includes("favicon") ||
|
|
url.includes(".ico") ||
|
|
url.includes("manifest") ||
|
|
url.includes(".js") ||
|
|
url.includes(".css") ||
|
|
url.includes(".png") ||
|
|
url.includes(".svg")
|
|
) {
|
|
failedRequests.push(`${request.failure()?.errorText} — ${url}`);
|
|
}
|
|
});
|
|
|
|
await staffPage.goto("/admin");
|
|
await staffPage.waitForLoadState("networkidle");
|
|
|
|
// Filter out non-critical errors
|
|
const criticalErrors = errors.filter(
|
|
(e) =>
|
|
!e.includes("favicon") &&
|
|
!e.includes("net::ERR_") &&
|
|
!e.includes("Failed to load resource")
|
|
);
|
|
|
|
expect(criticalErrors).toHaveLength(0);
|
|
expect(failedRequests).toHaveLength(0);
|
|
});
|
|
|
|
test("portal page has no console errors on initial load", async ({
|
|
clientPage,
|
|
}) => {
|
|
const errors: string[] = [];
|
|
const failedRequests: string[] = [];
|
|
|
|
clientPage.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
clientPage.on("requestfailed", (request) => {
|
|
const url = request.url();
|
|
if (
|
|
url.includes("favicon") ||
|
|
url.includes(".ico") ||
|
|
url.includes("manifest") ||
|
|
url.includes(".js") ||
|
|
url.includes(".css") ||
|
|
url.includes(".png") ||
|
|
url.includes(".svg")
|
|
) {
|
|
failedRequests.push(`${request.failure()?.errorText} — ${url}`);
|
|
}
|
|
});
|
|
|
|
await clientPage.goto("/");
|
|
await clientPage.waitForLoadState("networkidle");
|
|
|
|
const criticalErrors = errors.filter(
|
|
(e) =>
|
|
!e.includes("favicon") &&
|
|
!e.includes("net::ERR_") &&
|
|
!e.includes("Failed to load resource")
|
|
);
|
|
|
|
expect(criticalErrors).toHaveLength(0);
|
|
expect(failedRequests).toHaveLength(0);
|
|
});
|
|
|
|
test("no 404s for favicon or PWA assets", async ({ staffPage }) => {
|
|
const notFound: string[] = [];
|
|
|
|
staffPage.on("response", (response) => {
|
|
const status = response.status();
|
|
const url = response.url();
|
|
if (
|
|
status === 404 &&
|
|
(url.includes("favicon") ||
|
|
url.includes(".ico") ||
|
|
url.includes("manifest") ||
|
|
url.includes("sw.js") ||
|
|
url.includes("workbox"))
|
|
) {
|
|
notFound.push(url);
|
|
}
|
|
});
|
|
|
|
await staffPage.goto("/admin");
|
|
await staffPage.waitForLoadState("networkidle");
|
|
|
|
expect(notFound).toHaveLength(0);
|
|
});
|
|
}); |