MAESTRO: add visual regression e2e snapshots

This commit is contained in:
Mariusz Banach
2026-02-18 06:29:48 +01:00
parent c58c860d3b
commit 2c79e89e0a
2 changed files with 96 additions and 1 deletions

View File

@@ -75,7 +75,7 @@ All tasks in this phase are parallelizable [P] since they are independent E2E sp
- [x] T058 [P] Create `frontend/e2e/report-interaction.spec.ts` — test US4 report features: expand all cards → collapse all → search for a term → verify filtered results → clear search → export JSON → verify downloaded file is valid JSON. Export HTML → verify downloaded file contains styled content - [x] T058 [P] Create `frontend/e2e/report-interaction.spec.ts` — test US4 report features: expand all cards → collapse all → search for a term → verify filtered results → clear search → export JSON → verify downloaded file is valid JSON. Export HTML → verify downloaded file contains styled content
- [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] 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] 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
- [ ] 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__/` - [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 - [ ] 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 - [ ] 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

View File

@@ -0,0 +1,95 @@
import { test, expect } from "@playwright/test";
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 viewports = [
{ width: 320, height: 568, label: "320x568" },
{ width: 768, height: 1024, label: "768x1024" },
{ width: 1280, height: 720, label: "1280x720" },
{ width: 2560, height: 1080, label: "2560x1080" },
];
const baseScreenshotOptions = {
animations: "disabled" as const,
fullPage: true,
};
test.describe("visual regression snapshots", () => {
for (const viewport of viewports) {
test(`visual baselines at ${viewport.label}`, async ({ page }) => {
const headers = await fs.readFile(headersPath, "utf8");
const analyzer = new AnalyzerPage(page);
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await analyzer.goto();
const headerInput = page.getByRole("textbox", { name: "Header Input" });
await headerInput.waitFor({ state: "visible" });
await page.evaluate(() => window.scrollTo(0, 0));
await expect(page).toHaveScreenshot(
`landing-empty-${viewport.label}.png`,
baseScreenshotOptions,
);
await analyzer.pasteHeaders(headers);
await page.evaluate(() => window.scrollTo(0, 0));
await expect(page).toHaveScreenshot(
`landing-filled-${viewport.label}.png`,
baseScreenshotOptions,
);
await analyzer.clickAnalyse();
const progressIndicator = page.getByTestId("progress-indicator");
await expect(progressIndicator).toBeVisible({ timeout: 30000 });
await page.waitForFunction(() => {
const node = document.querySelector('[data-testid="progress-percentage"]');
if (!node) {
return false;
}
const value = Number((node.textContent ?? "").replace("%", "").trim());
return Number.isFinite(value) && value >= 1;
});
await page.evaluate(() => window.scrollTo(0, 0));
await expect(page).toHaveScreenshot(`progress-${viewport.label}.png`, {
...baseScreenshotOptions,
mask: [
page.getByTestId("progress-elapsed"),
page.getByTestId("progress-remaining"),
page.getByTestId("progress-percentage"),
page.getByTestId("progress-current-test"),
],
});
await analyzer.waitForResults();
const resultCards = analyzer.getResultCards();
const totalCards = await resultCards.count();
const expandCount = Math.min(totalCards, 3);
for (let index = 0; index < expandCount; index += 1) {
await analyzer.expandCard(index);
}
await page.waitForTimeout(200);
await page.evaluate(() => window.scrollTo(0, 0));
await expect(page).toHaveScreenshot(
`report-expanded-${viewport.label}.png`,
baseScreenshotOptions,
);
const hopChain = page.getByTestId("hop-chain-visualisation");
await hopChain.scrollIntoViewIfNeeded();
await expect(hopChain).toBeVisible();
await expect(hopChain).toHaveScreenshot(`hop-chain-${viewport.label}.png`, {
animations: "disabled",
});
});
}
});