@andrew-chen-wang/camoufox-mcp 0.0.1
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/config.d.ts +41 -0
- package/index.d.ts +22 -0
- package/index.js +1 -0
- package/lib/browserContextFactory.js +178 -0
- package/lib/config.js +204 -0
- package/lib/connection.js +84 -0
- package/lib/context.js +361 -0
- package/lib/fileUtils.js +32 -0
- package/lib/index.js +66 -0
- package/lib/javascript.js +49 -0
- package/lib/manualPromise.js +111 -0
- package/lib/mcpHttpServer.js +90 -0
- package/lib/package.js +20 -0
- package/lib/pageSnapshot.js +44 -0
- package/lib/resources/resource.js +16 -0
- package/lib/server.js +48 -0
- package/lib/tab.js +107 -0
- package/lib/tools/common.js +68 -0
- package/lib/tools/console.js +77 -0
- package/lib/tools/devtools.js +189 -0
- package/lib/tools/dialogs.js +52 -0
- package/lib/tools/files.js +51 -0
- package/lib/tools/forms.js +67 -0
- package/lib/tools/install.js +57 -0
- package/lib/tools/keyboard.js +158 -0
- package/lib/tools/mouse.js +119 -0
- package/lib/tools/navigate.js +117 -0
- package/lib/tools/network.js +197 -0
- package/lib/tools/pdf.js +49 -0
- package/lib/tools/screenshot.js +80 -0
- package/lib/tools/snapshot.js +307 -0
- package/lib/tools/storage.js +344 -0
- package/lib/tools/tabs.js +172 -0
- package/lib/tools/testing.js +60 -0
- package/lib/tools/tool.js +18 -0
- package/lib/tools/utils.js +87 -0
- package/lib/tools/vision.js +189 -0
- package/lib/tools/wait.js +59 -0
- package/lib/tools.js +73 -0
- package/package.json +48 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
const networkRequests = defineTool({
|
|
19
|
+
capability: 'core',
|
|
20
|
+
schema: {
|
|
21
|
+
name: 'browser_network_requests',
|
|
22
|
+
title: 'List network requests',
|
|
23
|
+
description: 'Returns all network requests since loading the page',
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
static: z.boolean().optional().describe('Include static resources like images, scripts, fonts, and stylesheets'),
|
|
26
|
+
requestBody: z.boolean().optional().describe('Include request body'),
|
|
27
|
+
requestHeaders: z.boolean().optional().describe('Include request headers'),
|
|
28
|
+
filter: z.string().optional().describe('Only include requests whose URL matches this regular expression'),
|
|
29
|
+
}),
|
|
30
|
+
type: 'readOnly',
|
|
31
|
+
},
|
|
32
|
+
handle: async (context, params) => {
|
|
33
|
+
const entries = [...context.currentTabOrDie().requests().entries()]
|
|
34
|
+
.filter(([request, response]) => includeRequest(request, response, !!params.static, params.filter));
|
|
35
|
+
const log = entries.map(([request, response]) => renderRequest(request, response, !!params.requestBody, !!params.requestHeaders)).join('\n\n');
|
|
36
|
+
return {
|
|
37
|
+
code: ['// <internal code to list network requests>'],
|
|
38
|
+
action: async () => ({ content: [{ type: 'text', text: log }] }),
|
|
39
|
+
captureSnapshot: false,
|
|
40
|
+
waitForNetwork: false,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const networkClear = defineTool({
|
|
45
|
+
capability: 'core',
|
|
46
|
+
schema: {
|
|
47
|
+
name: 'browser_network_clear',
|
|
48
|
+
title: 'Clear network requests',
|
|
49
|
+
description: 'Clear the collected network request log',
|
|
50
|
+
inputSchema: z.object({}),
|
|
51
|
+
type: 'destructive',
|
|
52
|
+
},
|
|
53
|
+
handle: async (context) => {
|
|
54
|
+
context.currentTabOrDie().clearRequests();
|
|
55
|
+
return {
|
|
56
|
+
code: ['// <internal code to clear network requests>'],
|
|
57
|
+
captureSnapshot: false,
|
|
58
|
+
waitForNetwork: false,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const route = defineTool({
|
|
63
|
+
capability: 'core',
|
|
64
|
+
schema: {
|
|
65
|
+
name: 'browser_route',
|
|
66
|
+
title: 'Mock route',
|
|
67
|
+
description: 'Mock network requests matching a URL pattern',
|
|
68
|
+
inputSchema: z.object({
|
|
69
|
+
pattern: z.string(),
|
|
70
|
+
status: z.number().optional(),
|
|
71
|
+
body: z.string().optional(),
|
|
72
|
+
contentType: z.string().optional(),
|
|
73
|
+
headers: z.array(z.string()).optional(),
|
|
74
|
+
removeHeaders: z.array(z.string()).optional(),
|
|
75
|
+
}),
|
|
76
|
+
type: 'destructive',
|
|
77
|
+
},
|
|
78
|
+
handle: async (context, params) => ({
|
|
79
|
+
code: ['// <internal code to add route>'],
|
|
80
|
+
action: async () => {
|
|
81
|
+
await context.addRouteRule(params.pattern, async (route) => {
|
|
82
|
+
const headers = parseHeaders(params.headers);
|
|
83
|
+
if (params.removeHeaders) {
|
|
84
|
+
for (const name of params.removeHeaders)
|
|
85
|
+
delete headers[name.toLowerCase()];
|
|
86
|
+
}
|
|
87
|
+
if (params.contentType)
|
|
88
|
+
headers['content-type'] = params.contentType;
|
|
89
|
+
await route.fulfill({
|
|
90
|
+
status: params.status ?? 200,
|
|
91
|
+
body: params.body ?? '',
|
|
92
|
+
headers,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
captureSnapshot: false,
|
|
97
|
+
waitForNetwork: false,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const routeList = defineTool({
|
|
101
|
+
capability: 'core',
|
|
102
|
+
schema: {
|
|
103
|
+
name: 'browser_route_list',
|
|
104
|
+
title: 'List routes',
|
|
105
|
+
description: 'List active network route patterns',
|
|
106
|
+
inputSchema: z.object({}),
|
|
107
|
+
type: 'readOnly',
|
|
108
|
+
},
|
|
109
|
+
handle: async (context) => ({
|
|
110
|
+
code: ['// <internal code to list routes>'],
|
|
111
|
+
action: async () => ({ content: [{ type: 'text', text: context.listRouteRules().join('\n') }] }),
|
|
112
|
+
captureSnapshot: false,
|
|
113
|
+
waitForNetwork: false,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
const unroute = defineTool({
|
|
117
|
+
capability: 'core',
|
|
118
|
+
schema: {
|
|
119
|
+
name: 'browser_unroute',
|
|
120
|
+
title: 'Remove routes',
|
|
121
|
+
description: 'Remove route mocks matching a pattern, or all routes',
|
|
122
|
+
inputSchema: z.object({
|
|
123
|
+
pattern: z.string().optional(),
|
|
124
|
+
}),
|
|
125
|
+
type: 'destructive',
|
|
126
|
+
},
|
|
127
|
+
handle: async (context, params) => ({
|
|
128
|
+
code: ['// <internal code to remove routes>'],
|
|
129
|
+
action: async () => {
|
|
130
|
+
await context.removeRouteRule(params.pattern);
|
|
131
|
+
},
|
|
132
|
+
captureSnapshot: false,
|
|
133
|
+
waitForNetwork: false,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
const networkState = defineTool({
|
|
137
|
+
capability: 'core',
|
|
138
|
+
schema: {
|
|
139
|
+
name: 'browser_network_state_set',
|
|
140
|
+
title: 'Set network state',
|
|
141
|
+
description: 'Set the browser network state to online or offline',
|
|
142
|
+
inputSchema: z.object({
|
|
143
|
+
state: z.enum(['online', 'offline']),
|
|
144
|
+
}),
|
|
145
|
+
type: 'destructive',
|
|
146
|
+
},
|
|
147
|
+
handle: async (context, params) => ({
|
|
148
|
+
code: [`// Set network state to ${params.state}`],
|
|
149
|
+
action: async () => {
|
|
150
|
+
await context.setNetworkOffline(params.state === 'offline');
|
|
151
|
+
},
|
|
152
|
+
captureSnapshot: false,
|
|
153
|
+
waitForNetwork: false,
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
function includeRequest(request, response, includeStatic, filter) {
|
|
157
|
+
if (filter && !(new RegExp(filter).test(request.url())))
|
|
158
|
+
return false;
|
|
159
|
+
if (includeStatic)
|
|
160
|
+
return true;
|
|
161
|
+
const staticTypes = new Set(['image', 'media', 'font', 'stylesheet', 'script']);
|
|
162
|
+
if (response && response.ok() && staticTypes.has(request.resourceType()))
|
|
163
|
+
return false;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
function renderRequest(request, response, includeBody, includeHeaders) {
|
|
167
|
+
const result = [];
|
|
168
|
+
result.push(`[${request.method().toUpperCase()}] ${request.url()}`);
|
|
169
|
+
if (response)
|
|
170
|
+
result.push(`=> [${response.status()}] ${response.statusText()}`);
|
|
171
|
+
if (includeHeaders)
|
|
172
|
+
result.push(`headers: ${JSON.stringify(request.headers(), null, 2)}`);
|
|
173
|
+
if (includeBody) {
|
|
174
|
+
const body = request.postData();
|
|
175
|
+
if (body)
|
|
176
|
+
result.push(`body: ${body}`);
|
|
177
|
+
}
|
|
178
|
+
return result.join('\n');
|
|
179
|
+
}
|
|
180
|
+
function parseHeaders(headers) {
|
|
181
|
+
const result = {};
|
|
182
|
+
for (const entry of headers ?? []) {
|
|
183
|
+
const separator = entry.indexOf(':');
|
|
184
|
+
if (separator === -1)
|
|
185
|
+
continue;
|
|
186
|
+
result[entry.slice(0, separator).trim().toLowerCase()] = entry.slice(separator + 1).trim();
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
export default [
|
|
191
|
+
networkRequests,
|
|
192
|
+
networkClear,
|
|
193
|
+
route,
|
|
194
|
+
routeList,
|
|
195
|
+
unroute,
|
|
196
|
+
networkState,
|
|
197
|
+
];
|
package/lib/tools/pdf.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
import * as javascript from '../javascript.js';
|
|
19
|
+
import { outputFile } from '../config.js';
|
|
20
|
+
const pdfSchema = z.object({
|
|
21
|
+
filename: z.string().optional().describe('File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.'),
|
|
22
|
+
});
|
|
23
|
+
const pdf = defineTool({
|
|
24
|
+
capability: 'pdf',
|
|
25
|
+
schema: {
|
|
26
|
+
name: 'browser_pdf_save',
|
|
27
|
+
title: 'Save as PDF',
|
|
28
|
+
description: 'Save page as PDF',
|
|
29
|
+
inputSchema: pdfSchema,
|
|
30
|
+
type: 'readOnly',
|
|
31
|
+
},
|
|
32
|
+
handle: async (context, params) => {
|
|
33
|
+
const tab = context.currentTabOrDie();
|
|
34
|
+
const fileName = await outputFile(context.config, params.filename ?? `page-${new Date().toISOString()}.pdf`);
|
|
35
|
+
const code = [
|
|
36
|
+
`// Save page as ${fileName}`,
|
|
37
|
+
`await page.pdf(${javascript.formatObject({ path: fileName })});`,
|
|
38
|
+
];
|
|
39
|
+
return {
|
|
40
|
+
code,
|
|
41
|
+
action: async () => tab.page.pdf({ path: fileName }).then(() => { }),
|
|
42
|
+
captureSnapshot: false,
|
|
43
|
+
waitForNetwork: false,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
export default [
|
|
48
|
+
pdf,
|
|
49
|
+
];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
import * as javascript from '../javascript.js';
|
|
19
|
+
import { outputFile } from '../config.js';
|
|
20
|
+
import { generateLocator, resolveLocator } from './utils.js';
|
|
21
|
+
const screenshotSchema = z.object({
|
|
22
|
+
raw: z.boolean().optional().describe('Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.'),
|
|
23
|
+
filename: z.string().optional().describe('File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified.'),
|
|
24
|
+
element: z.string().optional().describe('Optional human-readable target element description. If not provided, selector or ref can still be used.'),
|
|
25
|
+
ref: z.string().optional().describe('Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too.'),
|
|
26
|
+
selector: z.string().optional().describe('Selector for the target element. Mutually exclusive with ref.'),
|
|
27
|
+
fullPage: z.boolean().optional().describe('Capture the full page instead of the current viewport.'),
|
|
28
|
+
}).refine(data => {
|
|
29
|
+
const targetCount = Number(!!data.ref) + Number(!!data.selector);
|
|
30
|
+
return targetCount <= 1;
|
|
31
|
+
}, {
|
|
32
|
+
message: 'Provide at most one of ref or selector.',
|
|
33
|
+
path: ['ref']
|
|
34
|
+
});
|
|
35
|
+
const screenshot = defineTool({
|
|
36
|
+
capability: 'core',
|
|
37
|
+
schema: {
|
|
38
|
+
name: 'browser_take_screenshot',
|
|
39
|
+
title: 'Take a screenshot',
|
|
40
|
+
description: `Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.`,
|
|
41
|
+
inputSchema: screenshotSchema,
|
|
42
|
+
type: 'readOnly',
|
|
43
|
+
},
|
|
44
|
+
handle: async (context, params) => {
|
|
45
|
+
const tab = context.currentTabOrDie();
|
|
46
|
+
const snapshot = tab.snapshotOrDie();
|
|
47
|
+
const fileType = params.raw ? 'png' : 'jpeg';
|
|
48
|
+
const fileName = await outputFile(context.config, params.filename ?? `page-${new Date().toISOString()}.${fileType}`);
|
|
49
|
+
const options = { type: fileType, quality: fileType === 'png' ? undefined : 50, scale: 'css', path: fileName, fullPage: params.fullPage };
|
|
50
|
+
const isElementScreenshot = !!params.element;
|
|
51
|
+
const code = [
|
|
52
|
+
`// Screenshot ${isElementScreenshot ? params.element : 'viewport'} and save it as ${fileName}`,
|
|
53
|
+
];
|
|
54
|
+
const locator = params.element ? resolveLocator(tab, { element: params.element, ref: params.ref, selector: params.selector }) : null;
|
|
55
|
+
if (locator)
|
|
56
|
+
code.push(`await page.${await generateLocator(locator)}.screenshot(${javascript.formatObject(options)});`);
|
|
57
|
+
else
|
|
58
|
+
code.push(`await page.screenshot(${javascript.formatObject(options)});`);
|
|
59
|
+
const includeBase64 = context.clientSupportsImages();
|
|
60
|
+
const action = async () => {
|
|
61
|
+
const screenshot = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
|
|
62
|
+
return {
|
|
63
|
+
content: includeBase64 ? [{
|
|
64
|
+
type: 'image',
|
|
65
|
+
data: screenshot.toString('base64'),
|
|
66
|
+
mimeType: fileType === 'png' ? 'image/png' : 'image/jpeg',
|
|
67
|
+
}] : []
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
code,
|
|
72
|
+
action,
|
|
73
|
+
captureSnapshot: true,
|
|
74
|
+
waitForNetwork: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
export default [
|
|
79
|
+
screenshot,
|
|
80
|
+
];
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
import * as javascript from '../javascript.js';
|
|
19
|
+
import { generateLocator, resolveLocator } from './utils.js';
|
|
20
|
+
const snapshot = defineTool({
|
|
21
|
+
capability: 'core',
|
|
22
|
+
schema: {
|
|
23
|
+
name: 'browser_snapshot',
|
|
24
|
+
title: 'Page snapshot',
|
|
25
|
+
description: 'Capture accessibility snapshot of the current page, this is better than screenshot',
|
|
26
|
+
inputSchema: z.object({
|
|
27
|
+
filename: z.string().optional().describe('Ignored compatibility field accepted for Playwright CLI parity.'),
|
|
28
|
+
selector: z.string().optional().describe('Ignored compatibility field accepted for Playwright CLI parity.'),
|
|
29
|
+
depth: z.number().optional().describe('Ignored compatibility field accepted for Playwright CLI parity.'),
|
|
30
|
+
}),
|
|
31
|
+
type: 'readOnly',
|
|
32
|
+
},
|
|
33
|
+
handle: async (context) => {
|
|
34
|
+
await context.ensureTab();
|
|
35
|
+
return {
|
|
36
|
+
code: ['// <internal code to capture accessibility snapshot>'],
|
|
37
|
+
captureSnapshot: true,
|
|
38
|
+
waitForNetwork: false,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const elementBaseSchema = z.object({
|
|
43
|
+
element: z.string().optional().describe('Optional human-readable element description'),
|
|
44
|
+
ref: z.string().optional().describe('Exact target element reference from the page snapshot'),
|
|
45
|
+
selector: z.string().optional().describe('Selector for the target element'),
|
|
46
|
+
});
|
|
47
|
+
const elementSchema = elementBaseSchema.refine(value => !!value.ref !== !!value.selector, {
|
|
48
|
+
message: 'Provide exactly one of ref or selector.',
|
|
49
|
+
path: ['ref'],
|
|
50
|
+
});
|
|
51
|
+
const click = defineTool({
|
|
52
|
+
capability: 'core',
|
|
53
|
+
schema: {
|
|
54
|
+
name: 'browser_click',
|
|
55
|
+
title: 'Click',
|
|
56
|
+
description: 'Perform click on a web page',
|
|
57
|
+
inputSchema: elementBaseSchema.extend({
|
|
58
|
+
button: z.enum(['left', 'middle', 'right']).optional(),
|
|
59
|
+
modifiers: z.array(z.enum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift'])).optional(),
|
|
60
|
+
doubleClick: z.boolean().optional(),
|
|
61
|
+
}).refine(value => !!value.ref !== !!value.selector, {
|
|
62
|
+
message: 'Provide exactly one of ref or selector.',
|
|
63
|
+
path: ['ref'],
|
|
64
|
+
}),
|
|
65
|
+
type: 'destructive',
|
|
66
|
+
},
|
|
67
|
+
handle: async (context, params) => {
|
|
68
|
+
const tab = context.currentTabOrDie();
|
|
69
|
+
const locator = resolveLocator(tab, params);
|
|
70
|
+
const options = {
|
|
71
|
+
button: params.button,
|
|
72
|
+
modifiers: params.modifiers,
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
code: [
|
|
76
|
+
`// Click ${params.element ?? params.selector ?? params.ref}`,
|
|
77
|
+
`await page.${await generateLocator(locator)}.${params.doubleClick ? 'dblclick' : 'click'}(${javascript.formatObject(options)});`,
|
|
78
|
+
],
|
|
79
|
+
action: () => params.doubleClick ? locator.dblclick(options) : locator.click(options),
|
|
80
|
+
captureSnapshot: true,
|
|
81
|
+
waitForNetwork: true,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const doubleClick = defineTool({
|
|
86
|
+
capability: 'core',
|
|
87
|
+
schema: {
|
|
88
|
+
name: 'browser_double_click',
|
|
89
|
+
title: 'Double click',
|
|
90
|
+
description: 'Perform double click on a web page',
|
|
91
|
+
inputSchema: elementSchema,
|
|
92
|
+
type: 'destructive',
|
|
93
|
+
},
|
|
94
|
+
handle: async (context, params) => {
|
|
95
|
+
const tab = context.currentTabOrDie();
|
|
96
|
+
const locator = resolveLocator(tab, params);
|
|
97
|
+
return {
|
|
98
|
+
code: [
|
|
99
|
+
`// Double click ${params.element ?? params.selector ?? params.ref}`,
|
|
100
|
+
`await page.${await generateLocator(locator)}.dblclick();`,
|
|
101
|
+
],
|
|
102
|
+
action: () => locator.dblclick(),
|
|
103
|
+
captureSnapshot: true,
|
|
104
|
+
waitForNetwork: true,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const drag = defineTool({
|
|
109
|
+
capability: 'core',
|
|
110
|
+
schema: {
|
|
111
|
+
name: 'browser_drag',
|
|
112
|
+
title: 'Drag mouse',
|
|
113
|
+
description: 'Perform drag and drop between two elements',
|
|
114
|
+
inputSchema: z.object({
|
|
115
|
+
startElement: z.string().optional().describe('Optional human-readable source element description'),
|
|
116
|
+
startRef: z.string().optional().describe('Exact source element reference from the page snapshot'),
|
|
117
|
+
startSelector: z.string().optional().describe('Selector for the source element'),
|
|
118
|
+
endElement: z.string().optional().describe('Optional human-readable target element description'),
|
|
119
|
+
endRef: z.string().optional().describe('Exact target element reference from the page snapshot'),
|
|
120
|
+
endSelector: z.string().optional().describe('Selector for the target element'),
|
|
121
|
+
}).refine(value => !!value.startRef !== !!value.startSelector, {
|
|
122
|
+
message: 'Provide exactly one of startRef or startSelector.',
|
|
123
|
+
path: ['startRef'],
|
|
124
|
+
}).refine(value => !!value.endRef !== !!value.endSelector, {
|
|
125
|
+
message: 'Provide exactly one of endRef or endSelector.',
|
|
126
|
+
path: ['endRef'],
|
|
127
|
+
}),
|
|
128
|
+
type: 'destructive',
|
|
129
|
+
},
|
|
130
|
+
handle: async (context, params) => {
|
|
131
|
+
const tab = context.currentTabOrDie();
|
|
132
|
+
const startLocator = resolveLocator(tab, { element: params.startElement, ref: params.startRef, selector: params.startSelector });
|
|
133
|
+
const endLocator = resolveLocator(tab, { element: params.endElement, ref: params.endRef, selector: params.endSelector });
|
|
134
|
+
return {
|
|
135
|
+
code: [
|
|
136
|
+
`// Drag ${params.startElement ?? params.startSelector ?? params.startRef} to ${params.endElement ?? params.endSelector ?? params.endRef}`,
|
|
137
|
+
`await page.${await generateLocator(startLocator)}.dragTo(page.${await generateLocator(endLocator)});`,
|
|
138
|
+
],
|
|
139
|
+
action: () => startLocator.dragTo(endLocator),
|
|
140
|
+
captureSnapshot: true,
|
|
141
|
+
waitForNetwork: true,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const hover = defineTool({
|
|
146
|
+
capability: 'core',
|
|
147
|
+
schema: {
|
|
148
|
+
name: 'browser_hover',
|
|
149
|
+
title: 'Hover mouse',
|
|
150
|
+
description: 'Hover over element on page',
|
|
151
|
+
inputSchema: elementSchema,
|
|
152
|
+
type: 'readOnly',
|
|
153
|
+
},
|
|
154
|
+
handle: async (context, params) => {
|
|
155
|
+
const tab = context.currentTabOrDie();
|
|
156
|
+
const locator = resolveLocator(tab, params);
|
|
157
|
+
return {
|
|
158
|
+
code: [
|
|
159
|
+
`// Hover over ${params.element ?? params.selector ?? params.ref}`,
|
|
160
|
+
`await page.${await generateLocator(locator)}.hover();`,
|
|
161
|
+
],
|
|
162
|
+
action: () => locator.hover(),
|
|
163
|
+
captureSnapshot: true,
|
|
164
|
+
waitForNetwork: true,
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const fill = defineTool({
|
|
169
|
+
capability: 'core',
|
|
170
|
+
schema: {
|
|
171
|
+
name: 'browser_fill',
|
|
172
|
+
title: 'Fill text',
|
|
173
|
+
description: 'Fill text into editable element',
|
|
174
|
+
inputSchema: elementBaseSchema.extend({
|
|
175
|
+
text: z.string().describe('Text to fill into the element'),
|
|
176
|
+
}).refine(value => !!value.ref !== !!value.selector, {
|
|
177
|
+
message: 'Provide exactly one of ref or selector.',
|
|
178
|
+
path: ['ref'],
|
|
179
|
+
}),
|
|
180
|
+
type: 'destructive',
|
|
181
|
+
},
|
|
182
|
+
handle: async (context, params) => {
|
|
183
|
+
const tab = context.currentTabOrDie();
|
|
184
|
+
const locator = resolveLocator(tab, params);
|
|
185
|
+
return {
|
|
186
|
+
code: [
|
|
187
|
+
`// Fill "${params.text}" into "${params.element ?? params.selector ?? params.ref}"`,
|
|
188
|
+
`await page.${await generateLocator(locator)}.fill(${javascript.quote(params.text)});`,
|
|
189
|
+
],
|
|
190
|
+
action: () => locator.fill(params.text),
|
|
191
|
+
captureSnapshot: true,
|
|
192
|
+
waitForNetwork: true,
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const typeSchema = elementBaseSchema.extend({
|
|
197
|
+
text: z.string().describe('Text to type into the element'),
|
|
198
|
+
submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'),
|
|
199
|
+
slowly: z.boolean().optional().describe('Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.'),
|
|
200
|
+
}).refine(value => !!value.ref !== !!value.selector, {
|
|
201
|
+
message: 'Provide exactly one of ref or selector.',
|
|
202
|
+
path: ['ref'],
|
|
203
|
+
});
|
|
204
|
+
const type = defineTool({
|
|
205
|
+
capability: 'core',
|
|
206
|
+
schema: {
|
|
207
|
+
name: 'browser_type',
|
|
208
|
+
title: 'Type text',
|
|
209
|
+
description: 'Type text into editable element',
|
|
210
|
+
inputSchema: typeSchema,
|
|
211
|
+
type: 'destructive',
|
|
212
|
+
},
|
|
213
|
+
handle: async (context, params) => {
|
|
214
|
+
const tab = context.currentTabOrDie();
|
|
215
|
+
const locator = resolveLocator(tab, params);
|
|
216
|
+
const code = [];
|
|
217
|
+
const steps = [];
|
|
218
|
+
if (params.slowly) {
|
|
219
|
+
code.push(`// Press "${params.text}" sequentially into "${params.element ?? params.selector ?? params.ref}"`);
|
|
220
|
+
code.push(`await page.${await generateLocator(locator)}.pressSequentially(${javascript.quote(params.text)});`);
|
|
221
|
+
steps.push(() => locator.pressSequentially(params.text));
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
code.push(`// Fill "${params.text}" into "${params.element ?? params.selector ?? params.ref}"`);
|
|
225
|
+
code.push(`await page.${await generateLocator(locator)}.fill(${javascript.quote(params.text)});`);
|
|
226
|
+
steps.push(() => locator.fill(params.text));
|
|
227
|
+
}
|
|
228
|
+
if (params.submit) {
|
|
229
|
+
code.push('// Submit text');
|
|
230
|
+
code.push(`await page.${await generateLocator(locator)}.press('Enter');`);
|
|
231
|
+
steps.push(() => locator.press('Enter'));
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
code,
|
|
235
|
+
action: () => steps.reduce((acc, step) => acc.then(step), Promise.resolve()),
|
|
236
|
+
captureSnapshot: true,
|
|
237
|
+
waitForNetwork: true,
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
const selectOption = defineTool({
|
|
242
|
+
capability: 'core',
|
|
243
|
+
schema: {
|
|
244
|
+
name: 'browser_select_option',
|
|
245
|
+
title: 'Select option',
|
|
246
|
+
description: 'Select an option in a dropdown',
|
|
247
|
+
inputSchema: elementBaseSchema.extend({
|
|
248
|
+
values: z.array(z.string()).describe('Array of values to select in the dropdown. This can be a single value or multiple values.'),
|
|
249
|
+
}).refine(value => !!value.ref !== !!value.selector, {
|
|
250
|
+
message: 'Provide exactly one of ref or selector.',
|
|
251
|
+
path: ['ref'],
|
|
252
|
+
}),
|
|
253
|
+
type: 'destructive',
|
|
254
|
+
},
|
|
255
|
+
handle: async (context, params) => {
|
|
256
|
+
const tab = context.currentTabOrDie();
|
|
257
|
+
const locator = resolveLocator(tab, params);
|
|
258
|
+
return {
|
|
259
|
+
code: [
|
|
260
|
+
`// Select options [${params.values.join(', ')}] in ${params.element ?? params.selector ?? params.ref}`,
|
|
261
|
+
`await page.${await generateLocator(locator)}.selectOption(${javascript.formatObject(params.values)});`,
|
|
262
|
+
],
|
|
263
|
+
action: () => locator.selectOption(params.values).then(() => { }),
|
|
264
|
+
captureSnapshot: true,
|
|
265
|
+
waitForNetwork: true,
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
function defineCheckTool(name, title, checked) {
|
|
270
|
+
return defineTool({
|
|
271
|
+
capability: 'core',
|
|
272
|
+
schema: {
|
|
273
|
+
name,
|
|
274
|
+
title,
|
|
275
|
+
description: checked ? 'Check a checkbox or radio input' : 'Uncheck a checkbox input',
|
|
276
|
+
inputSchema: elementSchema,
|
|
277
|
+
type: 'destructive',
|
|
278
|
+
},
|
|
279
|
+
handle: async (context, params) => {
|
|
280
|
+
const tab = context.currentTabOrDie();
|
|
281
|
+
const locator = resolveLocator(tab, params);
|
|
282
|
+
return {
|
|
283
|
+
code: [
|
|
284
|
+
`// ${checked ? 'Check' : 'Uncheck'} ${params.element ?? params.selector ?? params.ref}`,
|
|
285
|
+
`await page.${await generateLocator(locator)}.${checked ? 'check' : 'uncheck'}();`,
|
|
286
|
+
],
|
|
287
|
+
action: () => checked ? locator.check() : locator.uncheck(),
|
|
288
|
+
captureSnapshot: true,
|
|
289
|
+
waitForNetwork: true,
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
const check = defineCheckTool('browser_check', 'Check element', true);
|
|
295
|
+
const uncheck = defineCheckTool('browser_uncheck', 'Uncheck element', false);
|
|
296
|
+
export default [
|
|
297
|
+
snapshot,
|
|
298
|
+
click,
|
|
299
|
+
doubleClick,
|
|
300
|
+
drag,
|
|
301
|
+
hover,
|
|
302
|
+
fill,
|
|
303
|
+
type,
|
|
304
|
+
selectOption,
|
|
305
|
+
check,
|
|
306
|
+
uncheck,
|
|
307
|
+
];
|