Accessibility fixes.
Some checks are pending
Test / vitest (push) Waiting to run
Test / playwright (push) Blocked by required conditions
Test / visual (push) Blocked by required conditions

This commit is contained in:
Jennie Robinson Faber 2026-04-05 16:03:10 +01:00
parent 4aacb26c4b
commit 88c94aaaf4
12 changed files with 276 additions and 260 deletions

View file

@ -1,112 +1,127 @@
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
import { loginAsAdmin } from './helpers/auth.js'
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' },
]
{ 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' },
]
{ 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' },
]
{ name: "Admin Members", path: "/admin/members" },
{ name: "Admin Events Create", path: "/admin/events/create" },
];
test.describe('accessibility — public pages', () => {
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')
await page.goto(path);
await page.waitForLoadState("networkidle");
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
.withTags(["wcag2a", "wcag2aa"])
.analyze();
const critical = results.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
)
(v) => v.impact === "critical" || v.impact === "serious",
);
expect(critical, `${name} has critical/serious a11y issues`).toEqual([])
})
expect(critical, `${name} has critical/serious a11y issues`).toEqual([]);
});
}
})
});
test.describe('accessibility — member pages', () => {
test.describe("accessibility — member pages", () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(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')
await page.goto(path);
await page.waitForLoadState("networkidle");
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
.withTags(["wcag2a", "wcag2aa"])
.analyze();
const critical = results.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
)
(v) => v.impact === "critical" || v.impact === "serious",
);
expect(critical, `${name} has critical/serious a11y issues`).toEqual([])
})
expect(critical, `${name} has critical/serious a11y issues`).toEqual([]);
});
}
})
});
test.describe('accessibility — admin pages', () => {
test.describe("accessibility — admin pages", () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(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')
await page.goto(path);
await page.waitForLoadState("networkidle");
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
.withTags(["wcag2a", "wcag2aa"])
.analyze();
const critical = results.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
)
(v) => v.impact === "critical" || v.impact === "serious",
);
expect(critical, `${name} has critical/serious a11y issues`).toEqual([])
})
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')
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.locator("#join-name").focus();
expect(
await page
.locator("#join-name")
.evaluate((el) => el === document.activeElement),
).toBe(true);
await page.keyboard.press('Tab')
await page.keyboard.press("Tab");
// Email field should receive focus next
expect(await page.locator('#join-email').evaluate((el) => el === document.activeElement)).toBe(true)
})
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')
// Wait for login modal to appear
const modal = page.locator('text=Sign in to continue').or(page.locator('text=Sign in to your dashboard'))
await expect(modal.first()).toBeVisible({ timeout: 10000 })
test("escape closes login modal", async ({ page }) => {
await page.goto("/member/dashboard");
await page.keyboard.press('Escape')
// 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 })
})
})
await expect(modal.first()).not.toBeVisible({ timeout: 5000 });
});
});