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.
- package/README.md +895 -15
- package/dist/constants.d.ts +274 -0
- package/dist/constants.js +279 -0
- package/dist/constants.js.map +1 -1
- package/dist/engine/browser.d.ts +20 -4
- package/dist/engine/browser.js +26 -9
- package/dist/engine/browser.js.map +1 -1
- package/dist/engine/clicks.d.ts +221 -0
- package/dist/engine/clicks.js +801 -0
- package/dist/engine/clicks.js.map +1 -0
- package/dist/engine/forms.d.ts +137 -0
- package/dist/engine/forms.js +474 -0
- package/dist/engine/forms.js.map +1 -0
- package/dist/engine/hmr.d.ts +41 -0
- package/dist/engine/hmr.js +91 -0
- package/dist/engine/hmr.js.map +1 -0
- package/dist/engine/inspect.d.ts +31 -0
- package/dist/engine/inspect.js +383 -0
- package/dist/engine/inspect.js.map +1 -0
- package/dist/engine/interaction.d.ts +12 -7
- package/dist/engine/interaction.js +110 -18
- package/dist/engine/interaction.js.map +1 -1
- package/dist/engine/links.d.ts +134 -0
- package/dist/engine/links.js +384 -0
- package/dist/engine/links.js.map +1 -0
- package/dist/engine/mocks.d.ts +53 -0
- package/dist/engine/mocks.js +148 -0
- package/dist/engine/mocks.js.map +1 -0
- package/dist/engine/rtl.d.ts +129 -0
- package/dist/engine/rtl.js +540 -0
- package/dist/engine/rtl.js.map +1 -0
- package/dist/engine/seo.d.ts +189 -0
- package/dist/engine/seo.js +398 -0
- package/dist/engine/seo.js.map +1 -0
- package/dist/engine/snapshot.d.ts +29 -0
- package/dist/engine/snapshot.js +10 -0
- package/dist/engine/snapshot.js.map +1 -0
- package/dist/engine/vue.d.ts +54 -0
- package/dist/engine/vue.js +419 -0
- package/dist/engine/vue.js.map +1 -0
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/accessibility.d.ts +4 -0
- package/dist/tools/accessibility.js +9 -2
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/api-mock.d.ts +405 -0
- package/dist/tools/api-mock.js +186 -0
- package/dist/tools/api-mock.js.map +1 -0
- package/dist/tools/capture.d.ts +90 -26
- package/dist/tools/capture.js +109 -58
- package/dist/tools/capture.js.map +1 -1
- package/dist/tools/compare.d.ts +4 -0
- package/dist/tools/compare.js +16 -5
- package/dist/tools/compare.js.map +1 -1
- package/dist/tools/dead-clicks.d.ts +128 -0
- package/dist/tools/dead-clicks.js +570 -0
- package/dist/tools/dead-clicks.js.map +1 -0
- package/dist/tools/form-test.d.ts +112 -0
- package/dist/tools/form-test.js +477 -0
- package/dist/tools/form-test.js.map +1 -0
- package/dist/tools/index.d.ts +17 -1
- package/dist/tools/index.js +45 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspect.d.ts +78 -0
- package/dist/tools/inspect.js +136 -0
- package/dist/tools/inspect.js.map +1 -0
- package/dist/tools/interact.d.ts +37 -18
- package/dist/tools/interact.js +113 -13
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/links.d.ts +129 -0
- package/dist/tools/links.js +640 -0
- package/dist/tools/links.js.map +1 -0
- package/dist/tools/responsive.d.ts +10 -6
- package/dist/tools/responsive.js +21 -4
- package/dist/tools/responsive.js.map +1 -1
- package/dist/tools/rtl.d.ts +241 -0
- package/dist/tools/rtl.js +410 -0
- package/dist/tools/rtl.js.map +1 -0
- package/dist/tools/save-auth.d.ts +263 -0
- package/dist/tools/save-auth.js +253 -0
- package/dist/tools/save-auth.js.map +1 -0
- package/dist/tools/screenshot.d.ts +4 -0
- package/dist/tools/screenshot.js +15 -4
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/seo.d.ts +113 -0
- package/dist/tools/seo.js +281 -0
- package/dist/tools/seo.js.map +1 -0
- package/dist/tools/snapshot.d.ts +122 -0
- package/dist/tools/snapshot.js +183 -0
- package/dist/tools/snapshot.js.map +1 -0
- package/dist/tools/wait-for.d.ts +107 -0
- package/dist/tools/wait-for.js +167 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/utils/arabic-text.d.ts +14 -0
- package/dist/utils/arabic-text.js +193 -0
- package/dist/utils/arabic-text.js.map +1 -0
- package/dist/utils/budget.d.ts +41 -0
- package/dist/utils/budget.js +182 -0
- package/dist/utils/budget.js.map +1 -0
- package/dist/utils/format.d.ts +11 -1
- package/dist/utils/format.js +27 -4
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/highlight.d.ts +69 -0
- package/dist/utils/highlight.js +181 -0
- package/dist/utils/highlight.js.map +1 -0
- package/dist/utils/link-rules.d.ts +100 -0
- package/dist/utils/link-rules.js +284 -0
- package/dist/utils/link-rules.js.map +1 -0
- package/dist/utils/mock-rules.d.ts +144 -0
- package/dist/utils/mock-rules.js +224 -0
- package/dist/utils/mock-rules.js.map +1 -0
- package/dist/utils/rtl-rules.d.ts +142 -0
- package/dist/utils/rtl-rules.js +296 -0
- package/dist/utils/rtl-rules.js.map +1 -0
- package/dist/utils/seo-rules.d.ts +129 -0
- package/dist/utils/seo-rules.js +726 -0
- package/dist/utils/seo-rules.js.map +1 -0
- package/dist/utils/snapshot-rules.d.ts +33 -0
- package/dist/utils/snapshot-rules.js +111 -0
- package/dist/utils/snapshot-rules.js.map +1 -0
- package/dist/utils/storage-state.d.ts +76 -0
- package/dist/utils/storage-state.js +195 -0
- package/dist/utils/storage-state.js.map +1 -0
- package/dist/utils/style-rules.d.ts +107 -0
- package/dist/utils/style-rules.js +223 -0
- package/dist/utils/style-rules.js.map +1 -0
- package/dist/utils/test-data.d.ts +75 -0
- package/dist/utils/test-data.js +294 -0
- package/dist/utils/test-data.js.map +1 -0
- package/dist/utils/vue-rules.d.ts +72 -0
- package/dist/utils/vue-rules.js +108 -0
- package/dist/utils/vue-rules.js.map +1 -0
- package/package.json +6 -4
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { NAVIGATION_TIMEOUT_MS, SELECTOR_TIMEOUT_MS, SWIPE_STEPS, SWIPE_STEP_DELAY_MS } from "../constants.js";
|
|
3
|
+
import { classifyNavigate } from "../utils/vue-rules.js";
|
|
4
|
+
import { detectVue, routerNavigate } from "./vue.js";
|
|
3
5
|
/**
|
|
4
6
|
* Interaction executor.
|
|
5
7
|
*
|
|
@@ -9,10 +11,31 @@ import { NAVIGATION_TIMEOUT_MS, SELECTOR_TIMEOUT_MS, SWIPE_STEPS, SWIPE_STEP_DEL
|
|
|
9
11
|
* a single actionable line naming the step that failed — a recording is a bad
|
|
10
12
|
* place to surface a 30-line call log.
|
|
11
13
|
*/
|
|
12
|
-
/** Actions `framewatch_capture` can
|
|
13
|
-
export const CAPTURE_ACTIONS = [
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/** Actions a replayable script (`framewatch_capture`, `framewatch_save_auth`) can perform. */
|
|
15
|
+
export const CAPTURE_ACTIONS = [
|
|
16
|
+
"click",
|
|
17
|
+
"tap",
|
|
18
|
+
"type",
|
|
19
|
+
"key",
|
|
20
|
+
"scroll",
|
|
21
|
+
"swipe",
|
|
22
|
+
"hover",
|
|
23
|
+
"select",
|
|
24
|
+
"wait",
|
|
25
|
+
"navigate",
|
|
26
|
+
];
|
|
27
|
+
/** Actions `framewatch_interact` can perform as a one-off — everything but `wait`, which needs a script to sit in. */
|
|
28
|
+
export const INTERACT_ACTIONS = [
|
|
29
|
+
"click",
|
|
30
|
+
"tap",
|
|
31
|
+
"type",
|
|
32
|
+
"key",
|
|
33
|
+
"scroll",
|
|
34
|
+
"swipe",
|
|
35
|
+
"navigate",
|
|
36
|
+
"select",
|
|
37
|
+
"hover",
|
|
38
|
+
];
|
|
16
39
|
/** Steps that need a touch-capable browser context (`hasTouch: true`). */
|
|
17
40
|
const TOUCH_ACTIONS = new Set(["tap", "swipe"]);
|
|
18
41
|
/** True when any step needs a touch-enabled browser context. */
|
|
@@ -41,7 +64,13 @@ export function validateInteraction(interaction) {
|
|
|
41
64
|
? `${action} needs both \`x\` and \`y\` (or a \`selector\`)`
|
|
42
65
|
: `${action} needs a \`selector\`, or \`x\` and \`y\``;
|
|
43
66
|
case "type":
|
|
44
|
-
return typeof value === "string"
|
|
67
|
+
return typeof value === "string"
|
|
68
|
+
? null
|
|
69
|
+
: "type needs a `value` (the text to type; `\n` presses Enter and `\t` presses Tab)";
|
|
70
|
+
case "key":
|
|
71
|
+
return typeof value === "string" && value.length > 0
|
|
72
|
+
? null
|
|
73
|
+
: "key needs a `value` (the key to press, e.g. `Enter`, `Escape`, `ArrowDown` or `Control+a`)";
|
|
45
74
|
case "select":
|
|
46
75
|
if (!selector)
|
|
47
76
|
return "select needs a `selector` naming the <select> element";
|
|
@@ -83,6 +112,8 @@ export function describeInteraction(interaction) {
|
|
|
83
112
|
return `type "${elide(value)}"${selector ? ` into "${selector}"` : ""}`;
|
|
84
113
|
case "select":
|
|
85
114
|
return `select "${elide(value)}"${selector ? ` in "${selector}"` : ""}`;
|
|
115
|
+
case "key":
|
|
116
|
+
return `press "${elide(value)}"${selector ? ` in "${selector}"` : ""}`;
|
|
86
117
|
case "scroll":
|
|
87
118
|
return `scroll${selector ? ` "${selector}"` : ""} by ${delta_x},${delta_y}`;
|
|
88
119
|
case "swipe":
|
|
@@ -117,7 +148,7 @@ export async function executeInteraction(page, interaction, options = {}) {
|
|
|
117
148
|
await sleep(delay);
|
|
118
149
|
const timeout = options.timeout_ms ?? SELECTOR_TIMEOUT_MS;
|
|
119
150
|
try {
|
|
120
|
-
await perform(page, interaction, timeout);
|
|
151
|
+
return (await perform(page, interaction, timeout)) ?? {};
|
|
121
152
|
}
|
|
122
153
|
catch (error) {
|
|
123
154
|
throw new Error(describeFailure(interaction, error), { cause: error });
|
|
@@ -145,12 +176,14 @@ async function perform(page, interaction, timeout) {
|
|
|
145
176
|
await page.touchscreen.tap(x, y);
|
|
146
177
|
return;
|
|
147
178
|
case "type":
|
|
148
|
-
|
|
149
|
-
|
|
179
|
+
await typeValue(page, selector, value, timeout);
|
|
180
|
+
return;
|
|
181
|
+
case "key":
|
|
182
|
+
// A key goes wherever focus is; `selector` is the way to say where that
|
|
183
|
+
// should be without spending a separate click step on it.
|
|
150
184
|
if (selector)
|
|
151
|
-
await page.locator(selector).first().
|
|
152
|
-
|
|
153
|
-
await page.keyboard.type(value);
|
|
185
|
+
await page.locator(selector).first().focus({ timeout });
|
|
186
|
+
await page.keyboard.press(value);
|
|
154
187
|
return;
|
|
155
188
|
case "select":
|
|
156
189
|
await page.locator(selector).first().selectOption(value, { timeout });
|
|
@@ -165,16 +198,68 @@ async function perform(page, interaction, timeout) {
|
|
|
165
198
|
case "swipe":
|
|
166
199
|
await swipe(page, x, y, delta_x, delta_y);
|
|
167
200
|
return;
|
|
168
|
-
case "navigate":
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
201
|
+
case "navigate": {
|
|
202
|
+
// A path on a page with vue-router goes through the router — same
|
|
203
|
+
// document, state kept, no reload — which is what a person clicking a
|
|
204
|
+
// link in the app gets. Anything else, or a path the router does not
|
|
205
|
+
// know, is a full load. Playwright only resolves relative URLs against
|
|
206
|
+
// a context `baseURL`; resolve against the current page instead, which
|
|
207
|
+
// is what a script author means by "navigate to /login".
|
|
208
|
+
const vue = await detectVue(page);
|
|
209
|
+
const plan = classifyNavigate(value, page.url(), vue?.router === true);
|
|
210
|
+
if (plan.via === "router") {
|
|
211
|
+
const outcome = await routerNavigate(page, plan.target);
|
|
212
|
+
if (outcome.ok)
|
|
213
|
+
return { note: " (vue-router)" };
|
|
214
|
+
await page.goto(resolveUrl(value, page.url()), { waitUntil: "commit", timeout: NAVIGATION_TIMEOUT_MS });
|
|
215
|
+
return { note: ` (full load — vue-router: ${outcome.error})` };
|
|
216
|
+
}
|
|
217
|
+
await page.goto(plan.target, { waitUntil: "commit", timeout: NAVIGATION_TIMEOUT_MS });
|
|
173
218
|
return;
|
|
219
|
+
}
|
|
174
220
|
case "wait":
|
|
175
221
|
return;
|
|
176
222
|
}
|
|
177
223
|
}
|
|
224
|
+
/** Characters a `type` value may carry inline to mean a key press. */
|
|
225
|
+
const TYPE_KEYS = { "\n": "Enter", "\t": "Tab" };
|
|
226
|
+
/**
|
|
227
|
+
* Type `value`, pressing Enter for each `\n` in it and Tab for each `\t`.
|
|
228
|
+
*
|
|
229
|
+
* Typing a newline is what ends most forms, and splitting "duo\n" into a type
|
|
230
|
+
* step and a key step is a step the caller should not have to think about.
|
|
231
|
+
* The first text run still goes in with `fill` — it clears the field, so
|
|
232
|
+
* replaying a script twice does not append to what is already there — and
|
|
233
|
+
* everything after a key press is typed on top of wherever focus ended up,
|
|
234
|
+
* which is what a person continuing to type would produce.
|
|
235
|
+
*/
|
|
236
|
+
async function typeValue(page, selector, value, timeout) {
|
|
237
|
+
// Keep the separators: split(/([\n\t])/) yields text, key, text, key, …
|
|
238
|
+
const segments = value.split(/([\n\t])/).filter((segment) => segment !== "");
|
|
239
|
+
// An empty value still means "empty the field", which is `fill("")`.
|
|
240
|
+
if (segments.length === 0)
|
|
241
|
+
segments.push("");
|
|
242
|
+
let focused = false;
|
|
243
|
+
for (const segment of segments) {
|
|
244
|
+
const key = TYPE_KEYS[segment];
|
|
245
|
+
if (key === undefined) {
|
|
246
|
+
// The first run replaces the field's contents; later runs are typed at
|
|
247
|
+
// the caret, since a key press may well have moved focus elsewhere.
|
|
248
|
+
if (selector && !focused)
|
|
249
|
+
await page.locator(selector).first().fill(segment, { timeout });
|
|
250
|
+
else
|
|
251
|
+
await page.keyboard.type(segment);
|
|
252
|
+
focused = true;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
// A value that opens with a key ("\n" alone, meaning "press Enter in this
|
|
256
|
+
// field") still has to land on the element the caller named.
|
|
257
|
+
if (selector && !focused)
|
|
258
|
+
await page.locator(selector).first().focus({ timeout });
|
|
259
|
+
focused = true;
|
|
260
|
+
await page.keyboard.press(key);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
178
263
|
/**
|
|
179
264
|
* A real finger drag: touchStart, several touchMoves, touchEnd.
|
|
180
265
|
*
|
|
@@ -233,8 +318,15 @@ function sleep(ms) {
|
|
|
233
318
|
* adds `delay_ms` where `framewatch_interact` adds `wait_ms`).
|
|
234
319
|
*/
|
|
235
320
|
export const interactionFieldShape = {
|
|
236
|
-
selector: z
|
|
237
|
-
|
|
321
|
+
selector: z
|
|
322
|
+
.string()
|
|
323
|
+
.optional()
|
|
324
|
+
.describe("CSS selector for click/tap/type/select/hover targets, the field to focus first for `key`, or the scroll container"),
|
|
325
|
+
value: z
|
|
326
|
+
.string()
|
|
327
|
+
.optional()
|
|
328
|
+
.describe("Text to type (`\n` in it presses Enter, `\t` presses Tab), key to press for `key` " +
|
|
329
|
+
"(e.g. `Enter`, `Escape`, `ArrowDown`, `Control+a`), option value to select, or URL to navigate to"),
|
|
238
330
|
x: z.number().optional().describe("X coordinate for click/tap/swipe (used when no selector is given)"),
|
|
239
331
|
y: z.number().optional().describe("Y coordinate for click/tap/swipe (used when no selector is given)"),
|
|
240
332
|
delta_x: z.number().optional().describe("Horizontal distance for scroll/swipe"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interaction.js","sourceRoot":"","sources":["../../src/engine/interaction.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { Page } from \"playwright\";\nimport { NAVIGATION_TIMEOUT_MS, SELECTOR_TIMEOUT_MS, SWIPE_STEPS, SWIPE_STEP_DELAY_MS } from \"../constants.js\";\n\n/**\n * Interaction executor.\n *\n * Turns one declarative interaction step (see CLAUDE.md \"Interactions as JSON\n * script\") into real input on a Playwright page. Every step is validated\n * before anything touches the page, and every Playwright failure is reduced to\n * a single actionable line naming the step that failed — a recording is a bad\n * place to surface a 30-line call log.\n */\n\n/** Actions `framewatch_capture` can replay during a recording. */\nexport const CAPTURE_ACTIONS = [\"click\", \"tap\", \"type\", \"scroll\", \"swipe\", \"wait\", \"navigate\"] as const;\n\n/** Actions `framewatch_interact` can perform as a one-off. */\nexport const INTERACT_ACTIONS = [\"click\", \"tap\", \"type\", \"scroll\", \"swipe\", \"navigate\", \"select\", \"hover\"] as const;\n\nexport type InteractionAction = (typeof CAPTURE_ACTIONS)[number] | (typeof INTERACT_ACTIONS)[number];\n\nexport interface Interaction {\n action: InteractionAction;\n /** CSS selector for click/tap/type/select/hover, or the scroll container. */\n selector?: string;\n /** Text to type, option value to select, or URL to navigate to. */\n value?: string;\n x?: number;\n y?: number;\n delta_x?: number;\n delta_y?: number;\n /** Wait this long *before* performing the action. */\n delay_ms?: number;\n}\n\nexport interface ExecuteOptions {\n /** Timeout for selector-based actions. Default SELECTOR_TIMEOUT_MS. */\n timeout_ms?: number;\n}\n\n/** Steps that need a touch-capable browser context (`hasTouch: true`). */\nconst TOUCH_ACTIONS = new Set<InteractionAction>([\"tap\", \"swipe\"]);\n\n/** True when any step needs a touch-enabled browser context. */\nexport function needsTouch(interactions: readonly Interaction[]): boolean {\n return interactions.some((i) => TOUCH_ACTIONS.has(i.action));\n}\n\n/**\n * Check that a step carries the fields its action needs. Returns a message\n * naming what is missing, or null when the step is executable. Used both by\n * the tools (at input-validation time, before a browser is even launched) and\n * by `executeInteraction` itself.\n */\nexport function validateInteraction(interaction: Interaction): string | null {\n const { action, selector, value, x, y, delta_x, delta_y } = interaction;\n const hasPoint = typeof x === \"number\" && typeof y === \"number\";\n const hasPartialPoint = typeof x === \"number\" || typeof y === \"number\";\n\n switch (action) {\n case \"click\":\n case \"tap\":\n case \"hover\":\n if (selector) return null;\n if (hasPoint) return null;\n return hasPartialPoint\n ? `${action} needs both \\`x\\` and \\`y\\` (or a \\`selector\\`)`\n : `${action} needs a \\`selector\\`, or \\`x\\` and \\`y\\``;\n\n case \"type\":\n return typeof value === \"string\" ? null : \"type needs a `value` (the text to type)\";\n\n case \"select\":\n if (!selector) return \"select needs a `selector` naming the <select> element\";\n return typeof value === \"string\" ? null : \"select needs a `value` (the option to choose)\";\n\n case \"scroll\":\n return typeof delta_x === \"number\" || typeof delta_y === \"number\"\n ? null\n : \"scroll needs `delta_y` (and/or `delta_x`) — the distance to scroll\";\n\n case \"swipe\":\n if (!hasPoint) return \"swipe needs `x` and `y` — where the finger goes down\";\n return typeof delta_x === \"number\" || typeof delta_y === \"number\"\n ? null\n : \"swipe needs `delta_x` and/or `delta_y` — how far the finger travels\";\n\n case \"navigate\":\n return typeof value === \"string\" && value.length > 0 ? null : \"navigate needs a `value` (the URL to go to)\";\n\n case \"wait\":\n return null;\n\n default:\n return `unknown action \"${String(action)}\"`;\n }\n}\n\n/** Longest `value` printed in a step description before it is elided. */\nconst DESCRIBE_VALUE_MAX = 60;\n\n/**\n * One-line, human-readable summary of a step, used in error messages and in\n * the capture summary. Long values (a pasted token, a password) are elided\n * rather than echoed in full.\n */\nexport function describeInteraction(interaction: Interaction): string {\n const { action, selector, value, x, y, delta_x = 0, delta_y = 0, delay_ms } = interaction;\n const target = selector ? ` \"${selector}\"` : typeof x === \"number\" && typeof y === \"number\" ? ` at ${x},${y}` : \"\";\n\n switch (action) {\n case \"click\":\n case \"tap\":\n case \"hover\":\n return `${action}${target}`;\n case \"type\":\n return `type \"${elide(value)}\"${selector ? ` into \"${selector}\"` : \"\"}`;\n case \"select\":\n return `select \"${elide(value)}\"${selector ? ` in \"${selector}\"` : \"\"}`;\n case \"scroll\":\n return `scroll${selector ? ` \"${selector}\"` : \"\"} by ${delta_x},${delta_y}`;\n case \"swipe\":\n return `swipe${target} by ${delta_x},${delta_y}`;\n case \"navigate\":\n return `navigate to ${elide(value)}`;\n case \"wait\":\n return `wait ${delay_ms ?? 0}ms`;\n default:\n return String(action);\n }\n}\n\nfunction elide(value: string | undefined): string {\n if (value === undefined) return \"\";\n return value.length > DESCRIBE_VALUE_MAX ? `${value.slice(0, DESCRIBE_VALUE_MAX)}…` : value;\n}\n\n/**\n * Perform one interaction on `page`.\n *\n * Waits `delay_ms` first (that is what `delay_ms` means: \"wait before this\n * action\"), so a `wait` step is simply a step with no action of its own.\n * Throws a single-line Error naming the step on any failure; the page is never\n * touched when the step is invalid.\n */\nexport async function executeInteraction(page: Page, interaction: Interaction, options: ExecuteOptions = {}): Promise<void> {\n const problem = validateInteraction(interaction);\n if (problem) throw new Error(problem);\n\n const delay = interaction.delay_ms;\n if (typeof delay === \"number\" && delay > 0) await sleep(delay);\n\n const timeout = options.timeout_ms ?? SELECTOR_TIMEOUT_MS;\n try {\n await perform(page, interaction, timeout);\n } catch (error) {\n throw new Error(describeFailure(interaction, error), { cause: error });\n }\n}\n\nasync function perform(page: Page, interaction: Interaction, timeout: number): Promise<void> {\n const { action, selector, value, x, y, delta_x = 0, delta_y = 0 } = interaction;\n\n switch (action) {\n case \"click\":\n if (selector) await page.locator(selector).first().click({ timeout });\n else await page.mouse.click(x!, y!);\n return;\n\n case \"hover\":\n if (selector) await page.locator(selector).first().hover({ timeout });\n else await page.mouse.move(x!, y!);\n return;\n\n case \"tap\":\n if (selector) await page.locator(selector).first().tap({ timeout });\n else await page.touchscreen.tap(x!, y!);\n return;\n\n case \"type\":\n // `fill` clears the field first and dispatches a single input event, so\n // replaying a script twice does not append to what is already there.\n if (selector) await page.locator(selector).first().fill(value!, { timeout });\n else await page.keyboard.type(value!);\n return;\n\n case \"select\":\n await page.locator(selector!).first().selectOption(value!, { timeout });\n return;\n\n case \"scroll\":\n // With a selector, put the pointer over that element first so the wheel\n // scrolls it rather than the page behind it.\n if (selector) await page.locator(selector).first().hover({ timeout });\n await page.mouse.wheel(delta_x, delta_y);\n return;\n\n case \"swipe\":\n await swipe(page, x!, y!, delta_x, delta_y);\n return;\n\n case \"navigate\":\n // Playwright only resolves relative URLs against a context `baseURL`;\n // resolve against the current page instead, which is what a script\n // author means by \"navigate to /login\".\n await page.goto(resolveUrl(value!, page.url()), { waitUntil: \"commit\", timeout: NAVIGATION_TIMEOUT_MS });\n return;\n\n case \"wait\":\n return;\n }\n}\n\n/**\n * A real finger drag: touchStart, several touchMoves, touchEnd.\n *\n * Playwright's Touchscreen can only tap, so the drag goes through CDP (we are\n * Chromium-only). Intermediate moves matter — a carousel or pull-to-refresh\n * needs the travel and its velocity, not a teleport — and each one is spaced\n * about a frame apart so the page's own velocity maths sees a plausible gesture.\n */\nasync function swipe(page: Page, x: number, y: number, deltaX: number, deltaY: number): Promise<void> {\n const cdp = await page.context().newCDPSession(page);\n try {\n await cdp.send(\"Input.dispatchTouchEvent\", { type: \"touchStart\", touchPoints: [{ x, y }] });\n for (let step = 1; step <= SWIPE_STEPS; step++) {\n await sleep(SWIPE_STEP_DELAY_MS);\n await cdp.send(\"Input.dispatchTouchEvent\", {\n // Interpolate from the origin (not from the previous point) so the last\n // move lands exactly on x + deltaX, y + deltaY.\n type: \"touchMove\",\n touchPoints: [{ x: x + (deltaX * step) / SWIPE_STEPS, y: y + (deltaY * step) / SWIPE_STEPS }],\n });\n }\n await cdp.send(\"Input.dispatchTouchEvent\", { type: \"touchEnd\", touchPoints: [] });\n } finally {\n await cdp.detach().catch(() => {});\n }\n}\n\nfunction resolveUrl(value: string, base: string): string {\n try {\n return new URL(value, base).href;\n } catch {\n return value;\n }\n}\n\n/**\n * Reduce a Playwright error to one actionable line naming the step. Touch\n * failures get their own wording: the fix is a context option the caller\n * controls, not anything about the page.\n */\nfunction describeFailure(interaction: Interaction, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split(\"\\n\")[0];\n const step = describeInteraction(interaction);\n\n if (/hasTouch|does not support tap/i.test(message)) {\n return `${step} failed: this page is not touch-enabled. Touch is turned on automatically for tap/swipe steps — if you see this, the page was opened without it.`;\n }\n return `${step} failed: ${firstLine}`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The shared zod fields every interaction step accepts. Both tools build their\n * own object around these (the action enums differ, and `framewatch_capture`\n * adds `delay_ms` where `framewatch_interact` adds `wait_ms`).\n */\nexport const interactionFieldShape = {\n selector: z.string().optional().describe(\"CSS selector for click/tap/type/select/hover targets, or the scroll container\"),\n value: z.string().optional().describe(\"Text to type, option value to select, or URL to navigate to\"),\n x: z.number().optional().describe(\"X coordinate for click/tap/swipe (used when no selector is given)\"),\n y: z.number().optional().describe(\"Y coordinate for click/tap/swipe (used when no selector is given)\"),\n delta_x: z.number().optional().describe(\"Horizontal distance for scroll/swipe\"),\n delta_y: z.number().optional().describe(\"Vertical distance for scroll/swipe\"),\n};\n\n/**\n * zod `superRefine` hook that rejects a step missing the fields its action\n * needs, so an unusable script is reported as invalid input instead of failing\n * half way through a recording.\n */\nexport function refineInteraction(value: Interaction, ctx: z.RefinementCtx): void {\n const problem = validateInteraction(value);\n if (problem !== null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem, path: [\"action\"] });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"interaction.js","sourceRoot":"","sources":["../../src/engine/interaction.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { Page } from \"playwright\";\nimport { NAVIGATION_TIMEOUT_MS, SELECTOR_TIMEOUT_MS, SWIPE_STEPS, SWIPE_STEP_DELAY_MS } from \"../constants.js\";\nimport { classifyNavigate } from \"../utils/vue-rules.js\";\nimport { detectVue, routerNavigate } from \"./vue.js\";\n\n/**\n * Interaction executor.\n *\n * Turns one declarative interaction step (see CLAUDE.md \"Interactions as JSON\n * script\") into real input on a Playwright page. Every step is validated\n * before anything touches the page, and every Playwright failure is reduced to\n * a single actionable line naming the step that failed — a recording is a bad\n * place to surface a 30-line call log.\n */\n\n/** Actions a replayable script (`framewatch_capture`, `framewatch_save_auth`) can perform. */\nexport const CAPTURE_ACTIONS = [\n \"click\",\n \"tap\",\n \"type\",\n \"key\",\n \"scroll\",\n \"swipe\",\n \"hover\",\n \"select\",\n \"wait\",\n \"navigate\",\n] as const;\n\n/** Actions `framewatch_interact` can perform as a one-off — everything but `wait`, which needs a script to sit in. */\nexport const INTERACT_ACTIONS = [\n \"click\",\n \"tap\",\n \"type\",\n \"key\",\n \"scroll\",\n \"swipe\",\n \"navigate\",\n \"select\",\n \"hover\",\n] as const;\n\nexport type InteractionAction = (typeof CAPTURE_ACTIONS)[number] | (typeof INTERACT_ACTIONS)[number];\n\nexport interface Interaction {\n action: InteractionAction;\n /** CSS selector for click/tap/type/select/hover, the field to focus for `key`, or the scroll container. */\n selector?: string;\n /** Text to type, key to press, option value to select, or URL to navigate to. */\n value?: string;\n x?: number;\n y?: number;\n delta_x?: number;\n delta_y?: number;\n /** Wait this long *before* performing the action. */\n delay_ms?: number;\n}\n\nexport interface ExecuteOptions {\n /** Timeout for selector-based actions. Default SELECTOR_TIMEOUT_MS. */\n timeout_ms?: number;\n}\n\n/** What a step has to add to its own description once it has run. */\nexport interface InteractionOutcome {\n /** Appended to `describeInteraction(step)`, e.g. ` (vue-router)`. */\n note?: string;\n}\n\n/** Steps that need a touch-capable browser context (`hasTouch: true`). */\nconst TOUCH_ACTIONS = new Set<InteractionAction>([\"tap\", \"swipe\"]);\n\n/** True when any step needs a touch-enabled browser context. */\nexport function needsTouch(interactions: readonly Interaction[]): boolean {\n return interactions.some((i) => TOUCH_ACTIONS.has(i.action));\n}\n\n/**\n * Check that a step carries the fields its action needs. Returns a message\n * naming what is missing, or null when the step is executable. Used both by\n * the tools (at input-validation time, before a browser is even launched) and\n * by `executeInteraction` itself.\n */\nexport function validateInteraction(interaction: Interaction): string | null {\n const { action, selector, value, x, y, delta_x, delta_y } = interaction;\n const hasPoint = typeof x === \"number\" && typeof y === \"number\";\n const hasPartialPoint = typeof x === \"number\" || typeof y === \"number\";\n\n switch (action) {\n case \"click\":\n case \"tap\":\n case \"hover\":\n if (selector) return null;\n if (hasPoint) return null;\n return hasPartialPoint\n ? `${action} needs both \\`x\\` and \\`y\\` (or a \\`selector\\`)`\n : `${action} needs a \\`selector\\`, or \\`x\\` and \\`y\\``;\n\n case \"type\":\n return typeof value === \"string\"\n ? null\n : \"type needs a `value` (the text to type; `\\n` presses Enter and `\\t` presses Tab)\";\n\n case \"key\":\n return typeof value === \"string\" && value.length > 0\n ? null\n : \"key needs a `value` (the key to press, e.g. `Enter`, `Escape`, `ArrowDown` or `Control+a`)\";\n\n case \"select\":\n if (!selector) return \"select needs a `selector` naming the <select> element\";\n return typeof value === \"string\" ? null : \"select needs a `value` (the option to choose)\";\n\n case \"scroll\":\n return typeof delta_x === \"number\" || typeof delta_y === \"number\"\n ? null\n : \"scroll needs `delta_y` (and/or `delta_x`) — the distance to scroll\";\n\n case \"swipe\":\n if (!hasPoint) return \"swipe needs `x` and `y` — where the finger goes down\";\n return typeof delta_x === \"number\" || typeof delta_y === \"number\"\n ? null\n : \"swipe needs `delta_x` and/or `delta_y` — how far the finger travels\";\n\n case \"navigate\":\n return typeof value === \"string\" && value.length > 0 ? null : \"navigate needs a `value` (the URL to go to)\";\n\n case \"wait\":\n return null;\n\n default:\n return `unknown action \"${String(action)}\"`;\n }\n}\n\n/** Longest `value` printed in a step description before it is elided. */\nconst DESCRIBE_VALUE_MAX = 60;\n\n/**\n * One-line, human-readable summary of a step, used in error messages and in\n * the capture summary. Long values (a pasted token, a password) are elided\n * rather than echoed in full.\n */\nexport function describeInteraction(interaction: Interaction): string {\n const { action, selector, value, x, y, delta_x = 0, delta_y = 0, delay_ms } = interaction;\n const target = selector ? ` \"${selector}\"` : typeof x === \"number\" && typeof y === \"number\" ? ` at ${x},${y}` : \"\";\n\n switch (action) {\n case \"click\":\n case \"tap\":\n case \"hover\":\n return `${action}${target}`;\n case \"type\":\n return `type \"${elide(value)}\"${selector ? ` into \"${selector}\"` : \"\"}`;\n case \"select\":\n return `select \"${elide(value)}\"${selector ? ` in \"${selector}\"` : \"\"}`;\n case \"key\":\n return `press \"${elide(value)}\"${selector ? ` in \"${selector}\"` : \"\"}`;\n case \"scroll\":\n return `scroll${selector ? ` \"${selector}\"` : \"\"} by ${delta_x},${delta_y}`;\n case \"swipe\":\n return `swipe${target} by ${delta_x},${delta_y}`;\n case \"navigate\":\n return `navigate to ${elide(value)}`;\n case \"wait\":\n return `wait ${delay_ms ?? 0}ms`;\n default:\n return String(action);\n }\n}\n\nfunction elide(value: string | undefined): string {\n if (value === undefined) return \"\";\n return value.length > DESCRIBE_VALUE_MAX ? `${value.slice(0, DESCRIBE_VALUE_MAX)}…` : value;\n}\n\n/**\n * Perform one interaction on `page`.\n *\n * Waits `delay_ms` first (that is what `delay_ms` means: \"wait before this\n * action\"), so a `wait` step is simply a step with no action of its own.\n * Throws a single-line Error naming the step on any failure; the page is never\n * touched when the step is invalid.\n */\nexport async function executeInteraction(\n page: Page,\n interaction: Interaction,\n options: ExecuteOptions = {},\n): Promise<InteractionOutcome> {\n const problem = validateInteraction(interaction);\n if (problem) throw new Error(problem);\n\n const delay = interaction.delay_ms;\n if (typeof delay === \"number\" && delay > 0) await sleep(delay);\n\n const timeout = options.timeout_ms ?? SELECTOR_TIMEOUT_MS;\n try {\n return (await perform(page, interaction, timeout)) ?? {};\n } catch (error) {\n throw new Error(describeFailure(interaction, error), { cause: error });\n }\n}\n\nasync function perform(page: Page, interaction: Interaction, timeout: number): Promise<InteractionOutcome | void> {\n const { action, selector, value, x, y, delta_x = 0, delta_y = 0 } = interaction;\n\n switch (action) {\n case \"click\":\n if (selector) await page.locator(selector).first().click({ timeout });\n else await page.mouse.click(x!, y!);\n return;\n\n case \"hover\":\n if (selector) await page.locator(selector).first().hover({ timeout });\n else await page.mouse.move(x!, y!);\n return;\n\n case \"tap\":\n if (selector) await page.locator(selector).first().tap({ timeout });\n else await page.touchscreen.tap(x!, y!);\n return;\n\n case \"type\":\n await typeValue(page, selector, value!, timeout);\n return;\n\n case \"key\":\n // A key goes wherever focus is; `selector` is the way to say where that\n // should be without spending a separate click step on it.\n if (selector) await page.locator(selector).first().focus({ timeout });\n await page.keyboard.press(value!);\n return;\n\n case \"select\":\n await page.locator(selector!).first().selectOption(value!, { timeout });\n return;\n\n case \"scroll\":\n // With a selector, put the pointer over that element first so the wheel\n // scrolls it rather than the page behind it.\n if (selector) await page.locator(selector).first().hover({ timeout });\n await page.mouse.wheel(delta_x, delta_y);\n return;\n\n case \"swipe\":\n await swipe(page, x!, y!, delta_x, delta_y);\n return;\n\n case \"navigate\": {\n // A path on a page with vue-router goes through the router — same\n // document, state kept, no reload — which is what a person clicking a\n // link in the app gets. Anything else, or a path the router does not\n // know, is a full load. Playwright only resolves relative URLs against\n // a context `baseURL`; resolve against the current page instead, which\n // is what a script author means by \"navigate to /login\".\n const vue = await detectVue(page);\n const plan = classifyNavigate(value!, page.url(), vue?.router === true);\n if (plan.via === \"router\") {\n const outcome = await routerNavigate(page, plan.target);\n if (outcome.ok) return { note: \" (vue-router)\" };\n await page.goto(resolveUrl(value!, page.url()), { waitUntil: \"commit\", timeout: NAVIGATION_TIMEOUT_MS });\n return { note: ` (full load — vue-router: ${outcome.error})` };\n }\n await page.goto(plan.target, { waitUntil: \"commit\", timeout: NAVIGATION_TIMEOUT_MS });\n return;\n }\n\n case \"wait\":\n return;\n }\n}\n\n/** Characters a `type` value may carry inline to mean a key press. */\nconst TYPE_KEYS: Record<string, string> = { \"\\n\": \"Enter\", \"\\t\": \"Tab\" };\n\n/**\n * Type `value`, pressing Enter for each `\\n` in it and Tab for each `\\t`.\n *\n * Typing a newline is what ends most forms, and splitting \"duo\\n\" into a type\n * step and a key step is a step the caller should not have to think about.\n * The first text run still goes in with `fill` — it clears the field, so\n * replaying a script twice does not append to what is already there — and\n * everything after a key press is typed on top of wherever focus ended up,\n * which is what a person continuing to type would produce.\n */\nasync function typeValue(page: Page, selector: string | undefined, value: string, timeout: number): Promise<void> {\n // Keep the separators: split(/([\\n\\t])/) yields text, key, text, key, …\n const segments = value.split(/([\\n\\t])/).filter((segment) => segment !== \"\");\n // An empty value still means \"empty the field\", which is `fill(\"\")`.\n if (segments.length === 0) segments.push(\"\");\n\n let focused = false;\n for (const segment of segments) {\n const key = TYPE_KEYS[segment];\n if (key === undefined) {\n // The first run replaces the field's contents; later runs are typed at\n // the caret, since a key press may well have moved focus elsewhere.\n if (selector && !focused) await page.locator(selector).first().fill(segment, { timeout });\n else await page.keyboard.type(segment);\n focused = true;\n continue;\n }\n // A value that opens with a key (\"\\n\" alone, meaning \"press Enter in this\n // field\") still has to land on the element the caller named.\n if (selector && !focused) await page.locator(selector).first().focus({ timeout });\n focused = true;\n await page.keyboard.press(key);\n }\n}\n\n/**\n * A real finger drag: touchStart, several touchMoves, touchEnd.\n *\n * Playwright's Touchscreen can only tap, so the drag goes through CDP (we are\n * Chromium-only). Intermediate moves matter — a carousel or pull-to-refresh\n * needs the travel and its velocity, not a teleport — and each one is spaced\n * about a frame apart so the page's own velocity maths sees a plausible gesture.\n */\nasync function swipe(page: Page, x: number, y: number, deltaX: number, deltaY: number): Promise<void> {\n const cdp = await page.context().newCDPSession(page);\n try {\n await cdp.send(\"Input.dispatchTouchEvent\", { type: \"touchStart\", touchPoints: [{ x, y }] });\n for (let step = 1; step <= SWIPE_STEPS; step++) {\n await sleep(SWIPE_STEP_DELAY_MS);\n await cdp.send(\"Input.dispatchTouchEvent\", {\n // Interpolate from the origin (not from the previous point) so the last\n // move lands exactly on x + deltaX, y + deltaY.\n type: \"touchMove\",\n touchPoints: [{ x: x + (deltaX * step) / SWIPE_STEPS, y: y + (deltaY * step) / SWIPE_STEPS }],\n });\n }\n await cdp.send(\"Input.dispatchTouchEvent\", { type: \"touchEnd\", touchPoints: [] });\n } finally {\n await cdp.detach().catch(() => {});\n }\n}\n\nfunction resolveUrl(value: string, base: string): string {\n try {\n return new URL(value, base).href;\n } catch {\n return value;\n }\n}\n\n/**\n * Reduce a Playwright error to one actionable line naming the step. Touch\n * failures get their own wording: the fix is a context option the caller\n * controls, not anything about the page.\n */\nfunction describeFailure(interaction: Interaction, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split(\"\\n\")[0];\n const step = describeInteraction(interaction);\n\n if (/hasTouch|does not support tap/i.test(message)) {\n return `${step} failed: this page is not touch-enabled. Touch is turned on automatically for tap/swipe steps — if you see this, the page was opened without it.`;\n }\n return `${step} failed: ${firstLine}`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The shared zod fields every interaction step accepts. Both tools build their\n * own object around these (the action enums differ, and `framewatch_capture`\n * adds `delay_ms` where `framewatch_interact` adds `wait_ms`).\n */\nexport const interactionFieldShape = {\n selector: z\n .string()\n .optional()\n .describe(\n \"CSS selector for click/tap/type/select/hover targets, the field to focus first for `key`, or the scroll container\",\n ),\n value: z\n .string()\n .optional()\n .describe(\n \"Text to type (`\\n` in it presses Enter, `\\t` presses Tab), key to press for `key` \" +\n \"(e.g. `Enter`, `Escape`, `ArrowDown`, `Control+a`), option value to select, or URL to navigate to\",\n ),\n x: z.number().optional().describe(\"X coordinate for click/tap/swipe (used when no selector is given)\"),\n y: z.number().optional().describe(\"Y coordinate for click/tap/swipe (used when no selector is given)\"),\n delta_x: z.number().optional().describe(\"Horizontal distance for scroll/swipe\"),\n delta_y: z.number().optional().describe(\"Vertical distance for scroll/swipe\"),\n};\n\n/**\n * zod `superRefine` hook that rejects a step missing the fields its action\n * needs, so an unusable script is reported as invalid input instead of failing\n * half way through a recording.\n */\nexport function refineInteraction(value: Interaction, ctx: z.RefinementCtx): void {\n const problem = validateInteraction(value);\n if (problem !== null) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem, path: [\"action\"] });\n }\n}\n"]}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright";
|
|
2
|
+
/**
|
|
3
|
+
* Link engine.
|
|
4
|
+
*
|
|
5
|
+
* Three jobs, and nothing that judges: collect every address the rendered page
|
|
6
|
+
* refers to, remember what the browser itself made of the ones it loaded, and
|
|
7
|
+
* check the rest over HTTP. What any of that *means* is `utils/link-rules.ts`,
|
|
8
|
+
* which is pure and therefore testable without a socket.
|
|
9
|
+
*
|
|
10
|
+
* Two decisions shape everything here.
|
|
11
|
+
*
|
|
12
|
+
* The page is read after it has rendered, not from its HTML source, for the
|
|
13
|
+
* same reason the SEO engine does: a component framework ships an empty
|
|
14
|
+
* `<div id="app">` and writes every link into it afterwards, so reading the
|
|
15
|
+
* source would report a client-rendered site as having no links at all.
|
|
16
|
+
*
|
|
17
|
+
* And the checks go out through the browser context rather than through
|
|
18
|
+
* `fetch`, so they carry the same cookies, headers and proxy settings the page
|
|
19
|
+
* itself was loaded with. A link behind a login is then checked as the
|
|
20
|
+
* signed-in visitor sees it instead of coming back 401 for everyone.
|
|
21
|
+
*/
|
|
22
|
+
/** A link a person clicks, or a resource the page pulls in on its own. */
|
|
23
|
+
export type LinkRole = "link" | "resource";
|
|
24
|
+
/** One address the rendered page refers to, exactly as it was written. */
|
|
25
|
+
export interface RawLink {
|
|
26
|
+
/** The href/src as authored — relative, empty, `javascript:` and all. */
|
|
27
|
+
href: string;
|
|
28
|
+
role: LinkRole;
|
|
29
|
+
tag: string;
|
|
30
|
+
/** Which attribute carried it, so a `srcset` candidate is not mistaken for a `src`. */
|
|
31
|
+
attribute: string;
|
|
32
|
+
/** How the element is named in the report — `a "Pricing"`, `img#hero`. */
|
|
33
|
+
description: string;
|
|
34
|
+
/** A CSS selector for the element, recomputed each visit rather than remembered. */
|
|
35
|
+
selector: string;
|
|
36
|
+
/** Which match of `selector` this is, for the common case where it is not unique. */
|
|
37
|
+
match_index: number;
|
|
38
|
+
rel?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface CollectLinksOptions {
|
|
41
|
+
/** Only look inside this. Defaults to the whole document. */
|
|
42
|
+
selector?: string;
|
|
43
|
+
/** Also collect the things the page loads for itself — images, scripts, stylesheets. */
|
|
44
|
+
include_resources?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read every address out of the rendered document, in document order.
|
|
48
|
+
*
|
|
49
|
+
* Anchors come first because they are what the tool is about; a page's own
|
|
50
|
+
* resources are collected after them so that a `max_links` cap spends itself
|
|
51
|
+
* on links before it spends itself on sprite sheets.
|
|
52
|
+
*/
|
|
53
|
+
export declare function collectLinks(page: Page, options?: CollectLinksOptions): Promise<RawLink[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Which of these fragments actually point at something.
|
|
56
|
+
*
|
|
57
|
+
* A `#pricing` that matches no element is a broken link that no amount of HTTP
|
|
58
|
+
* checking will ever find — the request succeeds, the page loads, and the
|
|
59
|
+
* visitor simply does not go where the link said. It is only answerable with
|
|
60
|
+
* the document in hand, which is the one thing a link checker without a
|
|
61
|
+
* browser does not have.
|
|
62
|
+
*
|
|
63
|
+
* Both spellings count: an `id`, and the `<a name>` that predates it and still
|
|
64
|
+
* works in every browser.
|
|
65
|
+
*/
|
|
66
|
+
export declare function findFragmentTargets(page: Page, fragments: readonly string[]): Promise<string[]>;
|
|
67
|
+
/** What Chromium made of one address it fetched for itself. */
|
|
68
|
+
export interface ObservedLoad {
|
|
69
|
+
status?: number;
|
|
70
|
+
/** Chromium's own error text, e.g. `net::ERR_NAME_NOT_RESOLVED`. */
|
|
71
|
+
failure?: string;
|
|
72
|
+
/** Every URL in the redirect chain, first to last. */
|
|
73
|
+
chain: string[];
|
|
74
|
+
}
|
|
75
|
+
export interface LoadWatch {
|
|
76
|
+
get(url: string): ObservedLoad | undefined;
|
|
77
|
+
detach(): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Record what the browser makes of every request it issues.
|
|
81
|
+
*
|
|
82
|
+
* The page has already fetched its own stylesheets, scripts and images by the
|
|
83
|
+
* time anything is checked, and Chromium's answer is better than a second
|
|
84
|
+
* request would be: it is what actually happened, in the right order, with the
|
|
85
|
+
* right headers, and it costs nothing. Re-requesting them all would double the
|
|
86
|
+
* load on the site under test and could easily disagree with the browser —
|
|
87
|
+
* which is the one verdict that matters.
|
|
88
|
+
*
|
|
89
|
+
* Intermediate redirects are skipped and recorded from the far end instead, so
|
|
90
|
+
* a resource that 301s to a working URL is not reported as a bare 301. An
|
|
91
|
+
* aborted request is recorded as nothing at all: `net::ERR_ABORTED` is what a
|
|
92
|
+
* cancelled preload and a navigation away both look like, and neither is a
|
|
93
|
+
* broken link.
|
|
94
|
+
*/
|
|
95
|
+
export declare function watchLoads(page: Page): LoadWatch;
|
|
96
|
+
/** The outcome of one check, as the checker saw it — no judgement attached. */
|
|
97
|
+
export interface CheckOutcome {
|
|
98
|
+
/**
|
|
99
|
+
* Every URL requested, starting with the one that was asked for. One entry
|
|
100
|
+
* means no redirect was followed; a repeat means the chain came back round.
|
|
101
|
+
*/
|
|
102
|
+
chain: string[];
|
|
103
|
+
/** The final status, when one arrived. */
|
|
104
|
+
status?: number;
|
|
105
|
+
/** The transport error, when the request never completed. */
|
|
106
|
+
error?: string;
|
|
107
|
+
/** Which verb produced the final answer. */
|
|
108
|
+
method: "HEAD" | "GET";
|
|
109
|
+
/** The chain was still going when `max_redirects` ran out. */
|
|
110
|
+
hops_exceeded?: boolean;
|
|
111
|
+
content_type?: string;
|
|
112
|
+
/** This verdict came from the browser's own load rather than from a fresh request. */
|
|
113
|
+
observed?: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface CheckOptions {
|
|
116
|
+
timeout_ms: number;
|
|
117
|
+
max_redirects?: number;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Ask a server about one URL, following its redirects by hand.
|
|
121
|
+
*
|
|
122
|
+
* Redirects are followed one hop at a time (`maxRedirects: 0`) rather than
|
|
123
|
+
* letting Playwright chase them, because the chain *is* part of the finding: a
|
|
124
|
+
* link that reaches its destination through four hops works and is still worth
|
|
125
|
+
* fixing, and a link that redirects into a 404 is a different bug from a plain
|
|
126
|
+
* 404.
|
|
127
|
+
*
|
|
128
|
+
* HEAD is an optimisation, never the test. A great many servers — CDNs, WAFs,
|
|
129
|
+
* a fair number of frameworks — answer 405 or 404 to a HEAD and serve the very
|
|
130
|
+
* same URL to a GET, so anything that comes back an error is asked for again
|
|
131
|
+
* properly before it is called broken. That costs a second request only on
|
|
132
|
+
* links that were about to be reported, which is the right place to spend it.
|
|
133
|
+
*/
|
|
134
|
+
export declare function checkUrl(context: BrowserContext, url: string, options: CheckOptions): Promise<CheckOutcome>;
|