From 2ec4d6664f5f5d4699c98ba286f57c16b00815ef Mon Sep 17 00:00:00 2001 From: Mariusz Banach Date: Wed, 18 Feb 2026 06:15:42 +0100 Subject: [PATCH] MAESTRO: add test selection e2e spec --- ...header-analyzer-Phase-10-Playwright-E2E.md | 2 +- frontend/e2e/test-selection.spec.ts | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 frontend/e2e/test-selection.spec.ts 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 c2c6f85..6c79514 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 @@ -71,7 +71,7 @@ All tasks in this phase are parallelizable [P] since they are independent E2E sp - [x] T054 Create Page Object Model in `frontend/e2e/pages/analyzer-page.ts` — encapsulates all page interactions: `pasteHeaders(text)`, `dropFile(path)`, `clickAnalyse()`, `pressCtrlEnter()`, `selectTests(ids)`, `deselectAll()`, `selectAll()`, `toggleDns()`, `toggleDecodeAll()`, `waitForResults()`, `getResultCards()`, `expandCard(index)`, `collapseCard(index)`, `searchReport(query)`, `exportJson()`, `exportHtml()`, `clearCache()`, `getCaptchaModal()`. Copy test fixture files to `frontend/e2e/fixtures/sample-headers.txt` and `frontend/e2e/fixtures/sample.eml` - [x] T055 [P] Create `frontend/e2e/paste-and-analyse.spec.ts` — test US1+US3+US4 primary flow: paste sample headers → click Analyse → verify progress indicator appears with test names → verify report renders with severity-coloured cards → expand/collapse a card → verify hop chain visualisation rendered. Also test Ctrl+Enter keyboard shortcut triggers analysis. Assert on UI effects of SSE progress (progress bar increments, test name updates) - [x] T056 [P] Create `frontend/e2e/file-drop.spec.ts` — test US1 file drop flow: dispatch synthetic DataTransfer+drop events on drop zone with `.eml` fixture → verify textarea auto-populates with header content → click Analyse → verify report renders. Test rejection of unsupported file types (e.g., `.pdf`) -- [ ] T057 [P] Create `frontend/e2e/test-selection.spec.ts` — test US2: open test selector → verify 106+ tests listed → click Deselect All → select 3 specific tests → analyse → verify only 3 results in report. Test search/filter narrows visible tests. Test DNS and decode-all toggle states persist through analysis +- [x] T057 [P] Create `frontend/e2e/test-selection.spec.ts` — test US2: open test selector → verify 106+ tests listed → click Deselect All → select 3 specific tests → analyse → verify only 3 results in report. Test search/filter narrows visible tests. Test DNS and decode-all toggle states persist through analysis - [ ] 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 - [ ] 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 - [ ] 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 diff --git a/frontend/e2e/test-selection.spec.ts b/frontend/e2e/test-selection.spec.ts new file mode 100644 index 0000000..c6b02a7 --- /dev/null +++ b/frontend/e2e/test-selection.spec.ts @@ -0,0 +1,109 @@ +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 extractSearchQuery = (label: string): string => { + const trimmed = label.trim(); + if (!trimmed) { + return ""; + } + const firstToken = trimmed.split(/\s+/)[0] ?? ""; + if (firstToken.length >= 4) { + return firstToken; + } + return trimmed.length > 4 ? trimmed.slice(0, 4) : trimmed; +}; + +test("test selection filters and limits analysis results", async ({ page }) => { + const headers = await fs.readFile(headersPath, "utf8"); + const analyzer = new AnalyzerPage(page); + + await analyzer.goto(); + + const checkboxLocator = page.locator('[data-testid^="test-checkbox-"]'); + await expect.poll(async () => checkboxLocator.count()).toBeGreaterThanOrEqual(106); + const totalCount = await checkboxLocator.count(); + expect(totalCount).toBeGreaterThanOrEqual(106); + + const firstCheckbox = checkboxLocator.first(); + await firstCheckbox.waitFor({ state: "visible" }); + const firstLabel = await firstCheckbox.getAttribute("aria-label"); + if (!firstLabel) { + throw new Error("Expected test checkbox to have an aria-label."); + } + + const searchInput = page.getByTestId("test-search-input"); + const searchQuery = extractSearchQuery(firstLabel); + if (!searchQuery) { + throw new Error("Unable to derive search query from test label."); + } + + await searchInput.fill(searchQuery); + await expect + .poll(async () => checkboxLocator.count()) + .toBeLessThan(totalCount); + const filteredCount = await checkboxLocator.count(); + expect(filteredCount).toBeGreaterThan(0); + + await searchInput.fill(""); + await expect.poll(async () => checkboxLocator.count()).toBe(totalCount); + + const resolveToggle = page.getByTestId("toggle-resolve"); + const decodeToggle = page.getByTestId("toggle-decode-all"); + + if ((await resolveToggle.getAttribute("aria-checked")) !== "true") { + await resolveToggle.click(); + } + if ((await decodeToggle.getAttribute("aria-checked")) !== "true") { + await decodeToggle.click(); + } + + await expect(resolveToggle).toHaveAttribute("aria-checked", "true"); + await expect(decodeToggle).toHaveAttribute("aria-checked", "true"); + + await analyzer.deselectAll(); + + const selectedIds = await checkboxLocator.evaluateAll((nodes) => { + const ids: number[] = []; + for (const node of nodes) { + if (ids.length >= 3) { + break; + } + const testId = node.getAttribute("data-testid") ?? ""; + const numeric = Number(testId.replace("test-checkbox-", "")); + if (Number.isFinite(numeric)) { + ids.push(numeric); + } + } + return ids; + }); + + if (selectedIds.length < 3) { + throw new Error("Expected at least three tests to select."); + } + + await analyzer.selectTests(selectedIds); + await analyzer.pasteHeaders(headers); + + const analysisResponse = page.waitForResponse( + (response) => response.url().includes("/api/analyse") && response.status() === 200, + ); + + await analyzer.clickAnalyse(); + await analysisResponse; + await analyzer.waitForResults(); + + const resultCards = analyzer.getResultCards(); + await expect(resultCards).toHaveCount(3, { timeout: 30000 }); + + for (const id of selectedIds) { + await expect(page.getByTestId(`test-result-card-${id}`)).toBeVisible(); + } + + await expect(resolveToggle).toHaveAttribute("aria-checked", "true"); + await expect(decodeToggle).toHaveAttribute("aria-checked", "true"); +});