diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-10-Playwright-E2E.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-10-Playwright-E2E.md index fbf4991..ff3b9fa 100644 --- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-10-Playwright-E2E.md +++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-10-Playwright-E2E.md @@ -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 diff --git a/frontend/e2e/accessibility.spec.ts b/frontend/e2e/accessibility.spec.ts new file mode 100644 index 0000000..56f77f5 --- /dev/null +++ b/frontend/e2e/accessibility.spec.ts @@ -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); +});