framewatch-mcp-server 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +895 -15
  2. package/dist/constants.d.ts +274 -0
  3. package/dist/constants.js +279 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/engine/browser.d.ts +20 -4
  6. package/dist/engine/browser.js +26 -9
  7. package/dist/engine/browser.js.map +1 -1
  8. package/dist/engine/clicks.d.ts +221 -0
  9. package/dist/engine/clicks.js +801 -0
  10. package/dist/engine/clicks.js.map +1 -0
  11. package/dist/engine/forms.d.ts +137 -0
  12. package/dist/engine/forms.js +474 -0
  13. package/dist/engine/forms.js.map +1 -0
  14. package/dist/engine/hmr.d.ts +41 -0
  15. package/dist/engine/hmr.js +91 -0
  16. package/dist/engine/hmr.js.map +1 -0
  17. package/dist/engine/inspect.d.ts +31 -0
  18. package/dist/engine/inspect.js +383 -0
  19. package/dist/engine/inspect.js.map +1 -0
  20. package/dist/engine/interaction.d.ts +12 -7
  21. package/dist/engine/interaction.js +110 -18
  22. package/dist/engine/interaction.js.map +1 -1
  23. package/dist/engine/links.d.ts +134 -0
  24. package/dist/engine/links.js +384 -0
  25. package/dist/engine/links.js.map +1 -0
  26. package/dist/engine/mocks.d.ts +53 -0
  27. package/dist/engine/mocks.js +148 -0
  28. package/dist/engine/mocks.js.map +1 -0
  29. package/dist/engine/rtl.d.ts +129 -0
  30. package/dist/engine/rtl.js +540 -0
  31. package/dist/engine/rtl.js.map +1 -0
  32. package/dist/engine/seo.d.ts +189 -0
  33. package/dist/engine/seo.js +398 -0
  34. package/dist/engine/seo.js.map +1 -0
  35. package/dist/engine/snapshot.d.ts +29 -0
  36. package/dist/engine/snapshot.js +10 -0
  37. package/dist/engine/snapshot.js.map +1 -0
  38. package/dist/engine/vue.d.ts +54 -0
  39. package/dist/engine/vue.js +419 -0
  40. package/dist/engine/vue.js.map +1 -0
  41. package/dist/index.js +45 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/tools/accessibility.d.ts +4 -0
  44. package/dist/tools/accessibility.js +9 -2
  45. package/dist/tools/accessibility.js.map +1 -1
  46. package/dist/tools/api-mock.d.ts +405 -0
  47. package/dist/tools/api-mock.js +186 -0
  48. package/dist/tools/api-mock.js.map +1 -0
  49. package/dist/tools/capture.d.ts +90 -26
  50. package/dist/tools/capture.js +109 -58
  51. package/dist/tools/capture.js.map +1 -1
  52. package/dist/tools/compare.d.ts +4 -0
  53. package/dist/tools/compare.js +16 -5
  54. package/dist/tools/compare.js.map +1 -1
  55. package/dist/tools/dead-clicks.d.ts +128 -0
  56. package/dist/tools/dead-clicks.js +570 -0
  57. package/dist/tools/dead-clicks.js.map +1 -0
  58. package/dist/tools/form-test.d.ts +112 -0
  59. package/dist/tools/form-test.js +477 -0
  60. package/dist/tools/form-test.js.map +1 -0
  61. package/dist/tools/index.d.ts +17 -1
  62. package/dist/tools/index.js +45 -1
  63. package/dist/tools/index.js.map +1 -1
  64. package/dist/tools/inspect.d.ts +78 -0
  65. package/dist/tools/inspect.js +136 -0
  66. package/dist/tools/inspect.js.map +1 -0
  67. package/dist/tools/interact.d.ts +37 -18
  68. package/dist/tools/interact.js +113 -13
  69. package/dist/tools/interact.js.map +1 -1
  70. package/dist/tools/links.d.ts +129 -0
  71. package/dist/tools/links.js +640 -0
  72. package/dist/tools/links.js.map +1 -0
  73. package/dist/tools/responsive.d.ts +10 -6
  74. package/dist/tools/responsive.js +21 -4
  75. package/dist/tools/responsive.js.map +1 -1
  76. package/dist/tools/rtl.d.ts +241 -0
  77. package/dist/tools/rtl.js +410 -0
  78. package/dist/tools/rtl.js.map +1 -0
  79. package/dist/tools/save-auth.d.ts +263 -0
  80. package/dist/tools/save-auth.js +253 -0
  81. package/dist/tools/save-auth.js.map +1 -0
  82. package/dist/tools/screenshot.d.ts +4 -0
  83. package/dist/tools/screenshot.js +15 -4
  84. package/dist/tools/screenshot.js.map +1 -1
  85. package/dist/tools/seo.d.ts +113 -0
  86. package/dist/tools/seo.js +281 -0
  87. package/dist/tools/seo.js.map +1 -0
  88. package/dist/tools/snapshot.d.ts +122 -0
  89. package/dist/tools/snapshot.js +183 -0
  90. package/dist/tools/snapshot.js.map +1 -0
  91. package/dist/tools/wait-for.d.ts +107 -0
  92. package/dist/tools/wait-for.js +167 -0
  93. package/dist/tools/wait-for.js.map +1 -0
  94. package/dist/utils/arabic-text.d.ts +14 -0
  95. package/dist/utils/arabic-text.js +193 -0
  96. package/dist/utils/arabic-text.js.map +1 -0
  97. package/dist/utils/budget.d.ts +41 -0
  98. package/dist/utils/budget.js +182 -0
  99. package/dist/utils/budget.js.map +1 -0
  100. package/dist/utils/format.d.ts +11 -1
  101. package/dist/utils/format.js +27 -4
  102. package/dist/utils/format.js.map +1 -1
  103. package/dist/utils/highlight.d.ts +69 -0
  104. package/dist/utils/highlight.js +181 -0
  105. package/dist/utils/highlight.js.map +1 -0
  106. package/dist/utils/link-rules.d.ts +100 -0
  107. package/dist/utils/link-rules.js +284 -0
  108. package/dist/utils/link-rules.js.map +1 -0
  109. package/dist/utils/mock-rules.d.ts +144 -0
  110. package/dist/utils/mock-rules.js +224 -0
  111. package/dist/utils/mock-rules.js.map +1 -0
  112. package/dist/utils/rtl-rules.d.ts +142 -0
  113. package/dist/utils/rtl-rules.js +296 -0
  114. package/dist/utils/rtl-rules.js.map +1 -0
  115. package/dist/utils/seo-rules.d.ts +129 -0
  116. package/dist/utils/seo-rules.js +726 -0
  117. package/dist/utils/seo-rules.js.map +1 -0
  118. package/dist/utils/snapshot-rules.d.ts +33 -0
  119. package/dist/utils/snapshot-rules.js +111 -0
  120. package/dist/utils/snapshot-rules.js.map +1 -0
  121. package/dist/utils/storage-state.d.ts +76 -0
  122. package/dist/utils/storage-state.js +195 -0
  123. package/dist/utils/storage-state.js.map +1 -0
  124. package/dist/utils/style-rules.d.ts +107 -0
  125. package/dist/utils/style-rules.js +223 -0
  126. package/dist/utils/style-rules.js.map +1 -0
  127. package/dist/utils/test-data.d.ts +75 -0
  128. package/dist/utils/test-data.js +294 -0
  129. package/dist/utils/test-data.js.map +1 -0
  130. package/dist/utils/vue-rules.d.ts +72 -0
  131. package/dist/utils/vue-rules.js +108 -0
  132. package/dist/utils/vue-rules.js.map +1 -0
  133. package/package.json +6 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dead-clicks.js","sourceRoot":"","sources":["../../src/tools/dead-clicks.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { ElementHandle, Page } from \"playwright\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n DEAD_CLICK_TIMEOUT_MS,\n DEFAULT_DEAD_CLICK_ELEMENTS,\n DEFAULT_DEAD_CLICK_SETTLE_MS,\n DEFAULT_DEAD_CLICK_WAIT_MS,\n DEFAULT_VIEWPORT,\n HIGHLIGHT_BROKEN_COLOUR,\n HIGHLIGHT_DEAD_COLOUR,\n MAX_DEAD_CLICK_EFFECTS,\n MAX_DEAD_CLICK_ELEMENTS,\n MAX_DEAD_CLICK_LISTED,\n MAX_DEAD_CLICK_SELECTOR_LENGTH,\n MAX_HIGHLIGHTS,\n MAX_VIEWPORT_HEIGHT,\n MAX_VIEWPORT_WIDTH,\n NAVIGATION_TIMEOUT_MS,\n SELECTOR_TIMEOUT_MS,\n} from \"../constants.js\";\nimport { withPage } from \"../engine/browser.js\";\nimport {\n NO_NOISE,\n describeNoise,\n diffPageState,\n discoverClickables,\n installClickWatcher,\n measureNoise,\n probeHover,\n readPageState,\n resetClickWatcher,\n resolveClickable,\n sameUrl,\n type ClickEffect,\n type ClickEvidence,\n type Clickable,\n type HoverProbe,\n type IdleNoise,\n} from \"../engine/clicks.js\";\nimport { ConsoleCollector, NetworkCollector } from \"../engine/layers/index.js\";\nimport { resizeForOutput, toBase64 } from \"../utils/image.js\";\nimport { highlightElements, type Highlight } from \"../utils/highlight.js\";\nimport { resolveStorageState, storageStateField, withAuthNote } from \"../utils/storage-state.js\";\n\nexport const DEAD_CLICKS_TOOL_NAME = \"framewatch_dead_clicks\";\n\nexport const deadClicksInputShape = {\n url: z\n .string()\n .url()\n .describe(\"URL to sweep, e.g. http://localhost:3000 (http, https and file URLs are accepted)\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_DEAD_CLICK_WAIT_MS)\n .describe(\n \"Wait time (ms) after each page load before anything is clicked. The page is reloaded whenever a click \" +\n \"changes it, so this is paid once per element that does something — lower it on a fast app.\",\n ),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) after each load\"),\n wait_for_timeout_ms: z\n .number()\n .int()\n .min(1)\n .default(SELECTOR_TIMEOUT_MS)\n .describe(\"Max time (ms) to wait for `wait_for` to appear (must be > 0)\"),\n settle_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_DEAD_CLICK_SETTLE_MS)\n .describe(\n \"How long to watch the page after each click before deciding nothing happened. Raise it for an app whose \" +\n \"handlers are slow; every element costs this much time.\",\n ),\n selector: z\n .string()\n .optional()\n .describe(\"Only sweep inside this container, e.g. 'nav' or '#app main'. Omit to sweep the whole page.\"),\n exclude: z\n .string()\n .optional()\n .describe(\n \"CSS selector for controls that must never be clicked, and nothing inside them either — 'Delete', \" +\n \"'Place order', anything that spends money. This tool presses every button it finds.\",\n ),\n include_pointer: z\n .boolean()\n .default(true)\n .describe(\n \"Also test elements that are only clickable-looking because their CSS cursor is a pointer. That is where \" +\n \"dead clicks actually live (a <div> has no default behaviour to fall back on), so leave it on unless the \" +\n \"page is full of decorative pointer styling.\",\n ),\n include_hover: z\n .boolean()\n .default(true)\n .describe(\n \"For each dead element, also check whether the page reacts to hovering it. A dead control that lights up \" +\n \"under the pointer is actively inviting the click that does nothing.\",\n ),\n max_elements: z\n .number()\n .int()\n .min(1)\n .max(MAX_DEAD_CLICK_ELEMENTS)\n .default(DEFAULT_DEAD_CLICK_ELEMENTS)\n .describe(\"Maximum elements to click. They are clicked in document order; the rest are counted and named.\"),\n full_page: z\n .boolean()\n .default(false)\n .describe(\"Photograph the whole document instead of the viewport, so dead elements below the fold are visible too\"),\n viewport: z\n .object({\n width: z.number().int().min(1).max(MAX_VIEWPORT_WIDTH).default(DEFAULT_VIEWPORT.width),\n height: z.number().int().min(1).max(MAX_VIEWPORT_HEIGHT).default(DEFAULT_VIEWPORT.height),\n })\n .optional()\n .describe(\"Viewport size (defaults to 1280x720)\"),\n storage_state: storageStateField,\n};\n\nexport const deadClicksInputSchema = z.object(deadClicksInputShape);\nexport type DeadClicksInput = z.input<typeof deadClicksInputSchema>;\ntype ParsedDeadClicksInput = z.output<typeof deadClicksInputSchema>;\n\n/** What one element did — or did not do — when it was clicked. */\nexport interface Verdict {\n clickable: Clickable;\n /** Everything the click caused. Empty means the click did nothing at all. */\n effects: ClickEffect[];\n /** Set when the element was never clicked, and why. */\n skipped?: string;\n /** How the page reacts to the pointer, asked only of dead elements. */\n hover?: HoverProbe;\n}\n\n/** Everything one sweep produced. */\ninterface Sweep {\n verdicts: Verdict[];\n /** Candidates found before `max_elements` trimmed the list. */\n found: number;\n /** Candidates past `max_elements`, which were never clicked. */\n untested: Clickable[];\n noise: IdleNoise;\n png?: Buffer;\n /** Dead/broken elements the overlay could not draw a box for. */\n unhighlighted: number;\n}\n\n/**\n * Find the elements on a page that look clickable and do nothing.\n *\n * Every candidate is clicked for real, and the page is then interrogated from\n * every angle at once — URL, DOM mutations, form state, storage, scroll,\n * focus, console, network, dialogs, popups, downloads. An element is only\n * called dead when all of them stayed silent (see engine/clicks.ts).\n *\n * Two things make the answer trustworthy rather than merely plausible. The\n * page is measured once with nobody clicking, so a ticking clock or a polling\n * fetch cannot make every element look alive. And the page is reloaded after\n * any click that changed it, so element five is judged on the page as it\n * shipped rather than on whatever elements one to four left behind — a click\n * that changed nothing needs no reload, which is exactly the case this tool is\n * looking for, so a page full of dead controls is also the fastest to sweep.\n *\n * This is the one read-only-looking tool that is not read-only: it presses\n * every button on the page, including the one that deletes the account. Use\n * `exclude` (or `selector`) on anything that matters.\n */\nexport async function findDeadClicks(rawInput: DeadClicksInput): Promise<CallToolResult> {\n const parsed = deadClicksInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Dead-click sweep failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n const viewport = input.viewport ?? { ...DEFAULT_VIEWPORT };\n\n let contextOptions = {};\n let auth = null;\n try {\n auth = await resolveStorageState(input.storage_state);\n if (auth) contextOptions = { storageState: auth.state };\n } catch (error) {\n return errorResult(`Dead-click sweep of ${input.url} failed: ${firstLine(error)}`);\n }\n\n let sweep: Sweep;\n try {\n sweep = await withPage({ viewport, contextOptions }, (page) => runSweep(input, page));\n } catch (error) {\n return errorResult(describeDeadClicksFailure(input, error));\n }\n\n return withAuthNote(await render(input, sweep), auth);\n}\n\n/* ── The sweep ────────────────────────────────────────────────────────── */\n\nasync function runSweep(input: ParsedDeadClicksInput, page: Page): Promise<Sweep> {\n const consoleLog = new ConsoleCollector(page).attach();\n const network = new NetworkCollector(page).attach();\n const dialogs: string[] = [];\n const popups: string[] = [];\n const downloads: string[] = [];\n\n // A page that opens an alert would otherwise be dismissed silently by\n // Playwright, and \"it opened a dialog\" is the whole answer for that element.\n page.on(\"dialog\", (dialog) => {\n dialogs.push(`${dialog.type()}: ${dialog.message()}`);\n void dialog.dismiss().catch(() => {});\n });\n page.on(\"popup\", (popup) => {\n popups.push(safely(() => popup.url()) ?? \"a new tab\");\n void popup.close().catch(() => {});\n });\n page.on(\"download\", (download) => {\n downloads.push(download.suggestedFilename());\n void download.cancel().catch(() => {});\n });\n\n // Before the navigation: the watcher has to be installed at the start of\n // every document, including the first one and every reload after it.\n await installClickWatcher(page);\n\n try {\n await load(input, page);\n\n const candidates = await discoverClickables(page, {\n ...(input.selector !== undefined ? { selector: input.selector } : {}),\n ...(input.exclude !== undefined ? { exclude: input.exclude } : {}),\n include_pointer: input.include_pointer,\n });\n if (candidates.length === 0) {\n throw new Error(\n input.selector\n ? `nothing inside \\`${input.selector}\\` looks clickable — no link, button, click role, onclick or pointer cursor`\n : \"nothing on this page looks clickable — no link, button, click role, onclick or pointer cursor\",\n );\n }\n\n const clear = (): void => {\n consoleLog.clear();\n network.clear();\n dialogs.length = 0;\n popups.length = 0;\n downloads.length = 0;\n };\n const evidence = (focusSelf: boolean, since: number): ClickEvidence => ({\n console: consoleLog.entries(since),\n network: network.events(since),\n dialogs: [...dialogs],\n popups: [...popups],\n downloads: [...downloads],\n focus_self: focusSelf,\n });\n\n const noise = await measureIdle(page, input, clear, evidence);\n\n const skipped = candidates.filter((candidate) => candidate.skip !== undefined);\n const testable = candidates.filter((candidate) => candidate.skip === undefined);\n const tested = testable.slice(0, input.max_elements);\n const untested = testable.slice(input.max_elements);\n\n const verdicts: Verdict[] = skipped.map((candidate) => ({\n clickable: candidate,\n effects: [],\n skipped: candidate.skip,\n }));\n\n let dirty = false;\n for (const candidate of tested) {\n if (dirty) {\n await load(input, page);\n dirty = false;\n }\n const verdict = await testOne(page, input, candidate, clear, evidence, noise);\n verdicts.push(verdict);\n // A click that changed nothing left the page exactly as it was, so there\n // is nothing to restore — which is why a page full of dead controls is\n // the fastest kind to sweep.\n if (verdict.effects.some((effect) => DISTURBING.has(effect.kind))) dirty = true;\n }\n\n // The overlay is drawn on a page nobody has touched: the boxes are a DOM\n // mutation of their own, and the report is about a page in its shipped state.\n await load(input, page);\n const marked = verdicts.filter(isFlagged);\n const highlights: Highlight[] = marked.slice(0, MAX_HIGHLIGHTS).map((verdict, index) => ({\n selector: verdict.clickable.selector,\n match_index: verdict.clickable.match_index,\n label: String(index + 1),\n colour: isBroken(verdict) ? HIGHLIGHT_BROKEN_COLOUR : HIGHLIGHT_DEAD_COLOUR,\n wash: isBroken(verdict) ? \"rgba(240, 140, 0, 0.16)\" : \"rgba(229, 25, 75, 0.16)\",\n }));\n const drawn = await highlightElements(page, highlights);\n const png = await safeScreenshot(page, input.full_page);\n\n return {\n verdicts,\n found: candidates.length,\n untested,\n noise,\n ...(png ? { png } : {}),\n unhighlighted: marked.length - drawn.drawn.length,\n };\n } finally {\n consoleLog.detach();\n network.detach();\n }\n}\n\n/** Effects that leave the page in a state the next element must not be judged against. */\nconst DISTURBING = new Set([\"navigated\", \"reloaded\", \"dom\", \"fields\", \"storage\", \"scroll\", \"title\", \"dialog\"]);\n\n/** Open the page (or put it back the way it started). */\nasync function load(input: ParsedDeadClicksInput, page: Page): Promise<void> {\n await page.goto(input.url, { waitUntil: \"load\", timeout: NAVIGATION_TIMEOUT_MS });\n if (input.wait_for) {\n await page.waitForSelector(input.wait_for, { state: \"visible\", timeout: input.wait_for_timeout_ms });\n }\n if (input.wait_ms > 0) {\n await page.waitForTimeout(input.wait_ms);\n }\n}\n\n/**\n * Watch the page for one settle window with nobody clicking.\n *\n * Whatever it does in that window it does by itself, and none of it can be\n * evidence that a click did something. Without this one measurement, a page\n * with a clock in the corner has no dead elements at all.\n */\nasync function measureIdle(\n page: Page,\n input: ParsedDeadClicksInput,\n clear: () => void,\n evidence: (focusSelf: boolean, since: number) => ClickEvidence,\n): Promise<IdleNoise> {\n await resetClickWatcher(page);\n clear();\n const since = Date.now();\n const before = await readPageState(page);\n if (!before) return NO_NOISE;\n if (input.settle_ms > 0) await page.waitForTimeout(input.settle_ms);\n const after = await readPageState(page);\n if (!after) return NO_NOISE;\n return measureNoise(before, after, evidence(true, since));\n}\n\n/** Click one element and work out what it did. Never throws. */\nasync function testOne(\n page: Page,\n input: ParsedDeadClicksInput,\n candidate: Clickable,\n clear: () => void,\n evidence: (focusSelf: boolean, since: number) => ClickEvidence,\n noise: IdleNoise,\n): Promise<Verdict> {\n const handle = await resolveClickable(page, candidate);\n if (handle === null) {\n return { clickable: candidate, effects: [], skipped: \"it was not on the page any more\" };\n }\n\n // Scroll first, then measure: Playwright would scroll to reach the element\n // anyway, and a scroll it caused must not be read back as something the\n // click did.\n await handle.scrollIntoViewIfNeeded({ timeout: DEAD_CLICK_TIMEOUT_MS }).catch(() => {});\n\n await resetClickWatcher(page);\n clear();\n const since = Date.now();\n const before = await readPageState(page);\n if (before === null) {\n return { clickable: candidate, effects: [], skipped: \"the page could not be read before the click\" };\n }\n\n try {\n // Playwright will not click an `aria-disabled` element: it waits for it to\n // become \"enabled\" and times out. But whether such an element still works\n // is precisely the question — an aria-disabled control with a live handler\n // tells a screen-reader user it is unavailable and everyone else that it\n // is not — so those are clicked with the actionability checks off.\n await handle.click({ timeout: DEAD_CLICK_TIMEOUT_MS, ...(candidate.aria_disabled ? { force: true } : {}) });\n } catch (error) {\n // A click that navigates can detach the element underneath Playwright.\n // The navigation proves the click landed, whatever the error says.\n if (sameUrl(safely(() => page.url()) ?? before.url, before.url)) {\n return { clickable: candidate, effects: [], skipped: `could not be clicked — ${firstLine(error)}` };\n }\n }\n\n if (input.settle_ms > 0) await page.waitForTimeout(input.settle_ms);\n\n const focusSelf = await focusStayedPut(handle);\n // A page mid-navigation cannot be read; its URL and everything Playwright\n // saw still can, so the comparison falls back to those.\n const after = (await readPageState(page)) ?? { ...before, url: safely(() => page.url()) ?? before.url };\n const effects = diffPageState(before, after, evidence(focusSelf, since), noise);\n\n const verdict: Verdict = { clickable: candidate, effects };\n if (effects.length === 0 && input.include_hover) {\n verdict.hover = await probeHover(page, handle, { timeout_ms: DEAD_CLICK_TIMEOUT_MS });\n }\n return verdict;\n}\n\n/**\n * Did focus stay on the element that was clicked?\n *\n * Clicking anything focusable focuses it, so that says nothing. Focus landing\n * somewhere else — a `<label>` handing it to its input, a skip link — is a\n * real effect, and for some controls it is the only one.\n */\nasync function focusStayedPut(handle: ElementHandle): Promise<boolean> {\n return handle\n .evaluate((node: any) => {\n const doc = (globalThis as any).document;\n const active = doc?.activeElement;\n // Focus falling back to <body> is a blur, not a destination.\n if (!active || active === doc?.body || active === doc?.documentElement) return true;\n return active === node || (node.contains ? node.contains(active) === true : false);\n })\n .catch(() => true);\n}\n\n/* ── Verdict shape ────────────────────────────────────────────────────── */\n\nfunction isDead(verdict: Verdict): boolean {\n return verdict.skipped === undefined && verdict.effects.length === 0;\n}\n\nfunction isBroken(verdict: Verdict): boolean {\n return verdict.skipped === undefined && verdict.effects.some((effect) => effect.kind === \"error\");\n}\n\n/** Dead or broken — the two kinds the overlay paints and the report leads with. */\nfunction isFlagged(verdict: Verdict): boolean {\n return isDead(verdict) || isBroken(verdict);\n}\n\n/* ── Rendering ────────────────────────────────────────────────────────── */\n\nasync function render(input: ParsedDeadClicksInput, sweep: Sweep): Promise<CallToolResult> {\n const content: CallToolResult[\"content\"] = [{ type: \"text\", text: formatSweep(input, sweep) }];\n\n if (sweep.png) {\n try {\n content.push({ type: \"image\", data: toBase64(await resizeForOutput(sweep.png)), mimeType: \"image/png\" });\n const marked = sweep.verdicts.filter(isFlagged).length;\n content.push({\n type: \"text\",\n text:\n marked > 0\n ? `${input.url}, with every dead element boxed in red and every broken one in orange, numbered as above.` +\n (sweep.unhighlighted > 0\n ? ` ${sweep.unhighlighted} of them could not be boxed — the page had re-rendered by the time the overlay was drawn.`\n : \"\")\n : `${input.url} — nothing to mark up.`,\n });\n } catch {\n // Measured already; failing to re-encode the frame is not worth failing\n // the sweep for.\n }\n }\n\n return { content };\n}\n\nfunction formatSweep(input: ParsedDeadClicksInput, sweep: Sweep): string {\n const dead = sweep.verdicts.filter(isDead);\n const broken = sweep.verdicts.filter(isBroken);\n const alive = sweep.verdicts.filter(\n (verdict) => verdict.skipped === undefined && verdict.effects.length > 0 && !isBroken(verdict),\n );\n const skipped = sweep.verdicts.filter((verdict) => verdict.skipped !== undefined);\n const clicked = dead.length + broken.length + alive.length;\n const lines: string[] = [];\n\n lines.push(\n `Dead-click sweep of ${input.url} — ${count(sweep.found, \"clickable element\", \"clickable elements\")} found, ` +\n `${clicked} clicked, ${dead.length} dead, ${broken.length} broken, ` +\n `${skipped.length + sweep.untested.length} not clicked.`,\n );\n\n const noise = describeNoise(sweep.noise);\n if (noise !== undefined) {\n lines.push(\n `This page changes on its own — ${noise} in a ${input.settle_ms}ms window with nobody clicking. That much ` +\n \"was subtracted from every element below, so a click has to do more than the page already does.\",\n );\n }\n\n if (clicked === 0) {\n lines.push(\"Nothing was clicked: every element found was one this tool will not press.\");\n } else if (dead.length === 0 && broken.length === 0) {\n lines.push(`Every one of the ${clicked} elements that was clicked did something.`);\n }\n\n const numbered = sweep.verdicts.filter(isFlagged);\n const badge = (verdict: Verdict): number => numbered.indexOf(verdict) + 1;\n\n if (dead.length > 0) {\n lines.push(\"\");\n lines.push(\"Dead — nothing at all happened when these were clicked:\");\n for (const verdict of dead.slice(0, MAX_DEAD_CLICK_LISTED)) {\n lines.push(` ${badge(verdict)}. ${nameOf(verdict)}`);\n for (const note of deadNotes(verdict)) lines.push(` ${note}`);\n }\n if (dead.length > MAX_DEAD_CLICK_LISTED) {\n lines.push(` … and ${dead.length - MAX_DEAD_CLICK_LISTED} more dead elements`);\n }\n }\n\n if (broken.length > 0) {\n lines.push(\"\");\n lines.push(\"Broken — the handler ran and threw:\");\n for (const verdict of broken.slice(0, MAX_DEAD_CLICK_LISTED)) {\n lines.push(` ${badge(verdict)}. ${nameOf(verdict)}`);\n for (const effect of verdict.effects.slice(0, MAX_DEAD_CLICK_EFFECTS)) {\n lines.push(` ${effect.detail}`);\n }\n }\n if (broken.length > MAX_DEAD_CLICK_LISTED) {\n lines.push(` … and ${broken.length - MAX_DEAD_CLICK_LISTED} more`);\n }\n }\n\n const lying = alive.filter((verdict) => verdict.clickable.aria_disabled);\n if (lying.length > 0) {\n lines.push(\"\");\n lines.push(\n \"Marked aria-disabled, but they still work — a screen reader is told these are unavailable, and everyone \" +\n \"else can use them (they are clicked with Playwright's actionability checks off, which is the only way \" +\n \"to reach one):\",\n );\n for (const verdict of lying.slice(0, MAX_DEAD_CLICK_LISTED)) {\n lines.push(` ${nameOf(verdict)} — ${verdict.effects[0].detail}`);\n }\n }\n\n // Everything else that worked. The aria-disabled ones have just had their\n // own section and are not repeated here.\n const ordinary = alive.filter((verdict) => !verdict.clickable.aria_disabled);\n if (ordinary.length > 0) {\n lines.push(\"\");\n lines.push(`Alive — ${count(ordinary.length, \"element\", \"elements\")} did something:`);\n for (const verdict of ordinary.slice(0, MAX_DEAD_CLICK_LISTED)) {\n const what = verdict.effects\n .slice(0, MAX_DEAD_CLICK_EFFECTS)\n .map((effect) => effect.detail)\n .join(\"; \");\n lines.push(` ${nameOf(verdict)} — ${what}`);\n }\n if (ordinary.length > MAX_DEAD_CLICK_LISTED) {\n lines.push(` … and ${ordinary.length - MAX_DEAD_CLICK_LISTED} more`);\n }\n }\n\n if (skipped.length > 0 || sweep.untested.length > 0) {\n lines.push(\"\");\n lines.push(`Not clicked (${skipped.length + sweep.untested.length}):`);\n for (const verdict of skipped.slice(0, MAX_DEAD_CLICK_LISTED)) {\n lines.push(` ${nameOf(verdict)} — ${verdict.skipped}`);\n }\n if (skipped.length > MAX_DEAD_CLICK_LISTED) {\n lines.push(` … and ${skipped.length - MAX_DEAD_CLICK_LISTED} more`);\n }\n if (sweep.untested.length > 0) {\n lines.push(\n ` ${count(sweep.untested.length, \"element\", \"elements\")} past \\`max_elements\\` (${input.max_elements}), ` +\n `starting at ${describe(sweep.untested[0])} — raise it, or narrow the sweep with \\`selector\\`.`,\n );\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/** `1. a \"Pricing\" (#nav-pricing)` — what it is and how to find it in the source. */\nfunction nameOf(verdict: Verdict): string {\n return describe(verdict.clickable);\n}\n\nfunction describe(clickable: Clickable): string {\n return `${clickable.description} (${elide(clickable.selector, MAX_DEAD_CLICK_SELECTOR_LENGTH)})`;\n}\n\n/**\n * The lines under a dead element: why it looked clickable in the first place.\n *\n * A dead control that also lights up under the pointer is the worst case —\n * the page is actively inviting a click it will not answer — and one whose\n * only claim to being a button is a `cursor: pointer` is the classic\n * handler-never-attached bug. Both are the difference between \"this is\n * missing\" and \"this is broken\".\n */\nfunction deadNotes(verdict: Verdict): string[] {\n const notes: string[] = [];\n const { clickable, hover } = verdict;\n\n const looks: string[] = [];\n if (clickable.kind === \"pointer\") looks.push(\"it is a plain element styled with a pointer cursor — nothing else says it is a control\");\n else if (clickable.cursor === \"pointer\") looks.push(\"the cursor is a pointer\");\n if (clickable.kind === \"link\" && clickable.href) {\n const href = clickable.href;\n if (href.endsWith(\"#\")) looks.push('its href is \"#\", so it relies entirely on a handler');\n else if (href.toLowerCase().startsWith(\"javascript:\")) looks.push(`its href is \"${elide(href, 40)}\", so it relies entirely on a handler`);\n else looks.push(`it links to ${elide(href, 60)}, and going there was prevented`);\n }\n if (clickable.role) looks.push(`it is marked role=\"${clickable.role}\"`);\n if (hover) {\n if (hover.failed !== undefined) looks.push(`hover could not be checked (${hover.failed})`);\n else if (hover.changed.length > 0) looks.push(`hovering it changes ${hover.changed.join(\", \")}`);\n else looks.push(\"hovering it changes nothing\");\n }\n if (looks.length > 0) notes.push(`Looks clickable: ${looks.join(\"; \")}.`);\n\n if (clickable.aria_disabled) {\n notes.push('It is marked aria-disabled=\"true\", so doing nothing may well be deliberate.');\n }\n return notes;\n}\n\n/* ── Helpers ──────────────────────────────────────────────────────────── */\n\n/** A screenshot is always worth having and never worth failing the sweep over. */\nasync function safeScreenshot(page: Page, fullPage: boolean): Promise<Buffer | undefined> {\n try {\n if (page.isClosed()) return undefined;\n return await page.screenshot({ type: \"png\", fullPage });\n } catch {\n return undefined;\n }\n}\n\nfunction elide(value: string, max: number): string {\n const flat = value.replace(/\\s+/g, \" \");\n return flat.length > max ? `${flat.slice(0, max)}…` : flat;\n}\n\nfunction count(n: number, singular: string, plural: string): string {\n return `${n} ${n === 1 ? singular : plural}`;\n}\n\nfunction safely<T>(read: () => T): T | undefined {\n try {\n return read();\n } catch {\n return undefined;\n }\n}\n\nfunction firstLine(error: unknown): string {\n return (error instanceof Error ? error.message : String(error)).split(\"\\n\")[0];\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\n/**\n * One actionable line for a sweep that could not run. Mirrors `describeFailure`\n * in screenshot.ts: match on the failing Playwright call, never on substrings\n * of a user-supplied selector.\n */\nexport function describeDeadClicksFailure(\n input: Pick<ParsedDeadClicksInput, \"url\" | \"wait_for\" | \"wait_for_timeout_ms\">,\n error: unknown,\n): string {\n const message = error instanceof Error ? error.message : String(error);\n const line = firstLine(message);\n const prefix = `Dead-click sweep of ${input.url} failed:`;\n\n if (/Executable doesn't exist|browserType\\.launch/i.test(message)) {\n return (\n `${prefix} Playwright's Chromium browser is not installed. ` +\n `Run \\`npx playwright install chromium\\` and try again. (${line})`\n );\n }\n if (input.wait_for && /^page\\.waitForSelector:/.test(message)) {\n return `${prefix} selector \"${input.wait_for}\" did not become visible within ${input.wait_for_timeout_ms}ms.`;\n }\n if (/^page\\.goto:/.test(message)) {\n return `${prefix} the page could not be opened — ${line}`;\n }\n return `${prefix} ${line}`;\n}\n\nexport function registerDeadClicksTool(server: McpServer): void {\n server.registerTool(\n DEAD_CLICKS_TOOL_NAME,\n {\n title: \"Dead clicks\",\n description:\n \"Find the elements on a page that look clickable and do nothing — a button whose handler never got \" +\n \"attached, a link to '#', a <div> with a pointer cursor and no listener. Every candidate (links, \" +\n \"buttons, ARIA click roles, onclick attributes, pointer-cursor elements) is really clicked, and the \" +\n \"page is then checked from every angle at once: URL, DOM mutations, form state, storage, scroll, focus, \" +\n \"console, network, dialogs, popups and downloads. Only silence on all of them counts as dead. What the \" +\n \"page does on its own is measured first and subtracted, so a clock or a polling fetch cannot hide the \" +\n \"findings. Returns the list plus a screenshot with the dead elements boxed in red and any that threw in \" +\n \"orange. NOTE: this presses every button it finds, which is a real write to the app under test — pass \" +\n \"`exclude` (or `selector`) to keep it away from anything destructive. A sweep costs roughly \" +\n \"`settle_ms` per element, plus a page reload after each element that did something.\",\n inputSchema: deadClicksInputShape,\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },\n },\n async (args) => findDeadClicks(args),\n );\n}\n"]}
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
+ export declare const FORM_TEST_TOOL_NAME = "framewatch_form_test";
5
+ export declare const formTestInputShape: {
6
+ url: z.ZodString;
7
+ selector: z.ZodOptional<z.ZodString>;
8
+ strategies: z.ZodDefault<z.ZodArray<z.ZodEnum<["valid", "empty", "maxlength", "special_chars", "rtl_arabic", "numbers_only", "spaces_only", "boundary", "xss"]>, "many">>;
9
+ submit: z.ZodDefault<z.ZodBoolean>;
10
+ wait_ms: z.ZodDefault<z.ZodNumber>;
11
+ wait_for: z.ZodOptional<z.ZodString>;
12
+ wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
13
+ timeout_ms: z.ZodDefault<z.ZodNumber>;
14
+ max_fields: z.ZodDefault<z.ZodNumber>;
15
+ full_page: z.ZodDefault<z.ZodBoolean>;
16
+ viewport: z.ZodOptional<z.ZodObject<{
17
+ width: z.ZodDefault<z.ZodNumber>;
18
+ height: z.ZodDefault<z.ZodNumber>;
19
+ }, "strip", z.ZodTypeAny, {
20
+ width: number;
21
+ height: number;
22
+ }, {
23
+ width?: number | undefined;
24
+ height?: number | undefined;
25
+ }>>;
26
+ storage_state: z.ZodOptional<z.ZodString>;
27
+ };
28
+ export declare const formTestInputSchema: z.ZodObject<{
29
+ url: z.ZodString;
30
+ selector: z.ZodOptional<z.ZodString>;
31
+ strategies: z.ZodDefault<z.ZodArray<z.ZodEnum<["valid", "empty", "maxlength", "special_chars", "rtl_arabic", "numbers_only", "spaces_only", "boundary", "xss"]>, "many">>;
32
+ submit: z.ZodDefault<z.ZodBoolean>;
33
+ wait_ms: z.ZodDefault<z.ZodNumber>;
34
+ wait_for: z.ZodOptional<z.ZodString>;
35
+ wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
36
+ timeout_ms: z.ZodDefault<z.ZodNumber>;
37
+ max_fields: z.ZodDefault<z.ZodNumber>;
38
+ full_page: z.ZodDefault<z.ZodBoolean>;
39
+ viewport: z.ZodOptional<z.ZodObject<{
40
+ width: z.ZodDefault<z.ZodNumber>;
41
+ height: z.ZodDefault<z.ZodNumber>;
42
+ }, "strip", z.ZodTypeAny, {
43
+ width: number;
44
+ height: number;
45
+ }, {
46
+ width?: number | undefined;
47
+ height?: number | undefined;
48
+ }>>;
49
+ storage_state: z.ZodOptional<z.ZodString>;
50
+ }, "strip", z.ZodTypeAny, {
51
+ url: string;
52
+ wait_ms: number;
53
+ wait_for_timeout_ms: number;
54
+ timeout_ms: number;
55
+ submit: boolean;
56
+ full_page: boolean;
57
+ strategies: ("valid" | "empty" | "maxlength" | "special_chars" | "rtl_arabic" | "numbers_only" | "spaces_only" | "boundary" | "xss")[];
58
+ max_fields: number;
59
+ viewport?: {
60
+ width: number;
61
+ height: number;
62
+ } | undefined;
63
+ wait_for?: string | undefined;
64
+ storage_state?: string | undefined;
65
+ selector?: string | undefined;
66
+ }, {
67
+ url: string;
68
+ viewport?: {
69
+ width?: number | undefined;
70
+ height?: number | undefined;
71
+ } | undefined;
72
+ wait_ms?: number | undefined;
73
+ wait_for?: string | undefined;
74
+ wait_for_timeout_ms?: number | undefined;
75
+ storage_state?: string | undefined;
76
+ timeout_ms?: number | undefined;
77
+ selector?: string | undefined;
78
+ submit?: boolean | undefined;
79
+ full_page?: boolean | undefined;
80
+ strategies?: ("valid" | "empty" | "maxlength" | "special_chars" | "rtl_arabic" | "numbers_only" | "spaces_only" | "boundary" | "xss")[] | undefined;
81
+ max_fields?: number | undefined;
82
+ }>;
83
+ export type FormTestInput = z.input<typeof formTestInputSchema>;
84
+ type ParsedFormTestInput = z.output<typeof formTestInputSchema>;
85
+ /**
86
+ * Fill a page's forms with deliberately awkward data and report what breaks.
87
+ *
88
+ * Every strategy gets its own fresh page. That costs a page load each, and it
89
+ * is the whole point: a form filled with valid data and then emptied is not
90
+ * the same test as a form that was empty from the start (the app has seen an
91
+ * input event on every field, live validation has run, and half the frameworks
92
+ * in use will have marked the form "dirty"), and an XSS payload that executed
93
+ * under one strategy would be reported under every later one if they shared a
94
+ * page.
95
+ *
96
+ * Strategies that only fill run concurrently, since filling a form changes
97
+ * nothing outside the browser. With `submit: true` they run one after another
98
+ * instead: submitting is a write to the app under test, and nine of them at
99
+ * once is not a test, it is a load spike.
100
+ *
101
+ * Console and network are cleared once the page has loaded, so everything
102
+ * reported here was caused by the form rather than by the page it lives on.
103
+ */
104
+ export declare function testForms(rawInput: FormTestInput): Promise<CallToolResult>;
105
+ /**
106
+ * One actionable line for a strategy that could not run. Mirrors
107
+ * `describeFailure` in screenshot.ts: match on the failing Playwright call,
108
+ * never on substrings of a user-supplied selector.
109
+ */
110
+ export declare function describeFormTestFailure(input: Pick<ParsedFormTestInput, "url" | "wait_for" | "wait_for_timeout_ms">, error: unknown): string;
111
+ export declare function registerFormTestTool(server: McpServer): void;
112
+ export {};