/// import { expect, test, type Page } from "@playwright/test"; import { readFile } from "node:fs/promises"; import { unzipSync } from "fflate"; async function setEditor(page: Page, testId: string, value: string) { const content = page.getByTestId(testId).locator(".cm-content"); await content.fill(value); } async function waitForReady(page: Page) { await expect(page.locator(".run-status")).toHaveClass(/status-ready/u, { timeout: 15_000, }); } async function attemptRunWhileDisabled(page: Page) { return page.evaluate(() => { const button = document.querySelector(".run-button"); if (!button) throw new Error("Run button missing"); if (!button.disabled) return false; button.click(); return true; }); } test("uses the shared 90rem content width without a browser body frame", async ({ page, }) => { await page.setViewportSize({ width: 1800, height: 900 }); await page.goto("./"); await expect(page.locator(".regex-application")).toBeVisible(); const layout = await page.evaluate(() => { const shellMain = document.querySelector( ".toolbox-shell__main", ); const application = document.querySelector(".regex-application"); if (!shellMain || !application) throw new Error("Application shell missing"); const shellMainStyle = getComputedStyle(shellMain); return { bodyMargin: getComputedStyle(document.body).margin, shellMainWidth: shellMain.getBoundingClientRect().width, shellMainContentWidth: shellMain.clientWidth - Number.parseFloat(shellMainStyle.paddingLeft) - Number.parseFloat(shellMainStyle.paddingRight), applicationWidth: application.getBoundingClientRect().width, }; }); expect(layout.bodyMargin).toBe("0px"); expect(layout.shellMainWidth).toBe(1440); expect(layout.applicationWidth).toBe(layout.shellMainContentWidth); }); test("standalone ECMAScript workbench parses, explains and extracts", async ({ page, }) => { const pageErrors: string[] = []; page.on("pageerror", (error) => pageErrors.push(error.message)); await page.goto("./"); await expect( page.getByRole("heading", { name: "Regex Tools" }), ).toBeVisible(); await waitForReady(page); await expect( page.getByRole("heading", { name: "Explanation tree" }), ).toBeVisible(); await expect( page.getByRole("heading", { name: "Extraction tree" }), ).toBeVisible(); await expect(page.locator(".extraction-row")).toHaveCount(8); await expect(page.getByText("alice", { exact: true }).first()).toBeVisible(); await expect( page.getByText("Bérénice", { exact: true }).first(), ).toBeVisible(); await expect( page.getByRole("columnheader", { name: "Native start" }), ).toBeVisible(); await expect( page.getByRole("checkbox", { name: "Global (g)" }), ).toBeChecked(); await expect(page.locator(".cm-capture-1").first()).toHaveAttribute( "title", /group 1 · date/u, ); await expect( page .locator('[aria-live="polite"]') .filter({ hasText: "Completed locally" }), ).toHaveCount(1); expect(pageErrors).toEqual([]); }); test("PCRE2 WebAssembly executes flavour-only syntax and native substitution", async ({ page, }) => { const pageErrors: string[] = []; page.on("pageerror", (error) => pageErrors.push(error.message)); await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2"); await expect( page.getByRole("spinbutton", { name: "Match steps" }), ).toHaveValue("1000000"); await setEditor( page, "editor-regular-expression-pattern", "(?|(a)|(b))-(?\\d+)", ); await setEditor(page, "editor-test-text", "b-42"); const run = page.getByRole("button", { name: "Run", exact: true }); await expect(run).toBeEnabled(); await run.click(); await waitForReady(page); await expect(page.locator(".run-status")).toContainText( "native offsets utf8-byte", ); await expect(page.locator(".extraction-row").first()).toContainText("b-42"); await expect(page.getByText("42", { exact: true }).first()).toBeVisible(); await page.getByRole("button", { name: "Replace", exact: true }).click(); await setEditor(page, "editor-replacement-template", "${number}:$1"); await expect(run).toBeEnabled(); await run.click(); await waitForReady(page); await expect( page.getByTestId("editor-replacement-output").locator(".cm-content"), ).toHaveText("42:b"); await page.getByRole("button", { name: "Quick reference" }).click(); await expect( page.getByRole("dialog", { name: "Quick reference" }), ).toContainText("Branch-reset group"); await page.keyboard.press("Escape"); await page.getByRole("button", { name: "Capabilities" }).click(); const capabilities = page.getByRole("dialog", { name: "Capabilities" }); await expect(capabilities).toContainText("PCRE2 WebAssembly"); await expect(capabilities).toContainText("10.47 2025-10-21"); expect(pageErrors).toEqual([]); }); test("PCRE2 trace viewer reports bounded automatic callouts in its own worker", async ({ page, }) => { const pageErrors: string[] = []; page.on("pageerror", (error) => pageErrors.push(error.message)); await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2"); await setEditor(page, "editor-regular-expression-pattern", "(a+)+$"); await setEditor(page, "editor-test-text", "aaaa!"); await page.getByRole("button", { name: "PCRE trace" }).click(); const dialog = page.getByRole("dialog", { name: "Execution trace" }); await expect(dialog).toBeVisible(); await expect(dialog).toContainText("reported automatic callouts"); await dialog.getByRole("spinbutton", { name: "Maximum events" }).fill("1"); await dialog .getByRole("spinbutton", { name: "Maximum serialized bytes" }) .fill("4096"); await dialog.getByRole("button", { name: "Run trace" }).click(); await expect(dialog.locator(".trace-status")).toHaveClass(/status-ready/u, { timeout: 15_000, }); await expect(dialog.locator(".trace-table tbody tr")).toHaveCount(1); await expect(dialog).toContainText("native status -37"); await expect(dialog).toContainText("one exact pcre2_match() invocation"); await expect(dialog).toContainText("configured event or serialized-byte cap"); await dialog.getByRole("button", { name: "Select trace event 1" }).click(); await expect(dialog).not.toBeVisible(); expect(pageErrors).toEqual([]); }); test("ECMAScript analysis runs bounded cold and warm samples outside trace mode", async ({ page, }) => { const pageErrors: string[] = []; page.on("pageerror", (error) => pageErrors.push(error.message)); await page.goto("./"); await waitForReady(page); await page.getByRole("button", { name: "Analysis" }).click(); const dialog = page.getByRole("dialog", { name: "Performance & risk analysis", }); await expect(dialog).toBeVisible(); await expect(dialog).toContainText("Potential risk findings"); await dialog.getByRole("tab", { name: "Benchmark" }).click(); await expect(dialog).toContainText("Trace mode is never used"); await dialog.getByRole("spinbutton", { name: "Warm-up samples" }).fill("0"); await dialog.getByRole("spinbutton", { name: "Measured samples" }).fill("2"); await dialog.getByRole("button", { name: "Run bounded benchmark" }).click(); await expect( dialog.getByRole("heading", { name: "Benchmark result" }), ).toBeVisible({ timeout: 15_000 }); await expect(dialog).toContainText("2 measured warm samples"); await expect(dialog).toContainText("Native ECMAScript RegExp"); expect(pageErrors).toEqual([]); }); test("serves the bundled PCRE2 runtime with WebAssembly MIME", async ({ request, }) => { const moduleResponse = await request.get("./engines/pcre2/pcre2.mjs"); expect(moduleResponse.ok()).toBe(true); expect(moduleResponse.headers()["content-type"]).toContain("text/javascript"); const response = await request.get("./engines/pcre2/pcre2.wasm"); expect(response.ok()).toBe(true); expect(response.headers()["content-type"]).toContain("application/wasm"); expect((await response.body()).subarray(0, 4)).toEqual( Buffer.from([0x00, 0x61, 0x73, 0x6d]), ); }); test("quick reference and capabilities open as populated in-viewport dialogs", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page .getByRole("button", { name: "Quick reference", exact: true }) .click(); const referenceDialog = page.getByRole("dialog", { name: "Quick reference", }); await expect(referenceDialog).toBeVisible(); await expect(referenceDialog).toBeInViewport(); await expect( referenceDialog.getByRole("button", { name: "Close quick reference" }), ).toBeFocused(); await expect( referenceDialog.getByRole("listitem").filter({ hasText: "Literal" }), ).toBeVisible(); await referenceDialog .getByLabel("Filter constructs") .fill("not a reference entry"); await expect(referenceDialog.getByRole("status")).toContainText( "No reference constructs match", ); await page.keyboard.press("Escape"); await expect(referenceDialog).not.toBeVisible(); await expect( page.getByRole("button", { name: "Quick reference", exact: true }), ).toHaveAttribute("aria-expanded", "false"); await page.getByRole("button", { name: "Capabilities", exact: true }).click(); const capabilitiesDialog = page.getByRole("dialog", { name: "Capabilities", }); await expect(capabilitiesDialog).toBeVisible(); await expect(capabilitiesDialog).toBeInViewport(); await expect( capabilitiesDialog.getByRole("button", { name: "Close capabilities" }), ).toBeFocused(); await expect(capabilitiesDialog).toContainText("regexpp-community 4.12.2"); await expect(capabilitiesDialog).toContainText("Native ECMAScript RegExp"); await capabilitiesDialog .getByRole("button", { name: "Close capabilities" }) .click(); await expect(capabilitiesDialog).not.toBeVisible(); }); test("corpus mode scans local text, applies exact replacements, and exports safely", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page .getByRole("button", { name: "Corpus / Apply", exact: true }) .click(); await expect( page.getByRole("heading", { name: "Corpus documents" }), ).toBeVisible(); await expect(page.getByTestId("editor-test-text")).not.toBeVisible(); await page .getByRole("textbox", { name: "Pasted corpus text" }) .fill("2026-07-24 alice\nnot a date"); await page.getByRole("button", { name: "Add pasted text" }).click(); await page.getByLabel("Add corpus text files").setInputFiles([ { name: "second.txt", mimeType: "text/plain", buffer: Buffer.from("2025-12-31 Bérénice", "utf8"), }, { name: "empty.txt", mimeType: "text/plain", buffer: Buffer.from("no date here", "utf8"), }, ]); await expect( page.getByRole("list", { name: "Corpus documents" }).getByRole("listitem"), ).toHaveCount(3); await page.getByRole("button", { name: "Scan corpus" }).click(); const results = page.getByRole("table", { name: "Corpus document results", }); await expect(results).toBeVisible(); await expect(results.locator("tbody tr")).toHaveCount(3); await expect(results.locator("tbody tr").nth(0)).toContainText("complete"); await expect(results.locator("tbody tr").nth(0)).toContainText("1"); await expect(results.locator("tbody tr").nth(2)).toContainText("0"); await expect( page.getByRole("table", { name: "Capture summaries for pasted-text.txt", }), ).toContainText("alice"); const reportDownload = page.waitForEvent("download"); await page.getByRole("button", { name: "Download summary JSON" }).click(); const report = await reportDownload; expect(report.suggestedFilename()).toBe("regex-corpus-report.json"); const reportPath = await report.path(); if (!reportPath) throw new Error("Corpus report download unavailable"); const reportText = await readFile(reportPath, "utf8"); expect(reportText).not.toContain("2026-07-24 alice"); expect(JSON.parse(reportText)).toMatchObject({ schemaVersion: 1, operation: "match", status: "complete", summary: { totalDocuments: 3, totalMatches: 2 }, }); await page.getByRole("radio", { name: /Apply replacement/u }).check(); await page.getByRole("button", { name: "Apply to corpus" }).click(); await expect(results.locator("tbody tr")).toHaveCount(3); await expect(results.locator("tbody tr").first()).toContainText("complete"); const contentConfirmation = page.getByRole("checkbox", { name: "I understand applied downloads contain corpus content", }); await expect( page.getByRole("button", { name: "Download applied ZIP" }), ).toBeDisabled(); await contentConfirmation.check(); await expect( page.getByRole("heading", { name: "Replacement preview" }), ).toBeVisible(); await expect(page.locator(".corpus-output-preview pre")).toContainText( "alice — 2026-07-24", ); const zipDownload = page.waitForEvent("download"); await page.getByRole("button", { name: "Download applied ZIP" }).click(); const archive = await zipDownload; expect(archive.suggestedFilename()).toBe("regex-corpus-applied.zip"); const archivePath = await archive.path(); if (!archivePath) throw new Error("Corpus ZIP download unavailable"); const files = unzipSync(await readFile(archivePath)); expect(Object.keys(files).sort()).toEqual([ "empty.replaced.txt", "pasted-text.replaced.txt", "second.replaced.txt", ]); expect(new TextDecoder().decode(files["pasted-text.replaced.txt"])).toContain( "alice — 2026-07-24", ); await page.getByRole("button", { name: "Project", exact: true }).click(); const projectDownload = page.waitForEvent("download"); await page .locator(".project-popover") .getByRole("button", { name: "Export JSON" }) .click(); const projectFile = await projectDownload; const projectPath = await projectFile.path(); if (!projectPath) throw new Error("Project download unavailable"); const projectText = await readFile(projectPath, "utf8"); expect(projectText).not.toContain("2026-07-24 alice"); expect(JSON.parse(projectText)).toMatchObject({ mode: "corpus" }); }); test("corpus independent-line mode has explicit anchor semantics and preserves separators", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-regular-expression-pattern", "^.+$"); await page .getByRole("button", { name: "Corpus / Apply", exact: true }) .click(); await page .getByRole("textbox", { name: "Pasted corpus text" }) .fill("alpha\r\nbeta"); await page.getByRole("button", { name: "Add pasted text" }).click(); await expect(page.getByRole("button", { name: "Scan corpus" })).toBeEnabled(); await page.getByRole("button", { name: "Scan corpus" }).click(); const resultRow = page .getByRole("table", { name: "Corpus document results" }) .locator("tbody tr") .first(); await expect(resultRow.locator("td").nth(1)).toHaveText("0"); await page.getByRole("radio", { name: /Independent lines/u }).check(); await expect(page.getByText(/Anchors see one line at a time/u)).toBeVisible(); await page.getByRole("button", { name: "Scan corpus" }).click(); await expect(resultRow.locator("td").nth(1)).toHaveText("2"); await page.getByRole("radio", { name: /Apply replacement/u }).check(); await page .getByRole("textbox", { name: "Replacement template" }) .fill("[$&]"); await page.getByRole("button", { name: "Apply to corpus" }).click(); await expect(page.locator(".corpus-output-preview pre")).toContainText( "[alpha]", ); await expect(page.locator(".corpus-output-preview pre")).toContainText( "[beta]", ); expect(await page.locator(".corpus-output-preview pre").textContent()).toBe( "[alpha]\n[beta]", ); }); test("a running corpus job can be cancelled and labels unprocessed output", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$"); await page.getByLabel("Manual limit").selectOption("10000"); await page .getByRole("button", { name: "Corpus / Apply", exact: true }) .click(); await page .getByRole("textbox", { name: "Pasted corpus text" }) .fill(`${"a".repeat(48)}!`); await page.getByRole("button", { name: "Add pasted text" }).click(); await expect(page.getByRole("button", { name: "Scan corpus" })).toBeEnabled(); await page.getByRole("button", { name: "Scan corpus" }).click(); await page.getByRole("button", { name: "Cancel corpus run" }).click(); await expect( page.getByRole("table", { name: "Corpus document results" }), ).toContainText("cancelled", { timeout: 15_000 }); await expect( page.getByRole("button", { name: "Download summary JSON" }), ).toBeEnabled(); }); test("editing reports malformed syntax and recovers with exact matches", async ({ page, }) => { await page.goto("./"); await waitForReady(page); const patternEditor = page .getByTestId("editor-regular-expression-pattern") .locator(".cm-content"); const originalPattern = await patternEditor.textContent(); await patternEditor.click(); await patternEditor.press("End"); await patternEditor.press("x"); await expect(patternEditor).toHaveText(`${originalPattern}x`); await setEditor(page, "editor-regular-expression-pattern", "("); await expect( page .locator(".diagnostic-error") .filter({ hasText: /unterminated|invalid/iu }), ).toBeVisible(); await expect(page.locator(".run-status")).toContainText( "Execution is paused", ); await setEditor(page, "editor-regular-expression-pattern", "(?\\w+)"); await setEditor(page, "editor-test-text", "hello world"); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect(page.locator(".extraction-row").first()).toContainText("hello"); }); test("rapid pattern and replacement edits cannot run with stale syntax", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); const initialExtractionCount = await page.locator(".extraction-row").count(); await page.getByRole("button", { name: "Replace", exact: true }).click(); await setEditor(page, "editor-regular-expression-pattern", "(?\\w+)"); await setEditor(page, "editor-replacement-template", "$"); await setEditor(page, "editor-replacement-template", "[$]"); await setEditor(page, "editor-test-text", "A B"); await setEditor( page, "editor-regular-expression-pattern", "(?[A-Z])", ); const runButton = page.getByRole("button", { name: "Run", exact: true }); expect(await attemptRunWhileDisabled(page)).toBe(true); await expect(page.locator(".run-status")).toHaveClass(/status-idle/u); await expect(page.locator(".run-status")).toContainText( "last completed result remains visible", ); await expect(page.locator(".extraction-row")).toHaveCount( initialExtractionCount, ); await expect(page.getByTestId("stale-result-frame")).toBeVisible(); await expect(runButton).toBeEnabled(); const currentNamedToken = page.locator(".token-row").filter({ has: page.locator("code", { hasText: "$" }), }); await expect(currentNamedToken.locator("code")).toHaveText("$"); await expect(currentNamedToken.locator("small")).toHaveText("named capture"); await page.getByRole("checkbox", { name: "Ignore case (i)" }).check(); expect(await attemptRunWhileDisabled(page)).toBe(true); await expect(page.locator(".run-status")).toHaveClass(/status-idle/u); await expect(runButton).toBeEnabled(); await runButton.click(); await waitForReady(page); await expect( page.getByTestId("editor-replacement-output").locator(".cm-content"), ).toHaveText("[A] [B]"); }); test("manual editing retains an explicitly stale result without applying its ranges", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); const extractionRows = page.locator(".extraction-row"); const captureRows = page.locator(".capture-table-scroll tbody tr"); const initialExtractionCount = await extractionRows.count(); const initialCaptureCount = await captureRows.count(); expect(initialExtractionCount).toBeGreaterThan(0); expect(initialCaptureCount).toBeGreaterThan(0); await setEditor(page, "editor-regular-expression-pattern", "nomatch"); await expect(page.locator(".run-status")).toHaveClass(/status-idle/u); await expect(page.locator(".run-status")).toContainText( "last completed result remains visible until the current inputs complete", ); await expect(page.getByTestId("stale-result-frame")).toContainText( "no ranges are applied to current editors", ); await expect(extractionRows).toHaveCount(initialExtractionCount); await expect(captureRows).toHaveCount(initialCaptureCount); await expect(page.locator(".cm-subject-match")).toHaveCount(0); }); test("capture-group resource limits block execution visibly", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); const initialExtractionCount = await page.locator(".extraction-row").count(); const initialCaptureCount = await page .locator(".capture-table-scroll tbody tr") .count(); await setEditor( page, "editor-regular-expression-pattern", "()".repeat(1_001), ); await expect(page.locator(".run-status")).toHaveClass(/status-error/u); await expect(page.locator(".run-status")).toContainText( "above the 1,000 group execution limit", ); await expect(page.getByTestId("stale-result-frame")).toBeVisible(); await expect(page.locator(".extraction-row")).toHaveCount( initialExtractionCount, ); await expect(page.locator(".capture-table-scroll tbody tr")).toHaveCount( initialCaptureCount, ); await expect(page.locator(".cm-subject-match")).toHaveCount(0); }); test("large valid patterns bound syntax tree nodes and editor decorations", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-regular-expression-pattern", "a".repeat(2_100)); await expect(page.getByTestId("pattern-mark-render-limit")).toBeVisible(); await expect(page.getByTestId("explanation-render-limit")).toContainText( "2,000", ); await expect( page .getByRole("tree", { name: "Pattern explanation" }) .getByRole("treeitem"), ).toHaveCount(2_000); }); test("the pattern editor rejects input above its hard resource limit", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); const editor = page .getByTestId("editor-regular-expression-pattern") .locator(".cm-content"); const previousPattern = await editor.textContent(); await editor.fill("a".repeat(64 * 1_024 + 1)); await expect(page.getByRole("alert")).toContainText( "limited to 65,536 UTF-16 units", ); await expect(editor).toHaveText(previousPattern ?? ""); }); test("large match sets bound tree nodes and editor highlights explicitly", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-regular-expression-pattern", "a"); await setEditor(page, "editor-test-text", "a".repeat(10_001)); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect(page.locator(".run-status")).toContainText("10,000 matches"); await expect(page.locator(".extraction-row")).toHaveCount(2_000); await expect(page.getByTestId("extraction-render-limit")).toContainText( "first 2,000 of 20,000", ); await expect(page.getByTestId("subject-mark-render-limit")).toContainText( "first 2,000 of 10,000", ); await expect( page.locator(".diagnostic").filter({ hasText: "configured limit" }), ).toBeVisible(); }); test("list export stays disabled when the engine returned a limited prefix", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await page.getByRole("button", { name: "List", exact: true }).click(); await setEditor(page, "editor-regular-expression-pattern", "a"); await setEditor(page, "editor-test-text", "a".repeat(10_001)); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect(page.getByText(/engine limit reached/u)).toBeVisible(); await expect( page.getByText(/engine stopped collecting matches/u), ).toBeVisible(); await expect( page.getByRole("button", { name: "Download TEXT" }), ).toBeDisabled(); }); test("subject caret selection identifies a capture without replacing it", async ({ page, }) => { await page.goto("./"); await waitForReady(page); const subjectEditor = page .getByTestId("editor-test-text") .locator(".cm-content"); const originalSubject = await subjectEditor.textContent(); await subjectEditor.click(); await subjectEditor.press("Control+Home"); await expect( page .getByRole("tree", { name: "Match extraction" }) .locator(".tree-row.is-selected"), ).toContainText("Group 1 — date"); await expect( page .getByRole("grid", { name: "Captured matches" }) .locator("tbody tr") .first(), ).toHaveAttribute("aria-selected", "true"); expect( await subjectEditor.evaluate((editor) => { const selection = globalThis.getSelection(); return ( selection?.isCollapsed === true && editor.contains(selection.anchorNode) ); }), ).toBe(true); await subjectEditor.press("x"); await expect(subjectEditor).toHaveText(`x${originalSubject}`); }); test("test-text whitespace and caret position are visible on request", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-test-text", "a b\ncd"); await expect(page.getByText("Ln 2, Col 3", { exact: true })).toBeVisible(); await page.getByRole("checkbox", { name: "Visible whitespace" }).check(); await expect( page.getByTestId("editor-test-text").locator(".cm-highlightSpace"), ).toHaveCount(1); }); test("capture-row selection remains synchronized after editor highlighting", async ({ page, }) => { await page.goto("./"); await waitForReady(page); const captureRows = page .getByRole("grid", { name: "Captured matches" }) .locator("tbody tr"); await captureRows.first().focus(); await captureRows.first().press("Enter"); await expect(captureRows.first()).toHaveAttribute("aria-selected", "true"); await expect(captureRows.first()).toHaveClass(/is-selected/u); await expect( page .getByRole("tree", { name: "Match extraction" }) .locator(".tree-row.is-selected"), ).toContainText("Group 1 — date"); await expect( page.getByTestId("editor-test-text").locator(".cm-selected-range"), ).toHaveCount(1); await page .getByRole("tree", { name: "Match extraction" }) .getByRole("treeitem", { name: /^Match 1 —/u }) .click(); await expect( page .getByRole("tree", { name: "Pattern explanation" }) .locator(".tree-row.is-selected"), ).toContainText("Regular-expression pattern"); }); test("pattern explanation selection synchronizes capture result views", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page .getByRole("tree", { name: "Pattern explanation" }) .locator(".tree-row") .filter({ hasText: "group 1 · date" }) .first() .click(); await expect( page .getByRole("tree", { name: "Match extraction" }) .locator(".tree-row.is-selected"), ).toContainText("Group 1 — date"); await expect( page .getByRole("grid", { name: "Captured matches" }) .locator("tbody tr") .first(), ).toHaveAttribute("aria-selected", "true"); await expect( page.getByTestId("editor-test-text").locator(".cm-selected-range"), ).toHaveCount(1); }); test("replacement and list modes use native and typed templates", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByRole("button", { name: "Replace", exact: true }).click(); await expect( page.getByRole("heading", { name: "Replacement tokens" }), ).toBeVisible(); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect( page.getByTestId("editor-replacement-output").locator(".cm-content"), ).toContainText("alice — 2026-07-24"); const perMatchPreview = page.locator(".replacement-preview-panel"); await expect( perMatchPreview.getByRole("heading", { name: "Per-match preview" }), ).toBeVisible(); await expect( perMatchPreview.locator(".replacement-match-preview"), ).toHaveCount(2); await expect( perMatchPreview.locator(".replacement-match-preview").first(), ).toContainText("2026-07-24 alice"); await expect( perMatchPreview.locator(".replacement-match-preview").first(), ).toContainText("alice — 2026-07-24"); const userContribution = perMatchPreview .getByRole("button", { name: /Insert named capture “user”/u }) .first(); await userContribution.click(); await expect(userContribution).toHaveAttribute("aria-pressed", "true"); await expect( page .getByRole("tree", { name: "Match extraction" }) .locator(".tree-row.is-selected"), ).toContainText("Group 2 — user"); await expect( page .getByTestId("editor-replacement-template") .locator(".cm-selected-range"), ).toHaveCount(1); await expect( page .getByTestId("editor-regular-expression-pattern") .locator(".cm-selected-range"), ).toHaveCount(1); await expect( page.getByTestId("editor-test-text").locator(".cm-selected-range"), ).toHaveCount(1); await expect( page.getByTestId("editor-replacement-output").locator(".cm-selected-range"), ).toHaveCount(1); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-replacement-template", "[$ — $]"); await expect(page.getByTestId("stale-replacement-syntax")).not.toBeVisible(); await expect(page.getByTestId("stale-result-frame")).toBeVisible(); await userContribution.click(); await expect( page .getByTestId("editor-replacement-template") .locator(".cm-selected-range"), ).toHaveCount(0); await expect( page .getByTestId("editor-regular-expression-pattern") .locator(".cm-selected-range"), ).toHaveCount(0); await expect( page.getByTestId("editor-test-text").locator(".cm-selected-range"), ).toHaveCount(0); await page.getByRole("button", { name: "List", exact: true }).click(); await expect( page.getByRole("heading", { name: "Generated rows" }), ).toBeVisible(); await expect(page.locator(".list-preview li")).toHaveCount(2); await expect(page.locator(".list-preview li").first()).toContainText( "alice — 2026-07-24", ); }); test("sticky scan-all replacement uses every authoritative match", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByRole("checkbox", { name: "Global (g)" }).press("Space"); await page.getByRole("checkbox", { name: "Unicode (u)" }).press("Space"); await page.getByRole("checkbox", { name: "Sticky (y)" }).press("Space"); await page.getByRole("checkbox", { name: "Scan all" }).check(); await setEditor(page, "editor-regular-expression-pattern", "a"); await setEditor(page, "editor-test-text", "aa"); await waitForReady(page); await page.getByRole("button", { name: "Replace", exact: true }).click(); await setEditor(page, "editor-replacement-template", "x"); await waitForReady(page); await expect(page.locator(".run-status")).toContainText("2 matches"); await expect( page .getByRole("tree", { name: "Match extraction" }) .getByRole("treeitem", { name: /^Match \d+ —/u }), ).toHaveCount(2); await expect( page.getByTestId("editor-replacement-output").locator(".cm-content"), ).toHaveText("xx"); }); test("unit tests run through the actual worker engine", async ({ page }) => { await page.goto("./"); await waitForReady(page); await page.getByRole("button", { name: "Unit tests", exact: true }).click(); await page .getByRole("textbox", { name: "Name", exact: true }) .fill("Current dates"); await page.getByRole("button", { name: "Add test" }).click(); await page.getByRole("button", { name: "Run all" }).click(); await expect(page.locator(".test-result")).toContainText("Pass", { timeout: 15_000, }); }); test("unit-test cases can be captured, cloned, edited, selectively run, filtered, and moved as JSON", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByRole("button", { name: "Unit tests", exact: true }).click(); await page.getByRole("button", { name: "Add current result" }).click(); await page .getByRole("button", { name: "Clone Current match result" }) .click(); await page .getByRole("button", { name: "Edit Current match result copy" }) .click(); await page .getByRole("textbox", { name: "Name", exact: true }) .fill("Expected failure"); await page .getByRole("combobox", { name: "Expectation" }) .selectOption("should-not-match"); await page.getByRole("button", { name: "Update test" }).click(); await page .getByRole("checkbox", { name: "Select Current match result" }) .check(); await page.getByRole("button", { name: "Run selected" }).click(); await expect( page .locator(".test-list li") .filter({ hasText: "Current match result" }) .first() .locator(".test-result"), ).toContainText("Pass", { timeout: 15_000 }); await expect( page .locator(".test-list li") .filter({ hasText: "Expected failure" }) .locator(".test-result"), ).toHaveText("Not run"); await page.getByRole("checkbox", { name: "Select Expected failure" }).check(); await page.getByRole("button", { name: "Run selected" }).click(); await expect( page .locator(".test-list li") .filter({ hasText: "Expected failure" }) .locator(".test-result"), ).toContainText("Fail", { timeout: 15_000 }); await page.getByRole("checkbox", { name: "Failures only" }).check(); await expect(page.locator(".test-list li")).toHaveCount(1); await expect(page.locator(".test-list li")).toContainText("Expected failure"); await page .getByRole("checkbox", { name: "Include subjects in JSON" }) .check(); const downloadPromise = page.waitForEvent("download"); await page.getByRole("button", { name: "Export JSON" }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe("regex-tests.regex-tests.json"); const downloadedPath = await download.path(); if (!downloadedPath) throw new Error("Test-suite download path unavailable"); await page .getByLabel("Import test-suite JSON file") .setInputFiles(downloadedPath); await expect(page.getByText("Imported 2 tests.")).toBeVisible(); await page.getByRole("checkbox", { name: "Failures only" }).uncheck(); await expect(page.locator(".test-list li")).toHaveCount(4); }); test("a running unit-test suite can be cancelled without affecting interactive execution", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$"); await setEditor(page, "editor-test-text", `${"a".repeat(48)}!`); await page.getByRole("button", { name: "Unit tests", exact: true }).click(); await page .getByRole("combobox", { name: "Expectation" }) .selectOption("must-time-out"); await page .getByRole("spinbutton", { name: "Per-test timeout (ms, optional)" }) .fill("10000"); await page.getByRole("button", { name: "Add test" }).click(); await page.getByRole("button", { name: "Run all" }).click(); await page.getByRole("button", { name: "Cancel" }).click(); await expect(page.getByRole("button", { name: "Run all" })).toBeEnabled(); await page.getByRole("button", { name: "Match", exact: true }).click(); await setEditor(page, "editor-regular-expression-pattern", "^ok$"); await setEditor(page, "editor-test-text", "ok"); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect(page.locator(".run-status")).toContainText("1 matches"); }); test("invalid Toolbox context degrades to the complete standalone app", async ({ page, }) => { await page.goto( "./?toolbox=https%3A%2F%2Fcross-origin.invalid%2Ftoolbox.catalog.json", ); await expect( page.getByRole("heading", { name: "Regex Tools" }), ).toBeVisible(); await waitForReady(page); await expect( page.getByRole("button", { name: "Run", exact: true }), ).toBeEnabled(); }); test("a valid same-origin Toolbox context connects the shared app switcher", async ({ page, }) => { await page.goto("./?toolbox=/toolbox.catalog.json"); await waitForReady(page); await expect(page.locator(".toolbox-shell")).toHaveAttribute( "data-toolbox-context", "connected", ); await page.getByRole("button", { name: "Apps" }).click(); const switcher = page.getByRole("navigation", { name: "Toolbox applications", }); await expect(switcher).toBeVisible(); await expect( switcher.getByRole("link", { name: "Regex Tools" }), ).toHaveAttribute("aria-current", "page"); }); test("the same production build loads from a deep nested path", async ({ page, }) => { const responses: string[] = []; page.on("response", (response) => { if (response.status() >= 400) responses.push(response.url()); }); await page.goto("/deep/nested/regex/"); await expect( page.getByRole("heading", { name: "Regex Tools" }), ).toBeVisible(); await waitForReady(page); await expect(page.locator(".extraction-row")).toHaveCount(8); expect(responses).toEqual([]); }); test("responsive layout retains editors and result trees", async ({ page }) => { await page.setViewportSize({ width: 320, height: 844 }); await page.goto("./"); await waitForReady(page); await expect( page.getByTestId("editor-regular-expression-pattern"), ).toBeVisible(); await expect( page.getByRole("heading", { name: "Explanation tree" }), ).toBeVisible(); await expect( page.getByRole("heading", { name: "Extraction tree" }), ).toBeVisible(); const overflows = await page.evaluate( () => document.documentElement.scrollWidth > window.innerWidth, ); expect(overflows).toBe(false); }); test("dark theme capture and success text meets normal-text contrast", async ({ page, }) => { await page.emulateMedia({ colorScheme: "dark" }); await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); await setEditor( page, "editor-regular-expression-pattern", "(a)(b)(c)(d)(e)(f)(g)(h)", ); await setEditor(page, "editor-test-text", "abcdefgh"); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect(page.locator(".cm-capture-8").first()).toBeAttached(); const ratios = await page.evaluate(() => { const channels = (value: string): [number, number, number] => { const values = value.match(/[\d.]+/gu)?.map(Number) ?? []; if (value.startsWith("color(srgb")) { return [values[0]! * 255, values[1]! * 255, values[2]! * 255]; } return [values[0]!, values[1]!, values[2]!]; }; const luminance = (color: [number, number, number]) => { const normalized = color.map((channel) => { const value = channel / 255; return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4; }); return ( 0.2126 * normalized[0]! + 0.7152 * normalized[1]! + 0.0722 * normalized[2]! ); }; const contrast = (foreground: string, background: string) => { const left = luminance(channels(foreground)); const right = luminance(channels(background)); return (Math.max(left, right) + 0.05) / (Math.min(left, right) + 0.05); }; const editor = document.querySelector(".code-editor"); if (!editor) throw new Error("Editor is unavailable."); const editorBackground = getComputedStyle(editor).backgroundColor; const captureRatios = Array.from({ length: 8 }, (_, index) => { const capture = document.querySelector( `.cm-capture-${index + 1}`, ); if (!capture) throw new Error(`Capture ${index + 1} is unavailable.`); return contrast(getComputedStyle(capture).color, editorBackground); }); const success = document.querySelector( ".run-status.status-ready > span", ); if (!success) throw new Error("Success status is unavailable."); return [ ...captureRatios, contrast( getComputedStyle(success).color, getComputedStyle(success).backgroundColor, ), ]; }); expect(Math.min(...ratios)).toBeGreaterThanOrEqual(4.5); }); test("project file picker is named and omitted from keyboard order", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByRole("button", { name: "Project", exact: true }).click(); const input = page.getByLabel("Import regex project JSON file"); await expect(input).toHaveAttribute("tabindex", "-1"); await expect(input).toHaveAttribute("type", "file"); }); test("result trees and capture rows support keyboard navigation", async ({ page, }) => { await page.goto("./"); await waitForReady(page); const explanationItems = page .getByRole("tree", { name: "Pattern explanation" }) .getByRole("treeitem"); await explanationItems.first().focus(); await explanationItems.first().press("ArrowDown"); await expect(explanationItems.nth(1)).toBeFocused(); const captureRows = page .getByRole("grid", { name: "Captured matches" }) .locator("tbody tr"); await captureRows.first().focus(); await captureRows.first().press("ArrowDown"); await expect(captureRows.nth(1)).toBeFocused(); const globalFlag = page.getByRole("checkbox", { name: "Global (g)" }); await globalFlag.focus(); const flagFocus = await globalFlag.evaluate((input) => { const indicator = input.nextElementSibling; if (!(indicator instanceof HTMLElement)) return null; const style = getComputedStyle(indicator); return { style: style.outlineStyle, width: style.outlineWidth }; }); expect(flagFocus).toEqual({ style: "solid", width: "3px" }); }); test("a timeout kills the worker and the next request uses a fresh engine", async ({ page, }) => { await page.goto("./"); await waitForReady(page); await page.getByLabel("Live").uncheck(); const initialExtractionCount = await page.locator(".extraction-row").count(); const initialCaptureCount = await page .locator(".capture-table-scroll tbody tr") .count(); await page.getByLabel("Manual limit").selectOption("250"); await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$"); await setEditor(page, "editor-test-text", `${"a".repeat(48)}!`); await page.getByRole("button", { name: "Run", exact: true }).click(); await expect(page.locator(".run-status")).toHaveClass(/status-timeout/u, { timeout: 15_000, }); await expect(page.locator(".run-status")).toContainText( "worker was terminated", ); await expect(page.getByTestId("stale-result-frame")).toBeVisible(); await expect(page.locator(".extraction-row")).toHaveCount( initialExtractionCount, ); await expect(page.locator(".capture-table-scroll tbody tr")).toHaveCount( initialCaptureCount, ); await expect(page.locator(".cm-subject-match")).toHaveCount(0); await setEditor(page, "editor-regular-expression-pattern", "^ok$"); await setEditor(page, "editor-test-text", "ok"); await page.getByRole("button", { name: "Run", exact: true }).click(); await waitForReady(page); await expect(page.locator(".run-status")).toContainText("1 matches"); });