@wordbricks/playwright-mcp 0.1.20 → 0.1.22

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.
Files changed (87) hide show
  1. package/cli-wrapper.js +15 -14
  2. package/cli.js +1 -1
  3. package/config.d.ts +11 -6
  4. package/index.d.ts +7 -5
  5. package/index.js +1 -1
  6. package/lib/browserContextFactory.js +131 -58
  7. package/lib/browserServerBackend.js +14 -12
  8. package/lib/config.js +60 -46
  9. package/lib/context.js +41 -39
  10. package/lib/extension/cdpRelay.js +67 -61
  11. package/lib/extension/extensionContextFactory.js +10 -10
  12. package/lib/frameworkPatterns.js +21 -21
  13. package/lib/hooks/antiBotDetectionHook.js +59 -52
  14. package/lib/hooks/core.js +11 -10
  15. package/lib/hooks/eventConsumer.js +21 -21
  16. package/lib/hooks/events.js +3 -3
  17. package/lib/hooks/formatToolCallEvent.js +3 -7
  18. package/lib/hooks/frameworkStateHook.js +40 -40
  19. package/lib/hooks/grouping.js +3 -3
  20. package/lib/hooks/jsonLdDetectionHook.js +44 -37
  21. package/lib/hooks/networkFilters.js +17 -17
  22. package/lib/hooks/networkSetup.js +9 -7
  23. package/lib/hooks/networkTrackingHook.js +21 -21
  24. package/lib/hooks/pageHeightHook.js +9 -9
  25. package/lib/hooks/registry.js +15 -16
  26. package/lib/hooks/requireTabHook.js +3 -3
  27. package/lib/hooks/schema.js +38 -38
  28. package/lib/hooks/waitHook.js +7 -7
  29. package/lib/index.js +12 -10
  30. package/lib/mcp/inProcessTransport.js +3 -4
  31. package/lib/mcp/proxyBackend.js +43 -28
  32. package/lib/mcp/server.js +24 -19
  33. package/lib/mcp/tool.js +14 -8
  34. package/lib/mcp/transport.js +60 -53
  35. package/lib/playwrightTransformer.js +129 -106
  36. package/lib/program.js +54 -52
  37. package/lib/response.js +36 -30
  38. package/lib/sessionLog.js +19 -17
  39. package/lib/tab.js +41 -39
  40. package/lib/tools/common.js +19 -19
  41. package/lib/tools/console.js +11 -11
  42. package/lib/tools/dialogs.js +18 -15
  43. package/lib/tools/evaluate.js +26 -17
  44. package/lib/tools/extractFrameworkState.js +48 -37
  45. package/lib/tools/files.js +17 -14
  46. package/lib/tools/form.js +32 -23
  47. package/lib/tools/getSnapshot.js +14 -15
  48. package/lib/tools/getVisibleHtml.js +33 -17
  49. package/lib/tools/install.js +20 -20
  50. package/lib/tools/keyboard.js +29 -24
  51. package/lib/tools/mouse.js +29 -31
  52. package/lib/tools/navigate.js +19 -23
  53. package/lib/tools/network.js +12 -14
  54. package/lib/tools/networkDetail.js +58 -49
  55. package/lib/tools/networkSearch/bodySearch.js +46 -32
  56. package/lib/tools/networkSearch/grouping.js +15 -6
  57. package/lib/tools/networkSearch/helpers.js +4 -4
  58. package/lib/tools/networkSearch/searchHtml.js +25 -16
  59. package/lib/tools/networkSearch/urlSearch.js +56 -14
  60. package/lib/tools/networkSearch.js +46 -36
  61. package/lib/tools/pdf.js +13 -12
  62. package/lib/tools/repl.js +66 -54
  63. package/lib/tools/screenshot.js +57 -33
  64. package/lib/tools/scroll.js +29 -24
  65. package/lib/tools/snapshot.js +66 -49
  66. package/lib/tools/tabs.js +22 -19
  67. package/lib/tools/tool.js +5 -3
  68. package/lib/tools/utils.js +17 -13
  69. package/lib/tools/wait.js +24 -19
  70. package/lib/tools.js +21 -20
  71. package/lib/utils/adBlockFilter.js +29 -26
  72. package/lib/utils/codegen.js +20 -16
  73. package/lib/utils/extensionPath.js +4 -4
  74. package/lib/utils/fileUtils.js +17 -13
  75. package/lib/utils/graphql.js +69 -58
  76. package/lib/utils/guid.js +3 -3
  77. package/lib/utils/httpServer.js +9 -9
  78. package/lib/utils/log.js +3 -3
  79. package/lib/utils/manualPromise.js +7 -7
  80. package/lib/utils/networkFormat.js +7 -5
  81. package/lib/utils/package.js +4 -4
  82. package/lib/utils/sanitizeHtml.js +66 -34
  83. package/lib/utils/truncate.js +25 -25
  84. package/lib/utils/withTimeout.js +1 -1
  85. package/package.json +34 -57
  86. package/src/index.ts +27 -17
  87. package/LICENSE +0 -202
@@ -13,29 +13,40 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTabTool } from './tool.js';
18
- import * as javascript from '../utils/codegen.js';
19
- import { generateLocator } from './utils.js';
16
+ import { z } from "zod";
17
+ import * as javascript from "../utils/codegen.js";
18
+ import { defineTabTool } from "./tool.js";
19
+ import { generateLocator } from "./utils.js";
20
20
  const evaluateSchema = z.object({
21
- function: z.string().describe('() => { /* code */ } or (element) => { /* code */ } when element is provided'),
22
- element: z.string().optional().describe('Human-readable element description used to obtain permission to interact with the element'),
23
- ref: z.string().optional().describe('Exact target element reference from the page snapshot'),
21
+ function: z
22
+ .string()
23
+ .describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
24
+ element: z
25
+ .string()
26
+ .optional()
27
+ .describe("Human-readable element description used to obtain permission to interact with the element"),
28
+ ref: z
29
+ .string()
30
+ .optional()
31
+ .describe("Exact target element reference from the page snapshot"),
24
32
  });
25
33
  const evaluate = defineTabTool({
26
- capability: 'core',
34
+ capability: "core",
27
35
  schema: {
28
- name: 'browser_evaluate',
29
- title: 'Evaluate JavaScript',
30
- description: 'Evaluate JavaScript expression on page or element',
36
+ name: "browser_evaluate",
37
+ title: "Evaluate JavaScript",
38
+ description: "Evaluate JavaScript expression on page or element",
31
39
  inputSchema: evaluateSchema,
32
- type: 'action',
40
+ type: "action",
33
41
  },
34
42
  handle: async (tab, params, response) => {
35
43
  response.setIncludeSnapshot();
36
44
  let locator;
37
45
  if (params.ref && params.element) {
38
- locator = await tab.refLocator({ ref: params.ref, element: params.element });
46
+ locator = await tab.refLocator({
47
+ ref: params.ref,
48
+ element: params.element,
49
+ });
39
50
  response.addCode(`await page.${await generateLocator(locator)}.evaluate(${javascript.quote(params.function)});`);
40
51
  }
41
52
  else {
@@ -44,10 +55,8 @@ const evaluate = defineTabTool({
44
55
  await tab.waitForCompletion(async () => {
45
56
  const receiver = locator ?? tab.page;
46
57
  const result = await receiver._evaluateFunction(params.function);
47
- response.addResult(JSON.stringify(result, null, 2) || 'undefined');
58
+ response.addResult(JSON.stringify(result, null, 2) || "undefined");
48
59
  });
49
60
  },
50
61
  });
51
- export default [
52
- evaluate,
53
- ];
62
+ export default [evaluate];
@@ -1,19 +1,25 @@
1
- import { z } from 'zod';
2
- import { defineTabTool } from './tool.js';
3
- import { FRAMEWORK_STATE_PATTERNS, MAX_DISPLAY_ITEMS } from '../frameworkPatterns.js';
4
- import { truncate } from '../utils/truncate.js';
1
+ import { z } from "zod";
2
+ import { FRAMEWORK_STATE_PATTERNS, MAX_DISPLAY_ITEMS, } from "../frameworkPatterns.js";
3
+ import { truncate } from "../utils/truncate.js";
4
+ import { defineTabTool } from "./tool.js";
5
5
  const extractFrameworkStateSchema = z.object({
6
- framework: z.string().optional().describe('Optional: specific key to extract from (e.g., "__remixContext")'),
7
- path: z.string().optional().describe('Path to extract (e.g., "state.loaderData")'),
6
+ framework: z
7
+ .string()
8
+ .optional()
9
+ .describe('Optional: specific key to extract from (e.g., "__remixContext")'),
10
+ path: z
11
+ .string()
12
+ .optional()
13
+ .describe('Path to extract (e.g., "state.loaderData")'),
8
14
  });
9
15
  const extractFrameworkState = defineTabTool({
10
- capability: 'core',
16
+ capability: "core",
11
17
  schema: {
12
- name: 'browser_extract_framework_state',
13
- title: 'Extract framework state',
14
- description: 'Extract framework state data from web pages (React, Redux, Remix, etc.)',
18
+ name: "browser_extract_framework_state",
19
+ title: "Extract framework state",
20
+ description: "Extract framework state data from web pages (React, Redux, Remix, etc.)",
15
21
  inputSchema: extractFrameworkStateSchema,
16
- type: 'readOnly',
22
+ type: "readOnly",
17
23
  },
18
24
  handle: async (tab, params, response) => {
19
25
  const { framework, // Optional: specific key to extract from
@@ -30,7 +36,7 @@ const extractFrameworkState = defineTabTool({
30
36
  try {
31
37
  let data = window[key];
32
38
  // If data is string, try to parse as JSON
33
- if (typeof data === 'string') {
39
+ if (typeof data === "string") {
34
40
  try {
35
41
  data = JSON.parse(data);
36
42
  }
@@ -43,14 +49,14 @@ const extractFrameworkState = defineTabTool({
43
49
  // Split path while respecting bracket-quoted segments like ['a.b']
44
50
  const splitPath = (p) => {
45
51
  const parts = [];
46
- let cur = '';
52
+ let cur = "";
47
53
  let inBracket = false;
48
54
  let inQuote = null;
49
55
  for (let i = 0; i < p.length; i++) {
50
56
  const ch = p[i];
51
57
  if (inQuote) {
52
58
  cur += ch;
53
- if (ch === inQuote && p[i - 1] !== '\\')
59
+ if (ch === inQuote && p[i - 1] !== "\\")
54
60
  inQuote = null;
55
61
  continue;
56
62
  }
@@ -59,26 +65,26 @@ const extractFrameworkState = defineTabTool({
59
65
  if (ch === '"' || ch === "'") {
60
66
  inQuote = ch;
61
67
  }
62
- else if (ch === ']') {
68
+ else if (ch === "]") {
63
69
  inBracket = false;
64
70
  parts.push(cur);
65
- cur = '';
71
+ cur = "";
66
72
  }
67
73
  continue;
68
74
  }
69
- if (ch === '.') {
75
+ if (ch === ".") {
70
76
  if (cur) {
71
77
  parts.push(cur);
72
- cur = '';
78
+ cur = "";
73
79
  }
74
80
  continue;
75
81
  }
76
- if (ch === '[') {
82
+ if (ch === "[") {
77
83
  if (cur) {
78
84
  parts.push(cur);
79
- cur = '';
85
+ cur = "";
80
86
  }
81
- cur = '[';
87
+ cur = "[";
82
88
  inBracket = true;
83
89
  continue;
84
90
  }
@@ -90,20 +96,22 @@ const extractFrameworkState = defineTabTool({
90
96
  };
91
97
  const pathParts = splitPath(targetPath);
92
98
  for (const part of pathParts) {
93
- if (data && typeof data === 'object') {
99
+ if (data && typeof data === "object") {
94
100
  // Handle array index with prop: prop[123]
95
101
  const arrayMatch = part.match(/^(.+)\[(\d+)\]$/);
96
102
  if (arrayMatch) {
97
103
  const prop = arrayMatch[1];
98
- const index = parseInt(arrayMatch[2], 10);
104
+ const index = Number.parseInt(arrayMatch[2], 10);
99
105
  const propData = data[prop];
100
- data = Array.isArray(propData) ? propData[index] : undefined;
106
+ data = Array.isArray(propData)
107
+ ? propData[index]
108
+ : undefined;
101
109
  }
102
110
  else if (part.match(/^\[(\d+)\]$/)) {
103
111
  // Handle standalone array index: [123]
104
112
  const indexMatch = part.match(/^\[(\d+)\]$/);
105
113
  if (indexMatch) {
106
- const index = parseInt(indexMatch[1], 10);
114
+ const index = Number.parseInt(indexMatch[1], 10);
107
115
  data = data?.[index];
108
116
  }
109
117
  }
@@ -151,14 +159,14 @@ const extractFrameworkState = defineTabTool({
151
159
  throw new Error(`Path "${path}" not found in framework state '${framework}'.`);
152
160
  }
153
161
  throw new Error(`Key '${framework}' not found in window. ` +
154
- 'Available framework states can be detected by the frameworkStateHook.');
162
+ "Available framework states can be detected by the frameworkStateHook.");
155
163
  }
156
- throw new Error('No framework state found. ' +
157
- 'Available framework states can be detected by the frameworkStateHook.');
164
+ throw new Error("No framework state found. " +
165
+ "Available framework states can be detected by the frameworkStateHook.");
158
166
  }
159
167
  // Prepare final data
160
168
  let finalData;
161
- const extractedKeys = result.states.map(s => s.key);
169
+ const extractedKeys = result.states.map((s) => s.key);
162
170
  if (result.states.length === 1) {
163
171
  finalData = result.states[0].data;
164
172
  }
@@ -170,7 +178,7 @@ const extractFrameworkState = defineTabTool({
170
178
  // Limit to MAX_DISPLAY_ITEMS for display if array
171
179
  const totalItems = Array.isArray(finalData)
172
180
  ? finalData.length
173
- : typeof finalData === 'object' && finalData !== null
181
+ : typeof finalData === "object" && finalData !== null
174
182
  ? Object.keys(finalData).length
175
183
  : 1;
176
184
  let displayData = finalData;
@@ -179,35 +187,38 @@ const extractFrameworkState = defineTabTool({
179
187
  displayData = finalData.slice(0, MAX_DISPLAY_ITEMS);
180
188
  isTruncated = true;
181
189
  }
182
- else if (typeof finalData === 'object' &&
190
+ else if (typeof finalData === "object" &&
183
191
  finalData !== null &&
184
192
  !Array.isArray(finalData)) {
185
193
  const keys = Object.keys(finalData);
186
194
  if (keys.length > MAX_DISPLAY_ITEMS) {
187
195
  displayData = {};
188
- keys.slice(0, MAX_DISPLAY_ITEMS).forEach(key => {
196
+ keys.slice(0, MAX_DISPLAY_ITEMS).forEach((key) => {
189
197
  displayData[key] = finalData[key];
190
198
  });
191
199
  isTruncated = true;
192
200
  }
193
201
  }
194
202
  // Truncate for readability and cap output
195
- const truncatedDisplay = truncate(displayData, { maxStringLength: 1000, maxItems: MAX_DISPLAY_ITEMS });
203
+ const truncatedDisplay = truncate(displayData, {
204
+ maxStringLength: 1000,
205
+ maxItems: MAX_DISPLAY_ITEMS,
206
+ });
196
207
  const outputStr = JSON.stringify(truncatedDisplay, null, 2);
197
208
  const summary = [
198
209
  `Found ${result.count} framework state(s)`,
199
210
  result.states.length > 1
200
- ? `Extracted from: ${extractedKeys.join(', ')}`
211
+ ? `Extracted from: ${extractedKeys.join(", ")}`
201
212
  : `Using: ${result.states[0]?.key}`,
202
213
  isTruncated
203
214
  ? `Showing first ${MAX_DISPLAY_ITEMS} of ${totalItems} results`
204
215
  : null,
205
216
  path ? `Extracted path: ${path}` : null,
206
- result.errors ? `Errors: ${result.errors.join('; ')}` : null,
217
+ result.errors ? `Errors: ${result.errors.join("; ")}` : null,
207
218
  ]
208
219
  .filter(Boolean)
209
- .join('\n');
210
- response.addResult(summary + '\n\n' + outputStr);
220
+ .join("\n");
221
+ response.addResult(summary + "\n\n" + outputStr);
211
222
  response.setIncludeSnapshot();
212
223
  },
213
224
  });
@@ -13,24 +13,29 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTabTool } from './tool.js';
16
+ import { z } from "zod";
17
+ import { defineTabTool } from "./tool.js";
18
18
  export const uploadFile = defineTabTool({
19
- capability: 'core',
19
+ capability: "core",
20
20
  schema: {
21
- name: 'browser_file_upload',
22
- title: 'Upload files',
23
- description: 'Upload one or multiple files',
21
+ name: "browser_file_upload",
22
+ title: "Upload files",
23
+ description: "Upload one or multiple files",
24
24
  inputSchema: z.object({
25
- paths: z.array(z.string()).optional().describe('The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled.'),
25
+ paths: z
26
+ .array(z.string())
27
+ .optional()
28
+ .describe("The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled."),
26
29
  }),
27
- type: 'action',
30
+ type: "action",
28
31
  },
29
32
  handle: async (tab, params, response) => {
30
33
  response.setIncludeSnapshot();
31
- const modalState = tab.modalStates().find(state => state.type === 'fileChooser');
34
+ const modalState = tab
35
+ .modalStates()
36
+ .find((state) => state.type === "fileChooser");
32
37
  if (!modalState)
33
- throw new Error('No file chooser visible');
38
+ throw new Error("No file chooser visible");
34
39
  response.addCode(`await fileChooser.setFiles(${JSON.stringify(params.paths)})`);
35
40
  tab.clearModalState(modalState);
36
41
  await tab.waitForCompletion(async () => {
@@ -38,8 +43,6 @@ export const uploadFile = defineTabTool({
38
43
  await modalState.fileChooser.setFiles(params.paths);
39
44
  });
40
45
  },
41
- clearsModalState: 'fileChooser',
46
+ clearsModalState: "fileChooser",
42
47
  });
43
- export default [
44
- uploadFile,
45
- ];
48
+ export default [uploadFile];
package/lib/tools/form.js CHANGED
@@ -13,45 +13,54 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { z } from 'zod';
17
- import { defineTabTool } from './tool.js';
18
- import * as codegen from '../utils/codegen.js';
19
- import { generateLocator } from './utils.js';
16
+ import { z } from "zod";
17
+ import * as codegen from "../utils/codegen.js";
18
+ import { defineTabTool } from "./tool.js";
19
+ import { generateLocator } from "./utils.js";
20
20
  const fillForm = defineTabTool({
21
- capability: 'core',
21
+ capability: "core",
22
22
  schema: {
23
- name: 'browser_fill_form',
24
- title: 'Fill form',
25
- description: 'Fill multiple form fields',
23
+ name: "browser_fill_form",
24
+ title: "Fill form",
25
+ description: "Fill multiple form fields",
26
26
  inputSchema: z.object({
27
- fields: z.array(z.object({
28
- name: z.string().describe('Human-readable field name'),
29
- type: z.enum(['textbox', 'checkbox', 'radio', 'combobox', 'slider']).describe('Type of the field'),
30
- ref: z.string().describe('Exact target field reference from the page snapshot'),
31
- value: z.string().describe('Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.'),
32
- })).describe('Fields to fill in'),
27
+ fields: z
28
+ .array(z.object({
29
+ name: z.string().describe("Human-readable field name"),
30
+ type: z
31
+ .enum(["textbox", "checkbox", "radio", "combobox", "slider"])
32
+ .describe("Type of the field"),
33
+ ref: z
34
+ .string()
35
+ .describe("Exact target field reference from the page snapshot"),
36
+ value: z
37
+ .string()
38
+ .describe("Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option."),
39
+ }))
40
+ .describe("Fields to fill in"),
33
41
  }),
34
- type: 'input',
42
+ type: "input",
35
43
  },
36
44
  handle: async (tab, params, response) => {
37
45
  for (const field of params.fields) {
38
- const locator = await tab.refLocator({ element: field.name, ref: field.ref });
39
- if (field.type === 'textbox' || field.type === 'slider') {
46
+ const locator = await tab.refLocator({
47
+ element: field.name,
48
+ ref: field.ref,
49
+ });
50
+ if (field.type === "textbox" || field.type === "slider") {
40
51
  const secret = tab.context.lookupSecret(field.value);
41
52
  await locator.fill(secret.value);
42
53
  response.addCode(`await page.${await generateLocator(locator)}.fill(${secret.code});`);
43
54
  }
44
- else if (field.type === 'checkbox' || field.type === 'radio') {
45
- await locator.setChecked(field.value === 'true');
55
+ else if (field.type === "checkbox" || field.type === "radio") {
56
+ await locator.setChecked(field.value === "true");
46
57
  response.addCode(`await page.${await generateLocator(locator)}.setChecked(${field.value});`);
47
58
  }
48
- else if (field.type === 'combobox') {
59
+ else if (field.type === "combobox") {
49
60
  await locator.selectOption({ label: field.value });
50
61
  response.addCode(`await page.${await generateLocator(locator)}.selectOption(${codegen.quote(field.value)});`);
51
62
  }
52
63
  }
53
64
  },
54
65
  });
55
- export default [
56
- fillForm,
57
- ];
66
+ export default [fillForm];
@@ -1,15 +1,16 @@
1
- import { z } from 'zod';
2
- import { defineTool } from './tool.js';
3
- import { truncate } from '../utils/truncate.js';
1
+ import { z } from "zod";
2
+ import { truncate } from "../utils/truncate.js";
3
+ import { defineTool } from "./tool.js";
4
4
  const MAX_LENGTH = 100_000;
5
5
  const getSnapshotSchema = z.object({});
6
6
  const getSnapshot = defineTool({
7
- capability: 'core',
7
+ capability: "core",
8
8
  schema: {
9
- name: 'browser_get_snapshot',
10
- title: 'Get snapshot',
11
- description: 'Get accessibility snapshot of the current page', inputSchema: getSnapshotSchema,
12
- type: 'readOnly',
9
+ name: "browser_get_snapshot",
10
+ title: "Get snapshot",
11
+ description: "Get accessibility snapshot of the current page",
12
+ inputSchema: getSnapshotSchema,
13
+ type: "readOnly",
13
14
  },
14
15
  handle: async (context, _params, response) => {
15
16
  const tab = await context.ensureTab();
@@ -20,18 +21,16 @@ const getSnapshot = defineTool({
20
21
  lines.push(`- Page URL: ${snapshot.url}`);
21
22
  lines.push(`- Page Title: ${snapshot.title}`);
22
23
  lines.push(`- Page Snapshot:`);
23
- lines.push('```yaml');
24
- let aria = snapshot.ariaSnapshot || '';
24
+ lines.push("```yaml");
25
+ let aria = snapshot.ariaSnapshot || "";
25
26
  aria = String(truncate(aria, { maxStringLength: MAX_LENGTH }));
26
27
  lines.push(aria);
27
- lines.push('```');
28
- response.addResult(lines.join('\n'));
28
+ lines.push("```");
29
+ response.addResult(lines.join("\n"));
29
30
  }
30
31
  catch (error) {
31
32
  response.addResult(`Failed to get snapshot: ${String(error)}`);
32
33
  }
33
34
  },
34
35
  });
35
- export default [
36
- getSnapshot,
37
- ];
36
+ export default [getSnapshot];
@@ -1,20 +1,35 @@
1
- import { z } from 'zod';
2
- import { sanitizeHtml } from '../utils/sanitizeHtml.js';
3
- import { defineTool } from './tool.js';
1
+ import { z } from "zod";
2
+ import { sanitizeHtml } from "../utils/sanitizeHtml.js";
3
+ import { defineTool } from "./tool.js";
4
4
  const getVisibleHtmlSchema = z.object({
5
- selector: z.string().optional().describe('CSS selector for a specific element (optional)'),
6
- cleanHtml: z.boolean().optional().default(true).describe('Clean HTML (true) or raw HTML (false)'),
7
- removeScripts: z.boolean().optional().default(true).describe('Remove scripts (true) or keep them (false)'),
8
- removeStyles: z.boolean().optional().default(true).describe('Remove styles (true) or keep them (false)'),
5
+ selector: z
6
+ .string()
7
+ .optional()
8
+ .describe("CSS selector for a specific element (optional)"),
9
+ cleanHtml: z
10
+ .boolean()
11
+ .optional()
12
+ .default(true)
13
+ .describe("Clean HTML (true) or raw HTML (false)"),
14
+ removeScripts: z
15
+ .boolean()
16
+ .optional()
17
+ .default(true)
18
+ .describe("Remove scripts (true) or keep them (false)"),
19
+ removeStyles: z
20
+ .boolean()
21
+ .optional()
22
+ .default(true)
23
+ .describe("Remove styles (true) or keep them (false)"),
9
24
  });
10
25
  const getVisibleHtml = defineTool({
11
- capability: 'core',
26
+ capability: "core",
12
27
  schema: {
13
- name: 'browser_get_visible_html',
14
- title: 'Get visible HTML',
15
- description: 'Get HTML content of the page or a specific element',
28
+ name: "browser_get_visible_html",
29
+ title: "Get visible HTML",
30
+ description: "Get HTML content of the page or a specific element",
16
31
  inputSchema: getVisibleHtmlSchema,
17
- type: 'readOnly',
32
+ type: "readOnly",
18
33
  },
19
34
  handle: async (context, params, response) => {
20
35
  const tab = await context.ensureTab();
@@ -28,7 +43,7 @@ const getVisibleHtml = defineTool({
28
43
  const element = await page.$(selector);
29
44
  if (!element)
30
45
  throw new Error(`Element with selector "${selector}" not found`);
31
- htmlContent = await page.evaluate(el => el.outerHTML, element);
46
+ htmlContent = await page.evaluate((el) => el.outerHTML, element);
32
47
  }
33
48
  else {
34
49
  // Otherwise get the full page HTML
@@ -39,7 +54,10 @@ const getVisibleHtml = defineTool({
39
54
  const shouldRemoveStyles = removeStyles || cleanHtml;
40
55
  // Apply filters server-side to avoid TrustedHTML/Trusted Types restrictions in page context
41
56
  if (shouldRemoveScripts || shouldRemoveStyles)
42
- htmlContent = sanitizeHtml(htmlContent, { shouldRemoveScripts, shouldRemoveStyles });
57
+ htmlContent = sanitizeHtml(htmlContent, {
58
+ shouldRemoveScripts,
59
+ shouldRemoveStyles,
60
+ });
43
61
  response.addResult(`HTML content:\n${htmlContent}`);
44
62
  }
45
63
  catch (error) {
@@ -47,6 +65,4 @@ const getVisibleHtml = defineTool({
47
65
  }
48
66
  },
49
67
  });
50
- export default [
51
- getVisibleHtml,
52
- ];
68
+ export default [getVisibleHtml];
@@ -13,39 +13,39 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { fork } from 'child_process';
17
- import path from 'path';
18
- import { z } from 'zod';
19
- import { defineTool } from './tool.js';
16
+ import { fork } from "child_process";
17
+ import path from "path";
18
+ import { z } from "zod";
19
+ import { defineTool } from "./tool.js";
20
20
  const install = defineTool({
21
- capability: 'core-install',
21
+ capability: "core-install",
22
22
  schema: {
23
- name: 'browser_install',
24
- title: 'Install the browser specified in the config',
25
- description: 'Install the browser specified in the config. Call this if you get an error about the browser not being installed.',
23
+ name: "browser_install",
24
+ title: "Install the browser specified in the config",
25
+ description: "Install the browser specified in the config. Call this if you get an error about the browser not being installed.",
26
26
  inputSchema: z.object({}),
27
- type: 'action',
27
+ type: "action",
28
28
  },
29
29
  handle: async (context, params, response) => {
30
- const channel = context.config.browser?.launchOptions?.channel ?? context.config.browser?.browserName ?? 'chrome';
31
- const cliPath = path.join(require.resolve('playwright/package.json'), '../cli.js');
32
- const child = fork(cliPath, ['install', channel], {
33
- stdio: 'pipe',
30
+ const channel = context.config.browser?.launchOptions?.channel ??
31
+ context.config.browser?.browserName ??
32
+ "chrome";
33
+ const cliPath = path.join(require.resolve("playwright/package.json"), "../cli.js");
34
+ const child = fork(cliPath, ["install", channel], {
35
+ stdio: "pipe",
34
36
  });
35
37
  const output = [];
36
- child.stdout?.on('data', data => output.push(data.toString()));
37
- child.stderr?.on('data', data => output.push(data.toString()));
38
+ child.stdout?.on("data", (data) => output.push(data.toString()));
39
+ child.stderr?.on("data", (data) => output.push(data.toString()));
38
40
  await new Promise((resolve, reject) => {
39
- child.on('close', code => {
41
+ child.on("close", (code) => {
40
42
  if (code === 0)
41
43
  resolve();
42
44
  else
43
- reject(new Error(`Failed to install browser: ${output.join('')}`));
45
+ reject(new Error(`Failed to install browser: ${output.join("")}`));
44
46
  });
45
47
  });
46
48
  response.setIncludeTabs();
47
49
  },
48
50
  });
49
- export default [
50
- install,
51
- ];
51
+ export default [install];