@tontoko/fast-playwright-mcp 0.1.1 → 0.1.3
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/services/selector-resolver.js +541 -0
- package/lib/tab.js +50 -27
- package/lib/tools/base-tool-handler.js +3 -19
- package/lib/tools/evaluate.js +11 -7
- package/lib/tools/inspect-html.js +238 -0
- package/lib/tools/keyboard.js +12 -14
- package/lib/tools/screenshot.js +15 -13
- package/lib/tools/shared-element-utils.js +61 -0
- package/lib/tools/snapshot.js +21 -23
- package/lib/tools.js +2 -0
- package/lib/types/batch.js +1 -1
- 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 +4 -4
package/lib/tools/evaluate.js
CHANGED
|
@@ -20,13 +20,14 @@ 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
29
|
function: z.string().describe("JS function: () => {...} or (element) => {...}"),
|
|
28
|
-
|
|
29
|
-
ref: z.string().optional().describe('System-generated element ID from previous tool results (e.g., "rNODE-45-1"). Never use custom values.'),
|
|
30
|
+
selectors: selectorsSchema.optional().describe("Optional element selectors. If provided, function receives element as parameter"),
|
|
30
31
|
expectation: expectationSchema.describe("Page state config. false for data extraction, true for DOM changes")
|
|
31
32
|
});
|
|
32
33
|
var evaluate = defineTabTool({
|
|
@@ -40,11 +41,14 @@ var evaluate = defineTabTool({
|
|
|
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)});`);
|
|
@@ -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({
|
|
@@ -43,16 +47,13 @@ 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
|
-
|
|
55
|
-
text: z.string(),
|
|
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,
|
|
56
|
+
text: z.string().describe("Text to type into the element"),
|
|
56
57
|
submit: z.boolean().optional().describe("Press Enter after typing if true"),
|
|
57
58
|
slowly: z.boolean().optional().describe("Type slowly for auto-complete if true"),
|
|
58
59
|
expectation: expectationSchema.describe("Page state config. Use batch_execute for forms")
|
|
@@ -67,7 +68,7 @@ var type = defineTabTool({
|
|
|
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/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
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) {
|
|
@@ -0,0 +1,61 @@
|
|
|
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 { selector, maxLength } = expectation.snapshotOptions ?? {};
|
|
33
|
+
const newSnapshot = selector || maxLength ? await tab.capturePartialSnapshot(selector, maxLength) : await tab.captureSnapshot();
|
|
34
|
+
response.setTabSnapshot(newSnapshot);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function resolveDragElements(tab, startSelectors, endSelectors) {
|
|
38
|
+
const [startResults, endResults] = await Promise.all([
|
|
39
|
+
tab.resolveElementLocators(startSelectors),
|
|
40
|
+
tab.resolveElementLocators(endSelectors)
|
|
41
|
+
]);
|
|
42
|
+
const startSuccessful = startResults.filter((r) => r.locator && !r.error);
|
|
43
|
+
const endSuccessful = endResults.filter((r) => r.locator && !r.error);
|
|
44
|
+
if (startSuccessful.length === 0) {
|
|
45
|
+
const errors = startResults.map((r) => r.error || "Unknown error").join(", ");
|
|
46
|
+
throw new Error(`Failed to resolve start element selectors: ${errors}`);
|
|
47
|
+
}
|
|
48
|
+
if (endSuccessful.length === 0) {
|
|
49
|
+
const errors = endResults.map((r) => r.error || "Unknown error").join(", ");
|
|
50
|
+
throw new Error(`Failed to resolve end element selectors: ${errors}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
startLocator: startSuccessful[0].locator,
|
|
54
|
+
endLocator: endSuccessful[0].locator
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
resolveFirstElement,
|
|
59
|
+
resolveDragElements,
|
|
60
|
+
handleSnapshotExpectation
|
|
61
|
+
};
|
package/lib/tools/snapshot.js
CHANGED
|
@@ -20,7 +20,13 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/tools/snapshot.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";
|
|
25
|
+
import {
|
|
26
|
+
handleSnapshotExpectation,
|
|
27
|
+
resolveDragElements,
|
|
28
|
+
resolveFirstElement
|
|
29
|
+
} from "./shared-element-utils.js";
|
|
24
30
|
import { defineTabTool, defineTool } from "./tool.js";
|
|
25
31
|
import { generateLocator } from "./utils.js";
|
|
26
32
|
var snapshot = defineTool({
|
|
@@ -45,11 +51,9 @@ var snapshot = defineTool({
|
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
});
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
var clickSchema = elementSchema.extend({
|
|
54
|
+
var selectorsSchema = z.array(elementSelectorSchema).min(1).max(5).describe("Array of element selectors (max 5). Selectors are tried in order until one succeeds (fallback mechanism). " + "Multiple matches trigger an error with candidate list. " + "Supports: ref (highest priority), CSS (#id, .class, tag), role (button, textbox, etc.), text content. " + 'Example: [{css: "#submit"}, {role: "button", text: "Submit"}] - tries ID first, falls back to role+text');
|
|
55
|
+
var clickSchema = z.object({
|
|
56
|
+
selectors: selectorsSchema,
|
|
53
57
|
doubleClick: z.boolean().optional().describe("Double-click if true"),
|
|
54
58
|
button: z.enum(["left", "right", "middle"]).optional().describe("Mouse button (default: left)"),
|
|
55
59
|
expectation: expectationSchema.describe("Page state capture config. Use batch_execute for multi-clicks")
|
|
@@ -64,7 +68,7 @@ var click = defineTabTool({
|
|
|
64
68
|
type: "destructive"
|
|
65
69
|
},
|
|
66
70
|
handle: async (tab, params, response) => {
|
|
67
|
-
const locator = await tab.
|
|
71
|
+
const { locator } = await resolveFirstElement(tab, params.selectors, "Failed to resolve any element selectors");
|
|
68
72
|
const button = params.button;
|
|
69
73
|
const buttonAttr = button ? `{ button: '${button}' }` : "";
|
|
70
74
|
if (params.doubleClick) {
|
|
@@ -79,10 +83,7 @@ var click = defineTabTool({
|
|
|
79
83
|
await locator.click({ button });
|
|
80
84
|
}
|
|
81
85
|
});
|
|
82
|
-
|
|
83
|
-
const newSnapshot = await tab.captureSnapshot();
|
|
84
|
-
response.setTabSnapshot(newSnapshot);
|
|
85
|
-
}
|
|
86
|
+
await handleSnapshotExpectation(tab, params.expectation, response);
|
|
86
87
|
}
|
|
87
88
|
});
|
|
88
89
|
var drag = defineTabTool({
|
|
@@ -92,19 +93,14 @@ var drag = defineTabTool({
|
|
|
92
93
|
title: "Drag mouse",
|
|
93
94
|
description: "Perform drag and drop between two elements",
|
|
94
95
|
inputSchema: z.object({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
endElement: z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
|
|
98
|
-
endRef: z.string().describe("System-generated target element ID from previous tool results. Never use custom values."),
|
|
96
|
+
startSelectors: selectorsSchema.describe("Source element selectors for drag start"),
|
|
97
|
+
endSelectors: selectorsSchema.describe("Target element selectors for drag end"),
|
|
99
98
|
expectation: expectationSchema.describe("Page state after drag. Use batch_execute for workflows")
|
|
100
99
|
}),
|
|
101
100
|
type: "destructive"
|
|
102
101
|
},
|
|
103
102
|
handle: async (tab, params, response) => {
|
|
104
|
-
const
|
|
105
|
-
{ ref: params.startRef, element: params.startElement },
|
|
106
|
-
{ ref: params.endRef, element: params.endElement }
|
|
107
|
-
]);
|
|
103
|
+
const { startLocator, endLocator } = await resolveDragElements(tab, params.startSelectors, params.endSelectors);
|
|
108
104
|
await tab.waitForCompletion(async () => {
|
|
109
105
|
await startLocator.dragTo(endLocator);
|
|
110
106
|
});
|
|
@@ -117,20 +113,22 @@ var hover = defineTabTool({
|
|
|
117
113
|
name: "browser_hover",
|
|
118
114
|
title: "Hover mouse",
|
|
119
115
|
description: "Hover over element on page",
|
|
120
|
-
inputSchema:
|
|
116
|
+
inputSchema: z.object({
|
|
117
|
+
selectors: selectorsSchema,
|
|
121
118
|
expectation: expectationSchema.describe("Page state after hover. Use batch_execute for hover→click")
|
|
122
119
|
}),
|
|
123
120
|
type: "readOnly"
|
|
124
121
|
},
|
|
125
122
|
handle: async (tab, params, response) => {
|
|
126
|
-
const locator = await tab.
|
|
123
|
+
const { locator } = await resolveFirstElement(tab, params.selectors);
|
|
127
124
|
response.addCode(`await page.${await generateLocator(locator)}.hover();`);
|
|
128
125
|
await tab.waitForCompletion(async () => {
|
|
129
126
|
await locator.hover();
|
|
130
127
|
});
|
|
131
128
|
}
|
|
132
129
|
});
|
|
133
|
-
var selectOptionSchema =
|
|
130
|
+
var selectOptionSchema = z.object({
|
|
131
|
+
selectors: selectorsSchema,
|
|
134
132
|
values: z.array(z.string()).describe("Values to select (array)"),
|
|
135
133
|
expectation: expectationSchema.describe("Page state after selection. Use batch_execute for forms")
|
|
136
134
|
});
|
|
@@ -144,7 +142,7 @@ var selectOption = defineTabTool({
|
|
|
144
142
|
type: "destructive"
|
|
145
143
|
},
|
|
146
144
|
handle: async (tab, params, response) => {
|
|
147
|
-
const locator = await tab.
|
|
145
|
+
const { locator } = await resolveFirstElement(tab, params.selectors);
|
|
148
146
|
response.addCode(`await page.${await generateLocator(locator)}.selectOption(${formatObject(params.values)});`);
|
|
149
147
|
await tab.waitForCompletion(async () => {
|
|
150
148
|
await locator.selectOption(params.values);
|
|
@@ -153,6 +151,6 @@ var selectOption = defineTabTool({
|
|
|
153
151
|
});
|
|
154
152
|
var snapshot_default = [snapshot, click, drag, hover, selectOption];
|
|
155
153
|
export {
|
|
156
|
-
|
|
154
|
+
selectorsSchema,
|
|
157
155
|
snapshot_default as default
|
|
158
156
|
};
|
package/lib/tools.js
CHANGED
|
@@ -26,6 +26,7 @@ import dialogs from "./tools/dialogs.js";
|
|
|
26
26
|
import evaluate from "./tools/evaluate.js";
|
|
27
27
|
import files from "./tools/files.js";
|
|
28
28
|
import { browserFindElements } from "./tools/find-elements.js";
|
|
29
|
+
import inspectHtml from "./tools/inspect-html.js";
|
|
29
30
|
import install from "./tools/install.js";
|
|
30
31
|
import keyboard from "./tools/keyboard.js";
|
|
31
32
|
import mouse from "./tools/mouse.js";
|
|
@@ -43,6 +44,7 @@ var allTools = [
|
|
|
43
44
|
...evaluate,
|
|
44
45
|
...files,
|
|
45
46
|
...install,
|
|
47
|
+
...inspectHtml,
|
|
46
48
|
...keyboard,
|
|
47
49
|
...navigate,
|
|
48
50
|
...network,
|
package/lib/types/batch.js
CHANGED
|
@@ -37,7 +37,7 @@ var batchStepSchema = z.object({
|
|
|
37
37
|
expectation: expectationSchema.describe("Expected output configuration for this step")
|
|
38
38
|
});
|
|
39
39
|
var batchExecuteSchema = z.object({
|
|
40
|
-
steps: z.array(batchStepSchema).min(1).describe('Array of steps to execute in sequence. Recommended for form filling (multiple type→click), multi-step navigation, any workflow with 2+ known steps. Saves 90% tokens vs individual calls. Example: [{tool:"browser_navigate",arguments:{url:"https://example.com"}},{tool:"browser_type",arguments:{
|
|
40
|
+
steps: z.array(batchStepSchema).min(1).describe('Array of steps to execute in sequence. Recommended for form filling (multiple type→click), multi-step navigation, any workflow with 2+ known steps. Saves 90% tokens vs individual calls. Example: [{tool:"browser_navigate",arguments:{url:"https://example.com"}},{tool:"browser_type",arguments:{selectors:[{css:"#user"}],text:"john"}},{tool:"browser_click",arguments:{selectors:[{css:"#btn"}]}}]'),
|
|
41
41
|
stopOnFirstError: z.boolean().optional().default(false).describe("Stop entire batch on first error"),
|
|
42
42
|
globalExpectation: z.preprocess(parseJsonString, expectationSchema).optional().describe('Default expectation for all steps. Recommended: {includeSnapshot:false,snapshotOptions:{selector:"#app"},diffOptions:{enabled:true}}. Per-step override with steps[].expectation')
|
|
43
43
|
});
|