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>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user