first commit
This commit is contained in:
146
e2e/tests/accessibility.spec.ts
Normal file
146
e2e/tests/accessibility.spec.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Accessibility E2E Tests
|
||||
*
|
||||
* Automated WCAG 2.1 AA audit using axe-core.
|
||||
* Tests for critical and high-priority accessibility issues across admin pages.
|
||||
*/
|
||||
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
|
||||
import { test, expect } from "../fixtures";
|
||||
|
||||
// Regex patterns for URL assertions (anchored to prevent false matches)
|
||||
const ADMIN_ROOT_URL = /\/_emdash\/admin\/?(?:[?#].*)?$/;
|
||||
const CONTENT_POSTS_URL = /\/content\/posts\/?(?:[?#].*)?$/;
|
||||
const CONTENT_POSTS_NEW_URL = /\/content\/posts\/new\/?(?:[?#].*)?$/;
|
||||
const MEDIA_URL = /\/media\/?(?:[?#].*)?$/;
|
||||
const USERS_URL = /\/users\/?(?:[?#].*)?$/;
|
||||
const SETTINGS_URL = /\/settings\/?(?:[?#].*)?$/;
|
||||
|
||||
// Known a11y violations from upstream dependencies:
|
||||
// - color-contrast: kumo design system colors on white backgrounds (needs upstream fix)
|
||||
// - aria-valid-attr-value: Base UI's Collapsible sets aria-controls on triggers pointing
|
||||
// to panel IDs that may not be in the DOM when collapsed (kumo Sidebar collapsible groups)
|
||||
const KNOWN_A11Y_EXCLUSIONS = ["color-contrast", "aria-valid-attr-value"];
|
||||
|
||||
test.describe("Accessibility Audit", () => {
|
||||
test.describe("Login Page", () => {
|
||||
test("should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goto("/login");
|
||||
|
||||
// Wait for stable content — admin pages need Astro compilation on first hit
|
||||
await expect(admin.page.locator("h1")).toContainText("Sign in", { timeout: 15000 });
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Authenticated Pages", () => {
|
||||
test.beforeEach(async ({ admin }) => {
|
||||
await admin.devBypassAuth();
|
||||
});
|
||||
|
||||
test("dashboard should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goToDashboard();
|
||||
await admin.waitForLoading();
|
||||
await expect(admin.page).toHaveURL(ADMIN_ROOT_URL);
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("content list should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goToContent("posts");
|
||||
await admin.waitForLoading();
|
||||
await expect(admin.page).toHaveURL(CONTENT_POSTS_URL);
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("content editor should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goToNewContent("posts");
|
||||
await admin.waitForLoading();
|
||||
await expect(admin.page).toHaveURL(CONTENT_POSTS_NEW_URL);
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.exclude(".ProseMirror") // Rich text editor has complex a11y needs
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("media library should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goToMedia();
|
||||
await admin.waitForLoading();
|
||||
await expect(admin.page).toHaveURL(MEDIA_URL);
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("users page should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goto("/users");
|
||||
await admin.waitForShell();
|
||||
await admin.waitForLoading();
|
||||
await expect(admin.page).toHaveURL(USERS_URL);
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("settings page should have no WCAG 2.x AA violations", async ({ admin }) => {
|
||||
await admin.goToSettings();
|
||||
await admin.waitForLoading();
|
||||
await expect(admin.page).toHaveURL(SETTINGS_URL);
|
||||
|
||||
const results = await new AxeBuilder({ page: admin.page })
|
||||
.withTags(["wcag2a", "wcag2aa", "wcag21aa"])
|
||||
.disableRules(KNOWN_A11Y_EXCLUSIONS)
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("content list should be keyboard navigable", async ({ admin }) => {
|
||||
await admin.goToContent("posts");
|
||||
await admin.waitForLoading();
|
||||
|
||||
// Tab through key interactive elements
|
||||
await admin.page.keyboard.press("Tab");
|
||||
|
||||
const focusedElements: string[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const focused = await admin.page.evaluate(() => document.activeElement?.tagName || "");
|
||||
focusedElements.push(focused);
|
||||
await admin.page.keyboard.press("Tab");
|
||||
}
|
||||
|
||||
// Should have found interactive elements (buttons, links)
|
||||
expect(focusedElements.some((el) => el === "BUTTON" || el === "A")).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user