This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
app/apps/web/e2e/tests/console-health.spec.ts
Flea Flicker fa9aa5cff1 feat(e2e): add Playwright E2E test suite for critical user journeys (GRO-306)
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>
2026-03-31 21:43:06 +00:00

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);
});
});