@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.
@@ -26,11 +26,11 @@ var handleDialog = defineTabTool({
26
26
  schema: {
27
27
  name: "browser_handle_dialog",
28
28
  title: "Handle a dialog",
29
- description: `Handle a dialog(alert,confirm,prompt).accept:true to accept,false to dismiss.promptText:"answer" for prompt dialogs.expectation:{includeSnapshot:true} to see page after dialog handling.USE batch_execute if dialog appears during workflow.`,
29
+ description: "Handle a dialog (alert, confirm, prompt)",
30
30
  inputSchema: z.object({
31
- accept: z.boolean().describe("Whether to accept the dialog."),
32
- promptText: z.string().optional().describe("The text of the prompt in case of a prompt dialog."),
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
  },
@@ -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("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
28
- element: z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
29
- ref: z.string().optional().describe('System-generated element ID from previous tool results (e.g., "rNODE-45-1"). Never use custom values.'),
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.Returns evaluation result.USE CASES:extract data,modify DOM,trigger events.expectation:{includeSnapshot:false} for data extraction,true if modifying page.element+ref to run on specific element.CONSIDER batch_execute for multiple evaluations.",
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.ref && params.element) {
44
- locator = await tab.refLocator({
45
- ref: params.ref,
46
- element: params.element
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)});`);
@@ -26,10 +26,10 @@ var uploadFile = defineTabTool({
26
26
  schema: {
27
27
  name: "browser_file_upload",
28
28
  title: "Upload files",
29
- description: `Upload one or multiple files to file input.paths:["/path/file1.jpg","/path/file2.pdf"] for multiple files.expectation:{includeSnapshot:true,snapshotOptions:{selector:"form"}} to verify upload.Must be triggered after file input interaction.USE batch_execute for click→upload workflows.`,
29
+ description: "Upload one or multiple files to file input",
30
30
  inputSchema: z.object({
31
- paths: z.array(z.string()).describe("The absolute paths to the files to upload. Can be a single file or multiple files."),
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
+ };
@@ -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 { baseElementSchema as elementSchema } from "./base-tool-handler.js";
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.Common keys:Enter,Escape,ArrowUp/Down/Left/Right,Tab,Backspace.expectation:{includeSnapshot:false} for navigation keys,true for content changes.CONSIDER batch_execute for multiple key presses.",
37
+ description: "Press a key on the keyboard",
34
38
  inputSchema: z.object({
35
- key: z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`"),
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
- if (params.expectation?.includeSnapshot) {
47
- const newSnapshot = await tab.captureSnapshot();
48
- response.setTabSnapshot(newSnapshot);
49
- }
50
+ await handleSnapshotExpectation(tab, params.expectation, response);
50
51
  }
51
52
  });
52
- var typeSchema = elementSchema.extend({
53
- element: z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
54
- ref: z.string().describe('System-generated element ID from previous tool results (e.g., "rNODE-45-1"). Never use custom values.'),
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("Whether to submit entered text (press Enter after)"),
57
- slowly: z.boolean().optional().describe("Whether type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once."),
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: `Type text into editable element.FOR FORMS:Use batch_execute to fill multiple fields efficiently.slowly:true for auto-complete fields,submit:true to press Enter after.expectation:{includeSnapshot:false} when filling multiple fields(use batch),true for final verification.snapshotOptions:{selector:"form"} to focus on form only.diffOptions:{enabled:true} shows only what changed in form.`,
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.refLocator(params);
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
- if (params.expectation?.includeSnapshot) {
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];
@@ -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.Requires --caps=vision.x,y:click position.expectation:{includeSnapshot:true} to verify result.PREFER browser_click with element ref over coordinates.USE batch_execute for coordinate-based workflows.",
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: `Drag from one coordinate to another.Requires --caps=vision.startX,startY→endX,endY.expectation:{includeSnapshot:true,snapshotOptions:{selector:".drop-zone"}} to verify.PREFER browser_drag with element refs over coordinates.`,
84
+ description: "Drag from one coordinate to another",
85
85
  inputSchema: elementSchema.extend({
86
- startX: z.number().describe("Start X coordinate"),
87
- startY: z.number().describe("Start Y coordinate"),
88
- endX: z.number().describe("End X coordinate"),
89
- endY: z.number().describe("End Y coordinate"),
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
  },
@@ -31,10 +31,10 @@ var navigate = defineTool({
31
31
  schema: {
32
32
  name: "browser_navigate",
33
33
  title: "Navigate to a URL",
34
- description: `Navigate to a URL.expectation:{includeSnapshot:true} to see what loaded,false if you know what to do next.snapshotOptions:{selector:"#content"} to focus on main content(saves 50% tokens).diffOptions:{enabled:true} when revisiting pages to see only changes.CONSIDER batch_execute for navigate→interact workflows.`,
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.expectation:{includeSnapshot:true} to see previous page,false if continuing workflow.diffOptions:{enabled:true} shows only what changed from forward page.USE batch_execute for back→interact sequences.",
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.expectation:{includeSnapshot:true} to see next page,false if continuing workflow.diffOptions:{enabled:true} shows only what changed from previous page.USE batch_execute for forward→interact sequences.",
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
  },
@@ -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: 'Returns network requests since loading the page with optional filtering. urlPatterns:["api/users"] to filter by URL patterns. excludeUrlPatterns:["analytics"] to exclude specific patterns. statusRanges:[{min:200,max:299}] for success codes only. methods:["GET","POST"] to filter by HTTP method. maxRequests:10 to limit results. newestFirst:false for chronological order. Supports regex patterns for advanced filtering.',
42
+ description: "Returns network requests since loading the page with optional filtering",
43
43
  inputSchema: networkFilterSchema.partial(),
44
44
  type: "readOnly"
45
45
  },
@@ -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
- element: z.string().optional().describe("Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too."),
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 !!data.element === !!data.ref;
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.element && params.ref);
54
+ return !!(params.selectors && params.selectors.length > 0);
59
55
  }
60
56
  function getScreenshotTarget(params, isElementScreenshot) {
61
- if (isElementScreenshot && params.element) {
62
- return params.element;
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.element && params.ref)) {
63
+ if (!(isElementScreenshot && params.selectors)) {
68
64
  return null;
69
65
  }
70
- return await tab.refLocator({ element: params.element, ref: params.ref });
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: `Take a screenshot of current page.Returns image data.expectation:{includeSnapshot:false} to avoid redundant accessibility tree(screenshot≠snapshot).imageOptions:{quality:50,format:"jpeg"} for 70% size reduction.fullPage:true for entire page,element+ref for specific element.USE CASES:visual verification,documentation,error capture.`,
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
+ };