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>
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import { test, expect } from "./fixtures.js";
|
|
|
|
/**
|
|
* E2E test: Portal Data Integrity (GRO-306)
|
|
*
|
|
* Verifies that the client portal sections render correctly with actual data
|
|
* and don't show auth-gate messages after login.
|
|
*
|
|
* DEPENDENCY: Requires GRO-300 to be deployed. Tests 1 & 2 share this dependency.
|
|
*
|
|
* Journey:
|
|
* 1. Login as client
|
|
* 2. Navigate to appointments section — assert no "Please sign in", content renders
|
|
* 3. Navigate to pets section — assert content renders (or explicit empty state)
|
|
* 4. Navigate to billing section — assert no JS errors, section renders
|
|
*/
|
|
test.describe("Portal Data Integrity", () => {
|
|
test.beforeEach(async ({ clientPage }) => {
|
|
await clientPage.goto("/");
|
|
await clientPage.waitForLoadState("networkidle");
|
|
});
|
|
|
|
test("appointments section renders without auth gate", async ({
|
|
clientPage,
|
|
}) => {
|
|
// Click the Appointments nav item
|
|
const appointmentsNav = clientPage.getByRole("button", { name: /appointments/i });
|
|
await appointmentsNav.click();
|
|
await clientPage.waitForLoadState("networkidle");
|
|
|
|
// Must NOT show "Please sign in" gate
|
|
await expect(
|
|
clientPage.locator("text=Please sign in")
|
|
).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// The section heading or nav should indicate we're in appointments
|
|
await expect(
|
|
clientPage.locator("text=Appointments")
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("pets section renders with content or explicit empty state", async ({
|
|
clientPage,
|
|
}) => {
|
|
// Click the My Pets nav item
|
|
const petsNav = clientPage.getByRole("button", { name: /my pets/i });
|
|
await petsNav.click();
|
|
await clientPage.waitForLoadState("networkidle");
|
|
|
|
// Must NOT show auth gate
|
|
await expect(
|
|
clientPage.locator("text=Please sign in")
|
|
).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Should show either pet content or a legitimate empty state
|
|
const hasPetsContent =
|
|
(await clientPage.locator("text=Add a pet").isVisible()) ||
|
|
(await clientPage.locator("text=No pets").isVisible()) ||
|
|
(await clientPage.locator('[role="button"]').count()) > 0;
|
|
|
|
expect(hasPetsContent).toBeTruthy();
|
|
});
|
|
|
|
test("billing section renders without JS errors", async ({ clientPage }) => {
|
|
// Capture console errors
|
|
const consoleErrors: string[] = [];
|
|
clientPage.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
consoleErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
// Click the Billing nav item
|
|
const billingNav = clientPage.getByRole("button", { name: /billing/i });
|
|
await billingNav.click();
|
|
await clientPage.waitForLoadState("networkidle");
|
|
|
|
// Must NOT show auth gate
|
|
await expect(
|
|
clientPage.locator("text=Please sign in")
|
|
).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// No JS exceptions on this section
|
|
const jsExceptions = consoleErrors.filter(
|
|
(e) => !e.includes("favicon") && !e.includes("404")
|
|
);
|
|
expect(jsExceptions).toHaveLength(0);
|
|
});
|
|
}); |