@tontoko/fast-playwright-mcp 0.1.0 → 0.1.2
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 +116 -96
- package/lib/diagnostics/page-analyzer.js +2 -1
- package/lib/schemas/expectation.js +5 -5
- package/lib/services/selector-resolver.js +541 -0
- package/lib/tab.js +47 -24
- package/lib/tools/base-tool-handler.js +3 -19
- package/lib/tools/batch-execute.js +1 -1
- package/lib/tools/dialogs.js +4 -4
- package/lib/tools/evaluate.js +14 -10
- package/lib/tools/files.js +3 -3
- package/lib/tools/inspect-html.js +238 -0
- package/lib/tools/keyboard.js +18 -20
- package/lib/tools/mouse.js +10 -10
- package/lib/tools/navigate.js +8 -8
- package/lib/tools/network.js +9 -9
- package/lib/tools/screenshot.js +17 -15
- package/lib/tools/shared-element-utils.js +60 -0
- package/lib/tools/snapshot.js +35 -37
- package/lib/tools/tabs.js +10 -10
- package/lib/tools/wait.js +5 -5
- package/lib/tools.js +2 -0
- package/lib/types/batch.js +2 -2
- package/lib/types/html-inspection.js +106 -0
- package/lib/types/selectors.js +126 -0
- package/lib/utilities/html-inspector.js +514 -0
- package/lib/utilities/index.js +24 -0
- package/lib/utils/tool-patterns.js +3 -30
- package/package.json +1 -1
package/lib/tools/dialogs.js
CHANGED
|
@@ -26,11 +26,11 @@ var handleDialog = defineTabTool({
|
|
|
26
26
|
schema: {
|
|
27
27
|
name: "browser_handle_dialog",
|
|
28
28
|
title: "Handle a dialog",
|
|
29
|
-
description:
|
|
29
|
+
description: "Handle a dialog (alert, confirm, prompt)",
|
|
30
30
|
inputSchema: z.object({
|
|
31
|
-
accept: z.boolean().describe("
|
|
32
|
-
promptText: z.string().optional().describe("
|
|
33
|
-
expectation: expectationSchema
|
|
31
|
+
accept: z.boolean().describe("Accept (true) or dismiss (false)"),
|
|
32
|
+
promptText: z.string().optional().describe("Text for prompt dialogs"),
|
|
33
|
+
expectation: expectationSchema.describe("Page state after dialog. Use batch_execute for workflows")
|
|
34
34
|
}),
|
|
35
35
|
type: "destructive"
|
|
36
36
|
},
|
package/lib/tools/evaluate.js
CHANGED
|
@@ -20,31 +20,35 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/tools/evaluate.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { elementSelectorSchema } from "../types/selectors.js";
|
|
23
24
|
import { quote } from "../utils/codegen.js";
|
|
24
25
|
import { defineTabTool } from "./tool.js";
|
|
25
26
|
import { generateLocator } from "./utils.js";
|
|
27
|
+
var selectorsSchema = z.array(elementSelectorSchema).min(1).max(5).describe("Array of element selectors (max 5) supporting ref, role, CSS, or text-based selection");
|
|
26
28
|
var evaluateSchema = z.object({
|
|
27
|
-
function: z.string().describe("() => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
expectation: expectationSchema
|
|
29
|
+
function: z.string().describe("JS function: () => {...} or (element) => {...}"),
|
|
30
|
+
selectors: selectorsSchema.optional().describe("Optional element selectors. If provided, function receives element as parameter"),
|
|
31
|
+
expectation: expectationSchema.describe("Page state config. false for data extraction, true for DOM changes")
|
|
31
32
|
});
|
|
32
33
|
var evaluate = defineTabTool({
|
|
33
34
|
capability: "core",
|
|
34
35
|
schema: {
|
|
35
36
|
name: "browser_evaluate",
|
|
36
37
|
title: "Evaluate JavaScript",
|
|
37
|
-
description: "Evaluate JavaScript expression on page or element
|
|
38
|
+
description: "Evaluate JavaScript expression on page or element and return result",
|
|
38
39
|
inputSchema: evaluateSchema,
|
|
39
40
|
type: "destructive"
|
|
40
41
|
},
|
|
41
42
|
handle: async (tab, params, response) => {
|
|
42
43
|
let locator;
|
|
43
|
-
if (params.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
if (params.selectors && params.selectors.length > 0) {
|
|
45
|
+
const resolutionResults = await tab.resolveElementLocators(params.selectors);
|
|
46
|
+
const successfulResults = resolutionResults.filter((r) => r.locator && !r.error);
|
|
47
|
+
if (successfulResults.length === 0) {
|
|
48
|
+
const errors = resolutionResults.map((r) => r.error || "Unknown error").join(", ");
|
|
49
|
+
throw new Error(`Failed to resolve element selectors: ${errors}`);
|
|
50
|
+
}
|
|
51
|
+
locator = successfulResults[0].locator;
|
|
48
52
|
response.addCode(`await page.${await generateLocator(locator)}.evaluate(${quote(params.function)});`);
|
|
49
53
|
} else {
|
|
50
54
|
response.addCode(`await page.evaluate(${quote(params.function)});`);
|
package/lib/tools/files.js
CHANGED
|
@@ -26,10 +26,10 @@ var uploadFile = defineTabTool({
|
|
|
26
26
|
schema: {
|
|
27
27
|
name: "browser_file_upload",
|
|
28
28
|
title: "Upload files",
|
|
29
|
-
description:
|
|
29
|
+
description: "Upload one or multiple files to file input",
|
|
30
30
|
inputSchema: z.object({
|
|
31
|
-
paths: z.array(z.string()).describe("
|
|
32
|
-
expectation: expectationSchema
|
|
31
|
+
paths: z.array(z.string()).describe("Absolute paths to upload (array)"),
|
|
32
|
+
expectation: expectationSchema.describe("Page state config. Use batch_execute for click→upload")
|
|
33
33
|
}),
|
|
34
34
|
type: "destructive"
|
|
35
35
|
},
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/tools/inspect-html.ts
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { htmlInspectionOptionsSchema } from "../types/html-inspection.js";
|
|
24
|
+
import { HTMLInspector } from "../utilities/html-inspector.js";
|
|
25
|
+
import { defineTabTool } from "./tool.js";
|
|
26
|
+
var browserInspectHtmlSchema = htmlInspectionOptionsSchema.extend({
|
|
27
|
+
includeSuggestions: z.boolean().optional().default(false).describe("Include CSS selector suggestions in output"),
|
|
28
|
+
includeChildren: z.boolean().optional().default(true).describe("Include child elements in extraction"),
|
|
29
|
+
optimizeForLLM: z.boolean().optional().default(false).describe("Optimize extracted HTML for LLM consumption"),
|
|
30
|
+
expectation: expectationSchema.optional().describe("Page state config (minimal for HTML inspection)")
|
|
31
|
+
});
|
|
32
|
+
function formatConfiguration(params) {
|
|
33
|
+
let text = `**Configuration:**
|
|
34
|
+
`;
|
|
35
|
+
text += `- selectors: ${params.selectors.length} selector(s)
|
|
36
|
+
`;
|
|
37
|
+
text += `- depth: ${params.depth || 2}
|
|
38
|
+
`;
|
|
39
|
+
text += `- format: ${params.format || "html"}
|
|
40
|
+
`;
|
|
41
|
+
text += `- maxSize: ${params.maxSize || 50000} bytes
|
|
42
|
+
`;
|
|
43
|
+
text += `- includeAttributes: ${params.includeAttributes !== false}
|
|
44
|
+
`;
|
|
45
|
+
text += `- optimizeForLLM: ${params.optimizeForLLM}
|
|
46
|
+
|
|
47
|
+
`;
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
function formatTiming(timing) {
|
|
51
|
+
let text = `**Timing:**
|
|
52
|
+
`;
|
|
53
|
+
text += `- total: ${timing.totalMs}ms
|
|
54
|
+
`;
|
|
55
|
+
text += `- selector resolution: ${timing.selectorResolutionMs}ms
|
|
56
|
+
`;
|
|
57
|
+
text += `- extraction: ${timing.extractionMs}ms
|
|
58
|
+
|
|
59
|
+
`;
|
|
60
|
+
return text;
|
|
61
|
+
}
|
|
62
|
+
function formatStatistics(stats, totalSizeBytes, truncated) {
|
|
63
|
+
let text = `**Statistics:**
|
|
64
|
+
`;
|
|
65
|
+
text += `- elements found: ${stats.elementsFound}
|
|
66
|
+
`;
|
|
67
|
+
text += `- selectors not found: ${stats.selectorsNotFound}
|
|
68
|
+
`;
|
|
69
|
+
text += `- average depth: ${stats.averageDepth.toFixed(1)}
|
|
70
|
+
`;
|
|
71
|
+
text += `- Total size: ${totalSizeBytes} bytes
|
|
72
|
+
`;
|
|
73
|
+
text += `- truncated: ${truncated}
|
|
74
|
+
|
|
75
|
+
`;
|
|
76
|
+
return text;
|
|
77
|
+
}
|
|
78
|
+
function formatElementInfo(index, element, includeChildren = false) {
|
|
79
|
+
let selectorInfo = "unknown";
|
|
80
|
+
if ("ref" in element.matchedSelector && element.matchedSelector.ref) {
|
|
81
|
+
selectorInfo = `ref=${element.matchedSelector.ref}`;
|
|
82
|
+
} else if ("css" in element.matchedSelector && element.matchedSelector.css) {
|
|
83
|
+
selectorInfo = element.matchedSelector.css;
|
|
84
|
+
} else if ("role" in element.matchedSelector && element.matchedSelector.role) {
|
|
85
|
+
selectorInfo = element.matchedSelector.role;
|
|
86
|
+
} else if ("text" in element.matchedSelector && element.matchedSelector.text) {
|
|
87
|
+
selectorInfo = element.matchedSelector.text;
|
|
88
|
+
}
|
|
89
|
+
let text = `### Element ${index} (${selectorInfo})
|
|
90
|
+
`;
|
|
91
|
+
text += `**Tag:** ${element.metadata.tagName}
|
|
92
|
+
`;
|
|
93
|
+
text += `**Size:** ${element.metadata.sizeBytes} bytes
|
|
94
|
+
`;
|
|
95
|
+
if (element.error) {
|
|
96
|
+
text += `**Error:** ${element.error}
|
|
97
|
+
|
|
98
|
+
`;
|
|
99
|
+
return text;
|
|
100
|
+
}
|
|
101
|
+
if (element.metadata.attributes && Object.keys(element.metadata.attributes).length > 0) {
|
|
102
|
+
text += `**Attributes:** ${JSON.stringify(element.metadata.attributes)}
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
text += "\n```html\n";
|
|
106
|
+
text += element.html;
|
|
107
|
+
text += "\n```\n\n";
|
|
108
|
+
if (includeChildren && element.children && element.children.length > 0) {
|
|
109
|
+
text += formatChildren(element.children);
|
|
110
|
+
}
|
|
111
|
+
return text;
|
|
112
|
+
}
|
|
113
|
+
function formatChildren(children) {
|
|
114
|
+
let text = `**Children (${children.length}):**
|
|
115
|
+
`;
|
|
116
|
+
for (const child of children) {
|
|
117
|
+
text += `- <${child.metadata.tagName}`;
|
|
118
|
+
if (child.metadata.attributes?.id) {
|
|
119
|
+
text += ` id="${child.metadata.attributes.id}"`;
|
|
120
|
+
}
|
|
121
|
+
if (child.metadata.attributes?.class) {
|
|
122
|
+
text += ` class="${child.metadata.attributes.class}"`;
|
|
123
|
+
}
|
|
124
|
+
text += `> (${child.metadata.sizeBytes} bytes)
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
text += `
|
|
128
|
+
`;
|
|
129
|
+
return text;
|
|
130
|
+
}
|
|
131
|
+
function formatFullResponse(params, result, suggestions) {
|
|
132
|
+
let text = `## HTML Inspection Results
|
|
133
|
+
|
|
134
|
+
`;
|
|
135
|
+
text += formatConfiguration(params);
|
|
136
|
+
text += formatTiming(result.timing);
|
|
137
|
+
text += formatStatistics(result.stats, result.totalSizeBytes, result.truncated);
|
|
138
|
+
if (result.stats.elementsFound > 0) {
|
|
139
|
+
text += `**Extracted HTML Content:**
|
|
140
|
+
|
|
141
|
+
`;
|
|
142
|
+
for (const [index, element] of Object.entries(result.elements)) {
|
|
143
|
+
text += formatElementInfo(index, element, params.includeChildren);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
text += `**No elements found matching the provided selectors.**
|
|
147
|
+
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
if (params.includeSuggestions && suggestions.length > 0) {
|
|
151
|
+
text += `**CSS Selector Suggestions:**
|
|
152
|
+
`;
|
|
153
|
+
const limitedSuggestions = suggestions.slice(0, 10);
|
|
154
|
+
for (const suggestion of limitedSuggestions) {
|
|
155
|
+
text += `- ${suggestion}
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
text += `
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
if (result.suggestions && result.suggestions.length > 0) {
|
|
162
|
+
text += `**Suggestions:**
|
|
163
|
+
`;
|
|
164
|
+
for (const suggestion of result.suggestions) {
|
|
165
|
+
text += `- ${suggestion}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
text += `
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
return text;
|
|
172
|
+
}
|
|
173
|
+
async function performInspection(inspector, params) {
|
|
174
|
+
const inspectionOptions = {
|
|
175
|
+
selectors: params.selectors,
|
|
176
|
+
depth: params.depth,
|
|
177
|
+
includeStyles: params.includeStyles,
|
|
178
|
+
maxSize: params.maxSize,
|
|
179
|
+
format: params.format,
|
|
180
|
+
includeAttributes: params.includeAttributes,
|
|
181
|
+
preserveWhitespace: params.preserveWhitespace,
|
|
182
|
+
excludeSelector: params.excludeSelector
|
|
183
|
+
};
|
|
184
|
+
const inspectionResult = await inspector.extractHTML(inspectionOptions);
|
|
185
|
+
const finalResult = params.optimizeForLLM ? await inspector.optimizeForLLM(inspectionResult) : inspectionResult;
|
|
186
|
+
let suggestions = [];
|
|
187
|
+
if (params.includeSuggestions) {
|
|
188
|
+
const cssSuggestions = inspector.suggestCSSSelectors(finalResult);
|
|
189
|
+
suggestions = cssSuggestions.map((s) => `${s.selector} (confidence: ${s.confidence}) - ${s.description}`);
|
|
190
|
+
}
|
|
191
|
+
return { result: finalResult, suggestions };
|
|
192
|
+
}
|
|
193
|
+
var browserInspectHtml = defineTabTool({
|
|
194
|
+
capability: "core",
|
|
195
|
+
schema: {
|
|
196
|
+
name: "browser_inspect_html",
|
|
197
|
+
title: "HTML inspection",
|
|
198
|
+
description: "Extract and analyze HTML content from web pages with intelligent filtering and size control. Optimized for LLM consumption with configurable depth, format options, and automatic truncation.",
|
|
199
|
+
inputSchema: browserInspectHtmlSchema,
|
|
200
|
+
type: "readOnly"
|
|
201
|
+
},
|
|
202
|
+
handle: async (tab, params, response) => {
|
|
203
|
+
try {
|
|
204
|
+
const inspector = new HTMLInspector(tab.page);
|
|
205
|
+
const { result: finalResult, suggestions } = await performInspection(inspector, params);
|
|
206
|
+
const responseText = formatFullResponse(params, finalResult, suggestions);
|
|
207
|
+
response.addResult(responseText);
|
|
208
|
+
response.addCode("// HTML inspection completed");
|
|
209
|
+
if (finalResult.stats.elementsFound > 0) {
|
|
210
|
+
response.addCode(`// Extracted ${finalResult.stats.elementsFound} element(s) in ${finalResult.timing.totalMs}ms`);
|
|
211
|
+
}
|
|
212
|
+
if (params.expectation?.includeSnapshot) {
|
|
213
|
+
const snapshot = await tab.captureSnapshot();
|
|
214
|
+
response.setTabSnapshot(snapshot);
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
218
|
+
response.addResult(`## HTML Inspection Error
|
|
219
|
+
|
|
220
|
+
An error occurred during HTML inspection: ${errorMessage}
|
|
221
|
+
|
|
222
|
+
**Troubleshooting:**
|
|
223
|
+
- Verify that the page has loaded completely
|
|
224
|
+
- Check that the provided selectors are valid CSS selectors or roles
|
|
225
|
+
- Ensure the elements exist on the current page
|
|
226
|
+
- Consider reducing the depth or size limits if the page is very large
|
|
227
|
+
|
|
228
|
+
Use browser_snapshot to see the current page state.`);
|
|
229
|
+
response.addCode(`// HTML inspection failed: ${errorMessage}`);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
var inspect_html_default = [browserInspectHtml];
|
|
235
|
+
export {
|
|
236
|
+
inspect_html_default as default,
|
|
237
|
+
browserInspectHtml
|
|
238
|
+
};
|
package/lib/tools/keyboard.js
CHANGED
|
@@ -20,9 +20,13 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/tools/keyboard.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { elementSelectorSchema } from "../types/selectors.js";
|
|
23
24
|
import { quote } from "../utils/codegen.js";
|
|
24
25
|
import { generateKeyPressCode } from "../utils/common-formatters.js";
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
handleSnapshotExpectation,
|
|
28
|
+
resolveFirstElement
|
|
29
|
+
} from "./shared-element-utils.js";
|
|
26
30
|
import { defineTabTool } from "./tool.js";
|
|
27
31
|
import { generateLocator } from "./utils.js";
|
|
28
32
|
var pressKey = defineTabTool({
|
|
@@ -30,10 +34,10 @@ var pressKey = defineTabTool({
|
|
|
30
34
|
schema: {
|
|
31
35
|
name: "browser_press_key",
|
|
32
36
|
title: "Press a key",
|
|
33
|
-
description: "Press a key on the keyboard
|
|
37
|
+
description: "Press a key on the keyboard",
|
|
34
38
|
inputSchema: z.object({
|
|
35
|
-
key: z.string().describe("
|
|
36
|
-
expectation: expectationSchema
|
|
39
|
+
key: z.string().describe("Key to press"),
|
|
40
|
+
expectation: expectationSchema.describe("Page state config. Use batch_execute for multiple keys")
|
|
37
41
|
}),
|
|
38
42
|
type: "destructive"
|
|
39
43
|
},
|
|
@@ -43,31 +47,28 @@ var pressKey = defineTabTool({
|
|
|
43
47
|
await tab.waitForCompletion(async () => {
|
|
44
48
|
await tab.page.keyboard.press(params.key);
|
|
45
49
|
});
|
|
46
|
-
|
|
47
|
-
const newSnapshot = await tab.captureSnapshot();
|
|
48
|
-
response.setTabSnapshot(newSnapshot);
|
|
49
|
-
}
|
|
50
|
+
await handleSnapshotExpectation(tab, params.expectation, response);
|
|
50
51
|
}
|
|
51
52
|
});
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
var selectorsSchema = z.array(elementSelectorSchema).min(1).max(5).describe("Array of element selectors (max 5) supporting ref, role, CSS, or text-based selection");
|
|
54
|
+
var typeSchema = z.object({
|
|
55
|
+
selectors: selectorsSchema,
|
|
55
56
|
text: z.string().describe("Text to type into the element"),
|
|
56
|
-
submit: z.boolean().optional().describe("
|
|
57
|
-
slowly: z.boolean().optional().describe("
|
|
58
|
-
expectation: expectationSchema
|
|
57
|
+
submit: z.boolean().optional().describe("Press Enter after typing if true"),
|
|
58
|
+
slowly: z.boolean().optional().describe("Type slowly for auto-complete if true"),
|
|
59
|
+
expectation: expectationSchema.describe("Page state config. Use batch_execute for forms")
|
|
59
60
|
});
|
|
60
61
|
var type = defineTabTool({
|
|
61
62
|
capability: "core",
|
|
62
63
|
schema: {
|
|
63
64
|
name: "browser_type",
|
|
64
65
|
title: "Type text",
|
|
65
|
-
description:
|
|
66
|
+
description: "Type text into editable element",
|
|
66
67
|
inputSchema: typeSchema,
|
|
67
68
|
type: "destructive"
|
|
68
69
|
},
|
|
69
70
|
handle: async (tab, params, response) => {
|
|
70
|
-
const locator = await tab.
|
|
71
|
+
const { locator } = await resolveFirstElement(tab, params.selectors);
|
|
71
72
|
await tab.waitForCompletion(async () => {
|
|
72
73
|
if (params.slowly) {
|
|
73
74
|
response.addCode(`await page.${await generateLocator(locator)}.pressSequentially(${quote(params.text)});`);
|
|
@@ -81,10 +82,7 @@ var type = defineTabTool({
|
|
|
81
82
|
await locator.press("Enter");
|
|
82
83
|
}
|
|
83
84
|
});
|
|
84
|
-
|
|
85
|
-
const newSnapshot = await tab.captureSnapshot();
|
|
86
|
-
response.setTabSnapshot(newSnapshot);
|
|
87
|
-
}
|
|
85
|
+
await handleSnapshotExpectation(tab, params.expectation, response);
|
|
88
86
|
}
|
|
89
87
|
});
|
|
90
88
|
var keyboard_default = [pressKey, type];
|
package/lib/tools/mouse.js
CHANGED
|
@@ -54,11 +54,11 @@ var mouseClick = defineTabTool({
|
|
|
54
54
|
schema: {
|
|
55
55
|
name: "browser_mouse_click_xy",
|
|
56
56
|
title: "Click",
|
|
57
|
-
description: "Click at specific coordinates
|
|
57
|
+
description: "Click at specific coordinates",
|
|
58
58
|
inputSchema: elementSchema.extend({
|
|
59
|
-
x: z.number().describe("X coordinate"),
|
|
60
|
-
y: z.number().describe("Y coordinate"),
|
|
61
|
-
expectation: expectationSchema
|
|
59
|
+
x: z.number().describe("X coordinate (requires --caps=vision)"),
|
|
60
|
+
y: z.number().describe("Y coordinate (requires --caps=vision)"),
|
|
61
|
+
expectation: expectationSchema.describe("Page state after click. Prefer element ref over coords")
|
|
62
62
|
}),
|
|
63
63
|
type: "destructive"
|
|
64
64
|
},
|
|
@@ -81,13 +81,13 @@ var mouseDrag = defineTabTool({
|
|
|
81
81
|
schema: {
|
|
82
82
|
name: "browser_mouse_drag_xy",
|
|
83
83
|
title: "Drag mouse",
|
|
84
|
-
description:
|
|
84
|
+
description: "Drag from one coordinate to another",
|
|
85
85
|
inputSchema: elementSchema.extend({
|
|
86
|
-
startX: z.number().describe("Start X
|
|
87
|
-
startY: z.number().describe("Start Y
|
|
88
|
-
endX: z.number().describe("End X
|
|
89
|
-
endY: z.number().describe("End Y
|
|
90
|
-
expectation: expectationSchema
|
|
86
|
+
startX: z.number().describe("Start X (requires --caps=vision)"),
|
|
87
|
+
startY: z.number().describe("Start Y (requires --caps=vision)"),
|
|
88
|
+
endX: z.number().describe("End X"),
|
|
89
|
+
endY: z.number().describe("End Y"),
|
|
90
|
+
expectation: expectationSchema.describe("Page state after drag. Prefer element refs over coords")
|
|
91
91
|
}),
|
|
92
92
|
type: "destructive"
|
|
93
93
|
},
|
package/lib/tools/navigate.js
CHANGED
|
@@ -31,10 +31,10 @@ var navigate = defineTool({
|
|
|
31
31
|
schema: {
|
|
32
32
|
name: "browser_navigate",
|
|
33
33
|
title: "Navigate to a URL",
|
|
34
|
-
description:
|
|
34
|
+
description: "Navigate to a URL",
|
|
35
35
|
inputSchema: z.object({
|
|
36
36
|
url: z.string().describe("The URL to navigate to"),
|
|
37
|
-
expectation: expectationSchema
|
|
37
|
+
expectation: expectationSchema.describe("Page state after navigation")
|
|
38
38
|
}),
|
|
39
39
|
type: "destructive"
|
|
40
40
|
},
|
|
@@ -48,10 +48,10 @@ var goBack = defineTabTool({
|
|
|
48
48
|
capability: "core",
|
|
49
49
|
schema: {
|
|
50
50
|
name: "browser_navigate_back",
|
|
51
|
-
title: "Go back",
|
|
52
|
-
description: "Go back to previous page
|
|
51
|
+
title: "Go back to previous page",
|
|
52
|
+
description: "Go back to previous page",
|
|
53
53
|
inputSchema: z.object({
|
|
54
|
-
expectation: expectationSchema
|
|
54
|
+
expectation: expectationSchema.describe("Page state after going back")
|
|
55
55
|
}),
|
|
56
56
|
type: "readOnly"
|
|
57
57
|
},
|
|
@@ -64,10 +64,10 @@ var goForward = defineTabTool({
|
|
|
64
64
|
capability: "core",
|
|
65
65
|
schema: {
|
|
66
66
|
name: "browser_navigate_forward",
|
|
67
|
-
title: "Go forward",
|
|
68
|
-
description: "Go forward to next page
|
|
67
|
+
title: "Go forward to next page",
|
|
68
|
+
description: "Go forward to next page",
|
|
69
69
|
inputSchema: z.object({
|
|
70
|
-
expectation: expectationSchema
|
|
70
|
+
expectation: expectationSchema.describe("Page state after going forward")
|
|
71
71
|
}),
|
|
72
72
|
type: "readOnly"
|
|
73
73
|
},
|
package/lib/tools/network.js
CHANGED
|
@@ -24,22 +24,22 @@ import {
|
|
|
24
24
|
} from "../utils/network-filter.js";
|
|
25
25
|
import { defineTabTool } from "./tool.js";
|
|
26
26
|
var networkFilterSchema = z.object({
|
|
27
|
-
urlPatterns: z.array(z.string()).optional(),
|
|
28
|
-
excludeUrlPatterns: z.array(z.string()).optional(),
|
|
27
|
+
urlPatterns: z.array(z.string()).optional().describe("URL patterns to filter (supports regex)"),
|
|
28
|
+
excludeUrlPatterns: z.array(z.string()).optional().describe("URL patterns to exclude (takes precedence)"),
|
|
29
29
|
statusRanges: z.array(z.object({
|
|
30
|
-
min: z.number(),
|
|
31
|
-
max: z.number()
|
|
32
|
-
})).optional(),
|
|
33
|
-
methods: z.array(z.string()).optional(),
|
|
34
|
-
maxRequests: z.number().default(20),
|
|
35
|
-
newestFirst: z.boolean().default(true)
|
|
30
|
+
min: z.number().describe("Minimum status code"),
|
|
31
|
+
max: z.number().describe("Maximum status code")
|
|
32
|
+
})).optional().describe("Status code ranges (e.g., [{min:200,max:299}])"),
|
|
33
|
+
methods: z.array(z.string()).optional().describe("HTTP methods to filter"),
|
|
34
|
+
maxRequests: z.number().default(20).describe("Max requests to return (default: 20)"),
|
|
35
|
+
newestFirst: z.boolean().default(true).describe("Order by timestamp (default: newest first)")
|
|
36
36
|
});
|
|
37
37
|
var requests = defineTabTool({
|
|
38
38
|
capability: "core",
|
|
39
39
|
schema: {
|
|
40
40
|
name: "browser_network_requests",
|
|
41
41
|
title: "List network requests",
|
|
42
|
-
description:
|
|
42
|
+
description: "Returns network requests since loading the page with optional filtering",
|
|
43
43
|
inputSchema: networkFilterSchema.partial(),
|
|
44
44
|
type: "readOnly"
|
|
45
45
|
},
|
package/lib/tools/screenshot.js
CHANGED
|
@@ -20,23 +20,19 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/tools/screenshot.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { elementSelectorSchema } from "../types/selectors.js";
|
|
23
24
|
import { formatObject } from "../utils/codegen.js";
|
|
24
25
|
import { defineTabTool } from "./tool.js";
|
|
25
26
|
import { generateLocator } from "./utils.js";
|
|
27
|
+
var selectorsSchema = z.array(elementSelectorSchema).min(1).max(5).describe("Array of element selectors (max 5) supporting ref, role, CSS, or text-based selection");
|
|
26
28
|
var screenshotSchema = z.object({
|
|
27
29
|
type: z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
28
30
|
filename: z.string().optional().describe("File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified."),
|
|
29
|
-
|
|
30
|
-
ref: z.string().optional().describe('System-generated element ID from previous tool results (e.g., "rNODE-45-1"). Never use custom values. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too.'),
|
|
31
|
+
selectors: selectorsSchema.optional().describe("Optional element selectors for element screenshots. If not provided, viewport screenshot will be taken."),
|
|
31
32
|
fullPage: z.boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots."),
|
|
32
|
-
expectation: expectationSchema
|
|
33
|
+
expectation: expectationSchema.describe("Additional page state config")
|
|
33
34
|
}).refine((data) => {
|
|
34
|
-
return
|
|
35
|
-
}, {
|
|
36
|
-
message: "Both element and ref must be provided or neither.",
|
|
37
|
-
path: ["ref", "element"]
|
|
38
|
-
}).refine((data) => {
|
|
39
|
-
return !(data.fullPage && (data.element || data.ref));
|
|
35
|
+
return !(data.fullPage && data.selectors && data.selectors.length > 0);
|
|
40
36
|
}, {
|
|
41
37
|
message: "fullPage cannot be used with element screenshots.",
|
|
42
38
|
path: ["fullPage"]
|
|
@@ -55,19 +51,25 @@ function createScreenshotOptions(fileType, fileName, fullPage) {
|
|
|
55
51
|
};
|
|
56
52
|
}
|
|
57
53
|
function isElementScreenshotRequest(params) {
|
|
58
|
-
return !!(params.
|
|
54
|
+
return !!(params.selectors && params.selectors.length > 0);
|
|
59
55
|
}
|
|
60
56
|
function getScreenshotTarget(params, isElementScreenshot) {
|
|
61
|
-
if (isElementScreenshot
|
|
62
|
-
return
|
|
57
|
+
if (isElementScreenshot) {
|
|
58
|
+
return "element";
|
|
63
59
|
}
|
|
64
60
|
return params.fullPage ? "full page" : "viewport";
|
|
65
61
|
}
|
|
66
62
|
async function getScreenshotLocator(tab, params, isElementScreenshot) {
|
|
67
|
-
if (!(isElementScreenshot && params.
|
|
63
|
+
if (!(isElementScreenshot && params.selectors)) {
|
|
68
64
|
return null;
|
|
69
65
|
}
|
|
70
|
-
|
|
66
|
+
const resolutionResults = await tab.resolveElementLocators(params.selectors);
|
|
67
|
+
const successfulResults = resolutionResults.filter((r) => r.locator && !r.error);
|
|
68
|
+
if (successfulResults.length === 0) {
|
|
69
|
+
const errors = resolutionResults.map((r) => r.error || "Unknown error").join(", ");
|
|
70
|
+
throw new Error(`Failed to resolve element selectors for screenshot: ${errors}`);
|
|
71
|
+
}
|
|
72
|
+
return successfulResults[0].locator;
|
|
71
73
|
}
|
|
72
74
|
async function addScreenshotCode(response, locator, options) {
|
|
73
75
|
if (locator) {
|
|
@@ -84,7 +86,7 @@ var screenshot = defineTabTool({
|
|
|
84
86
|
schema: {
|
|
85
87
|
name: "browser_take_screenshot",
|
|
86
88
|
title: "Take a screenshot",
|
|
87
|
-
description:
|
|
89
|
+
description: "Take a screenshot of current page and return image data",
|
|
88
90
|
inputSchema: screenshotSchema,
|
|
89
91
|
type: "readOnly"
|
|
90
92
|
},
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/tools/shared-element-utils.ts
|
|
21
|
+
async function resolveFirstElement(tab, selectors, errorMessage = "Failed to resolve element selectors") {
|
|
22
|
+
const resolutionResults = await tab.resolveElementLocators(selectors);
|
|
23
|
+
const successfulResults = resolutionResults.filter((r) => r.locator && !r.error);
|
|
24
|
+
if (successfulResults.length === 0) {
|
|
25
|
+
const errors = resolutionResults.map((r) => r.error || "Unknown error").join(", ");
|
|
26
|
+
throw new Error(`${errorMessage}: ${errors}`);
|
|
27
|
+
}
|
|
28
|
+
return { locator: successfulResults[0].locator };
|
|
29
|
+
}
|
|
30
|
+
async function handleSnapshotExpectation(tab, expectation, response) {
|
|
31
|
+
if (expectation?.includeSnapshot) {
|
|
32
|
+
const newSnapshot = await tab.captureSnapshot();
|
|
33
|
+
response.setTabSnapshot(newSnapshot);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function resolveDragElements(tab, startSelectors, endSelectors) {
|
|
37
|
+
const [startResults, endResults] = await Promise.all([
|
|
38
|
+
tab.resolveElementLocators(startSelectors),
|
|
39
|
+
tab.resolveElementLocators(endSelectors)
|
|
40
|
+
]);
|
|
41
|
+
const startSuccessful = startResults.filter((r) => r.locator && !r.error);
|
|
42
|
+
const endSuccessful = endResults.filter((r) => r.locator && !r.error);
|
|
43
|
+
if (startSuccessful.length === 0) {
|
|
44
|
+
const errors = startResults.map((r) => r.error || "Unknown error").join(", ");
|
|
45
|
+
throw new Error(`Failed to resolve start element selectors: ${errors}`);
|
|
46
|
+
}
|
|
47
|
+
if (endSuccessful.length === 0) {
|
|
48
|
+
const errors = endResults.map((r) => r.error || "Unknown error").join(", ");
|
|
49
|
+
throw new Error(`Failed to resolve end element selectors: ${errors}`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
startLocator: startSuccessful[0].locator,
|
|
53
|
+
endLocator: endSuccessful[0].locator
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
resolveFirstElement,
|
|
58
|
+
resolveDragElements,
|
|
59
|
+
handleSnapshotExpectation
|
|
60
|
+
};
|