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/fixtures.ts
T
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

110 lines
3.4 KiB
TypeScript

import { test as base, Page, Browser, BrowserContext } from "@playwright/test";
import path from "path";
const STAFF_STORAGE = path.join(process.cwd(), ".auth/staff.json");
const CLIENT_STORAGE = path.join(process.cwd(), ".auth/client.json");
/**
* Authenticates as a staff user via the dev login selector and saves storage state.
*/
async function authenticateStaff(browser: Browser): Promise<string> {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("/login");
// Click "Alice Groomer" (first staff user)
const alice = page.getByText("Alice Groomer");
if (await alice.isVisible({ timeout: 5000 })) {
await alice.click();
} else {
// Fallback: click any staff user
await page.getByText(/groomer|manager/i).first().click();
}
await page.waitForURL(/\/(admin|portal)/, { timeout: 10000 });
const storageState = await context.storageState();
await context.close();
return JSON.stringify(storageState);
}
/**
* Authenticates as a client via the dev login selector and saves storage state.
*/
async function authenticateClient(browser: Browser): Promise<string> {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("/login");
// Click "Carol Client" (first client user)
const carol = page.getByText("Carol Client");
if (await carol.isVisible({ timeout: 5000 })) {
await carol.click();
} else {
// Fallback: click any client user
await page.getByText(/\d+ pets?/i).first().click();
}
await page.waitForURL(/\//, { timeout: 10000 });
await page.waitForLoadState("networkidle");
const storageState = await context.storageState();
await context.close();
return JSON.stringify(storageState);
}
export type UserType = "staff" | "client";
/**
* Returns the storage state file path for the given user type.
* Creates the auth file if it doesn't exist.
*/
async function getStorageState(browser: Browser, userType: UserType): Promise<string> {
const filePath = userType === "staff" ? STAFF_STORAGE : CLIENT_STORAGE;
const dir = path.dirname(filePath);
if (!require("fs").existsSync(filePath)) {
const state =
userType === "staff"
? await authenticateStaff(browser)
: await authenticateClient(browser);
require("fs").mkdirSync(dir, { recursive: true });
require("fs").writeFileSync(filePath, state);
}
return filePath;
}
/**
* Custom test fixture that provides an authenticated page for E2E tests.
* Automatically handles login via the dev login selector.
*
* Usage:
* test("my test", async ({ staffPage }) => { ... }); // staff user
* test("my test", async ({ clientPage }) => { ... }); // client user
*/
export const test = base.extend<{
staffPage: Page;
clientPage: Page;
}>({
staffPage: async ({ browser }, use, workerInfo): Promise<void> => {
const storageStatePath = await getStorageState(browser, "staff");
const context = await browser.newContext({ storageState: storageStatePath });
const page = await context.newPage();
await use(page);
await context.close();
},
clientPage: async ({ browser }, use, workerInfo): Promise<void> => {
const storageStatePath = await getStorageState(browser, "client");
const context = await browser.newContext({ storageState: storageStatePath });
const page = await context.newPage();
await use(page);
await context.close();
},
});
export { expect } from "@playwright/test";