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/e2e/tests/navigation.spec.ts
T
groombook-paperclip[bot] 3388895912 Add dev/demo login selector for quick user switching (#62)
* Add dev/demo login selector for quick user switching

When AUTH_DISABLED=true, the app now shows a login selector page that
lists staff members and clients from the database. Selecting a user
sets a localStorage-based session and sends X-Dev-User-Id header on
all API requests. A persistent bottom bar shows the active persona
with a "Switch user" link.

- API: /api/dev/config (public) and /api/dev/users (auth-disabled only)
- API: auth middleware reads X-Dev-User-Id header when auth is disabled
- Frontend: DevLoginSelector page, DevSessionIndicator bar
- Frontend: fetch interceptor injects X-Dev-User-Id on /api/* calls
- Tests: 7 passing (5 nav + 2 dev login)

Closes #60

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(e2e): seed dev user in localStorage to prevent login redirect

E2E tests were failing because the dev login selector redirects to
/login when AUTH_DISABLED=true and no dev user is in localStorage.
Added a shared Playwright fixture that pre-seeds localStorage with
a default dev user before each test.

Also rebased onto latest main to resolve merge conflict in App.test.tsx.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(e2e): mock /api/dev/config to bypass auth redirect in tests

The fixture now also mocks /api/dev/config to return authDisabled: false,
preventing the app from entering the redirect flow during E2E tests.
Previously only seeded localStorage, but the async config fetch from the
real Docker API was still triggering the redirect check.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-19 07:35:07 +00:00

91 lines
3.2 KiB
TypeScript

import { test, expect } from "./fixtures.js";
/**
* Navigation smoke tests — verifies that each page loads without errors.
* These tests mock all API calls so they can run without a live backend.
*/
test.beforeEach(async ({ page }) => {
// Intercept all API calls and return empty defaults so pages render.
// Reports endpoints need shaped responses (not bare []) to avoid render crashes.
await page.route("/api/**", (route) => {
const url = route.request().url();
if (url.includes("/api/reports/summary")) {
return route.fulfill({
json: {
from: "",
to: "",
revenue: { totalCents: 0, paidInvoices: 0 },
appointments: { total: 0, completed: 0, cancelled: 0, noShow: 0 },
clients: { total: 0, new: 0 },
},
});
}
if (url.includes("/api/reports/revenue")) {
return route.fulfill({ json: { byPeriod: [], byGroomer: [] } });
}
if (url.includes("/api/reports/appointments")) {
return route.fulfill({ json: { byPeriod: [] } });
}
if (url.includes("/api/reports/services")) {
return route.fulfill({ json: { rows: [] } });
}
if (url.includes("/api/reports/clients")) {
return route.fulfill({
json: { newClients: [], activeInPeriodCount: 0, churnRisk: [], churnRiskTotal: 0 },
});
}
// Appointments, clients, services, staff, invoices, book, etc.
return route.fulfill({ json: [] });
});
});
test("customer portal loads at root", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("navigation").getByText("Paws & Reflect")).toBeVisible();
await expect(page.locator("nav")).toBeVisible();
});
test("admin appointments page loads", async ({ page }) => {
await page.goto("/admin");
await expect(page.getByText("GroomBook")).toBeVisible();
// Calendar/appointments view renders
await expect(page.locator("nav")).toBeVisible();
});
test("admin clients page loads", async ({ page }) => {
await page.goto("/admin/clients");
await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Clients" })).toBeVisible();
});
test("admin services page loads", async ({ page }) => {
await page.goto("/admin/services");
await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Services" })).toBeVisible();
});
test("admin staff page loads", async ({ page }) => {
await page.goto("/admin/staff");
await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Staff" })).toBeVisible();
});
test("admin invoices page loads", async ({ page }) => {
await page.goto("/admin/invoices");
await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Invoices" })).toBeVisible();
});
test("admin reports page loads", async ({ page }) => {
await page.goto("/admin/reports");
await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Reports" })).toBeVisible();
});
test("admin booking portal loads", async ({ page }) => {
await page.goto("/admin/book");
await expect(page.getByText("Book an Appointment")).toBeVisible();
await expect(page.getByText("Choose a service")).toBeVisible();
});