@tontoko/fast-playwright-mcp 0.1.1 → 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.
@@ -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
- 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
+ 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.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)});`);
@@ -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({
@@ -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
- 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. Never use custom values."),
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.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];
@@ -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
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) {
@@ -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
+ };
@@ -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 elementSchema = z.object({
49
- element: z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
50
- ref: z.string().describe("System-generated element ID from previous tool results. Never use custom values.")
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.refLocator(params);
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
- if (params.expectation?.includeSnapshot) {
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
- startElement: z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
96
- startRef: z.string().describe("System-generated source element ID from previous tool results. Never use custom values."),
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 [startLocator, endLocator] = await tab.refLocators([
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: elementSchema.extend({
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.refLocator(params);
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 = elementSchema.extend({
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.refLocator(params);
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
- elementSchema,
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,
@@ -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:{element:"username",ref:"#user",text:"john"}},{tool:"browser_click",arguments:{element:"submit",ref:"#btn"}}]'),
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
  });