From 76b04e31a0d2fae5a085fa1b626cfe6607887561 Mon Sep 17 00:00:00 2001 From: Mariusz Banach Date: Wed, 18 Feb 2026 06:09:11 +0100 Subject: [PATCH] MAESTRO: add paste-and-analyse Playwright spec --- ...header-analyzer-Phase-10-Playwright-E2E.md | 2 +- frontend/e2e/paste-and-analyse.spec.ts | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 frontend/e2e/paste-and-analyse.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 d766118..19ff1bc 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 @@ -69,7 +69,7 @@ frontend/e2e/ All tasks in this phase are parallelizable [P] since they are independent E2E spec files. - [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` -- [ ] 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] 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) - [ ] 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 - [ ] 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 diff --git a/frontend/e2e/paste-and-analyse.spec.ts b/frontend/e2e/paste-and-analyse.spec.ts new file mode 100644 index 0000000..4a990fe --- /dev/null +++ b/frontend/e2e/paste-and-analyse.spec.ts @@ -0,0 +1,96 @@ +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 parsePercentage = (value: string | null): number => { + if (!value) { + return 0; + } + const numeric = Number(value.replace("%", "").trim()); + return Number.isFinite(numeric) ? numeric : 0; +}; + +test("paste headers and analyse renders progress and report", async ({ page }) => { + const headers = await fs.readFile(headersPath, "utf8"); + const analyzer = new AnalyzerPage(page); + + await analyzer.goto(); + await analyzer.pasteHeaders(headers); + await analyzer.clickAnalyse(); + + const progressIndicator = page.getByTestId("progress-indicator"); + await expect(progressIndicator).toBeVisible({ timeout: 30000 }); + + const currentTest = page.getByTestId("progress-current-test"); + await expect(currentTest).toBeVisible(); + await expect(currentTest).not.toHaveText(/^\s*$/); + await expect(currentTest).not.toHaveText(/Preparing analysis/i); + + const firstTestName = (await currentTest.textContent())?.trim() ?? ""; + await page.waitForFunction( + ({ testId, previous }) => { + const node = document.querySelector(`[data-testid="${testId}"]`); + if (!node) { + return false; + } + const nextValue = (node.textContent ?? "").trim(); + return nextValue.length > 0 && nextValue !== previous; + }, + { testId: "progress-current-test", previous: firstTestName }, + ); + + const progressPercentage = page.getByTestId("progress-percentage"); + const initialPercentage = parsePercentage(await progressPercentage.textContent()); + await page.waitForFunction( + ({ testId, initial }) => { + const node = document.querySelector(`[data-testid="${testId}"]`); + if (!node) { + return false; + } + const value = Number((node.textContent ?? "").replace("%", "").trim()); + return Number.isFinite(value) && value > initial; + }, + { testId: "progress-percentage", initial: initialPercentage }, + ); + + await analyzer.waitForResults(); + + const resultCards = analyzer.getResultCards(); + await expect(resultCards.first()).toBeVisible(); + + const severityBadge = page.locator('[data-testid^="test-result-severity-"]').first(); + await expect(severityBadge).toBeVisible(); + const severityClass = await severityBadge.getAttribute("class"); + expect(severityClass ?? "").toMatch(/text-(spam|suspicious|clean|accent)/); + + const firstToggle = resultCards.first().locator('[data-testid^="test-result-toggle-"]'); + await expect(firstToggle).toHaveAttribute("aria-expanded", "false"); + await analyzer.expandCard(0); + await expect(firstToggle).toHaveAttribute("aria-expanded", "true"); + await analyzer.collapseCard(0); + await expect(firstToggle).toHaveAttribute("aria-expanded", "false"); + + await expect(page.getByTestId("hop-chain-visualisation")).toBeVisible(); +}); + +test("Ctrl+Enter triggers analysis", async ({ page }) => { + const headers = await fs.readFile(headersPath, "utf8"); + const analyzer = new AnalyzerPage(page); + + await analyzer.goto(); + await analyzer.pasteHeaders(headers); + + const analysisResponse = page.waitForResponse( + (response) => response.url().includes("/api/analyse") && response.status() === 200, + ); + + await analyzer.pressCtrlEnter(); + await analysisResponse; + await analyzer.waitForResults(); + + await expect(page.getByTestId("report-container")).toBeVisible(); +});