MAESTRO: add accessibility e2e audit

This commit is contained in:
Mariusz Banach
2026-02-18 06:32:41 +01:00
parent 2c79e89e0a
commit 0001ff60bf
2 changed files with 79 additions and 1 deletions

View File

@@ -76,7 +76,7 @@ All tasks in this phase are parallelizable [P] since they are independent E2E sp
- [x] T059 [P] Create `frontend/e2e/browser-cache.spec.ts` — test US5: complete analysis → reload page → verify headers and results restored from cache → click Clear Cache → verify input and report cleared → reload → verify empty state
- [x] T060 [P] Create `frontend/e2e/rate-limiting.spec.ts` — test US6 rate limiting flow: submit requests until 429 response → verify CAPTCHA modal appears → solve CAPTCHA → verify bypass token stored → retry original request succeeds. Test that the CAPTCHA modal is keyboard accessible and visually correct
- [x] T061 [P] Create `frontend/e2e/visual-regression.spec.ts` — screenshot-based visual testing at 4 viewports (320×568, 768×1024, 1280×720, 2560×1080). Capture: landing page (empty state), landing page with headers pasted, progress indicator active, report view with results expanded, hop chain visualisation. Use `expect(page).toHaveScreenshot()` with `animations: 'disabled'` and `mask` for dynamic content (timestamps, elapsed time). Baselines stored in `frontend/e2e/__snapshots__/`
- [ ] T062 [P] Create `frontend/e2e/accessibility.spec.ts` — WCAG 2.1 AA audit using `@axe-core/playwright`. Run `AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze()` on: landing page (empty), landing page with input, report view, CAPTCHA modal (if rate-limited). Assert zero violations. Document any necessary exceptions with justification
- [x] T062 [P] Create `frontend/e2e/accessibility.spec.ts` — WCAG 2.1 AA audit using `@axe-core/playwright`. Run `AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze()` on: landing page (empty), landing page with input, report view, CAPTCHA modal (if rate-limited). Assert zero violations. Document any necessary exceptions with justification
- [ ] T063 [P] Create `frontend/e2e/responsive.spec.ts` — viewport matrix test at breakpoints 320px, 768px, 1024px, 1440px, 2560px. At each viewport: verify no horizontal scrollbar, all interactive elements visible and clickable, text readable (no overflow/clipping), report cards stack correctly on narrow viewports. Use `page.setViewportSize()` for per-test overrides
## Completion

View File

@@ -0,0 +1,78 @@
import { test, expect } from "@playwright/test";
import type { Page } from "@playwright/test";
import { AxeBuilder } from "@axe-core/playwright";
import fs from "fs/promises";
import path from "path";
import { AnalyzerPage } from "./pages/analyzer-page";
const headersPath = path.resolve(__dirname, "fixtures/sample-headers.txt");
const axeTags = ["wcag2a", "wcag2aa", "wcag21aa"];
const runAxeAudit = async (page: Page) => {
const results = await new AxeBuilder({ page }).withTags(axeTags).analyze();
expect(results.violations).toEqual([]);
};
test("accessibility audit covers landing page states", async ({ page }) => {
const headers = await fs.readFile(headersPath, "utf8");
const analyzer = new AnalyzerPage(page);
await analyzer.goto();
await expect(page.getByRole("textbox", { name: "Header Input" })).toBeVisible();
await runAxeAudit(page);
await analyzer.pasteHeaders(headers);
await runAxeAudit(page);
});
test("accessibility audit covers report view", async ({ page }) => {
const headers = await fs.readFile(headersPath, "utf8");
const analyzer = new AnalyzerPage(page);
await analyzer.goto();
await analyzer.pasteHeaders(headers);
await analyzer.clickAnalyse();
await analyzer.waitForResults();
await runAxeAudit(page);
});
test("accessibility audit covers captcha modal", async ({ page }) => {
const headers = await fs.readFile(headersPath, "utf8");
const analyzer = new AnalyzerPage(page);
const captchaChallenge = {
challengeToken: "playwright-challenge-token",
imageBase64:
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFgwJ/l0pNqQAAAABJRU5ErkJggg==",
};
const corsHeaders = {
"access-control-allow-origin": "http://localhost:3100",
"access-control-allow-credentials": "true",
};
await page.route("**/api/analyse", async (route) => {
await route.fulfill({
status: 429,
contentType: "application/json",
headers: {
...corsHeaders,
"retry-after": "60",
},
body: JSON.stringify({
error: "Too many requests",
retryAfter: 60,
captchaChallenge,
}),
});
});
await analyzer.goto();
await analyzer.pasteHeaders(headers);
await analyzer.clickAnalyse();
await expect(analyzer.getCaptchaModal()).toBeVisible();
await runAxeAudit(page);
});