import { test, expect } from "@playwright/test"; import AxeBuilder from "@axe-core/playwright"; import { loginAsAdmin } from "./helpers/auth.js"; const publicPages = [ { name: "Home", path: "/" }, { name: "Join", path: "/join" }, { name: "Events", path: "/events" }, { name: "Coming Soon", path: "/coming-soon" }, ]; const memberPages = [ { name: "Member Dashboard", path: "/member/dashboard" }, { name: "Member Profile", path: "/member/profile" }, ]; const adminPages = [ { name: "Admin Members", path: "/admin/members" }, { name: "Admin Events Create", path: "/admin/events/create" }, ]; test.describe("accessibility — public pages", () => { for (const { name, path } of publicPages) { test(`${name} has no critical a11y violations`, async ({ page }) => { await page.goto(path); await page.waitForLoadState("networkidle"); const results = await new AxeBuilder({ page }) .withTags(["wcag2a", "wcag2aa"]) .analyze(); const critical = results.violations.filter( (v) => v.impact === "critical" || v.impact === "serious", ); expect(critical, `${name} has critical/serious a11y issues`).toEqual([]); }); } }); test.describe("accessibility — member pages", () => { test.beforeEach(async ({ page }) => { await loginAsAdmin(page); }); for (const { name, path } of memberPages) { test(`${name} has no critical a11y violations`, async ({ page }) => { await page.goto(path); await page.waitForLoadState("networkidle"); const results = await new AxeBuilder({ page }) .withTags(["wcag2a", "wcag2aa"]) .analyze(); const critical = results.violations.filter( (v) => v.impact === "critical" || v.impact === "serious", ); expect(critical, `${name} has critical/serious a11y issues`).toEqual([]); }); } }); test.describe("accessibility — admin pages", () => { test.beforeEach(async ({ page }) => { await loginAsAdmin(page); }); for (const { name, path } of adminPages) { test(`${name} has no critical a11y violations`, async ({ page }) => { await page.goto(path); await page.waitForLoadState("networkidle"); const results = await new AxeBuilder({ page }) .withTags(["wcag2a", "wcag2aa"]) .analyze(); const critical = results.violations.filter( (v) => v.impact === "critical" || v.impact === "serious", ); expect(critical, `${name} has critical/serious a11y issues`).toEqual([]); }); } }); test.describe("keyboard navigation", () => { test("tab through join form fields in order", async ({ page }) => { await page.goto("/join"); await page.waitForLoadState("networkidle"); // Focus the name field and tab through await page.locator("#join-name").focus(); expect( await page .locator("#join-name") .evaluate((el) => el === document.activeElement), ).toBe(true); await page.keyboard.press("Tab"); // Email field should receive focus next expect( await page .locator("#join-email") .evaluate((el) => el === document.activeElement), ).toBe(true); }); test("escape closes login modal", async ({ page }) => { await page.goto("/member/dashboard"); // The page renders an inline "sign in required" wall for unauthenticated users const signInBlock = page.locator("h2", { hasText: "Sign in required" }); await expect(signInBlock).toBeVisible({ timeout: 10000 }); // Click the Sign In button to open the login modal overlay await page.locator("button", { hasText: "Sign In" }).click(); const modal = page.locator("text=Sign in to your dashboard"); await expect(modal.first()).toBeVisible({ timeout: 5000 }); await page.keyboard.press("Escape"); // Modal should close await expect(modal.first()).not.toBeVisible({ timeout: 5000 }); }); });