chrome-devtools-mcp 0.18.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/build/src/McpContext.js +242 -266
- package/build/src/McpPage.js +95 -0
- package/build/src/McpResponse.js +124 -48
- package/build/src/bin/chrome-devtools-cli-options.js +651 -0
- package/build/src/{cli.js → bin/chrome-devtools-mcp-cli-options.js} +12 -2
- package/build/src/bin/chrome-devtools-mcp-main.js +35 -0
- package/build/src/bin/chrome-devtools-mcp.js +21 -0
- package/build/src/bin/chrome-devtools.js +185 -0
- package/build/src/bin/cliDefinitions.js +615 -0
- package/build/src/browser.js +13 -12
- package/build/src/daemon/client.js +152 -0
- package/build/src/daemon/daemon.js +56 -17
- package/build/src/daemon/types.js +6 -0
- package/build/src/daemon/utils.js +57 -16
- package/build/src/index.js +204 -16
- package/build/src/telemetry/watchdog/ClearcutSender.js +2 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +1480 -111
- package/build/src/third_party/bundled-packages.json +4 -3
- package/build/src/third_party/devtools-formatter-worker.js +5 -14
- package/build/src/third_party/index.js +2128 -472
- package/build/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +7 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +54183 -0
- package/build/src/tools/ToolDefinition.js +52 -0
- package/build/src/tools/console.js +3 -3
- package/build/src/tools/emulation.js +13 -45
- package/build/src/tools/extensions.js +17 -0
- package/build/src/tools/input.js +33 -33
- package/build/src/tools/lighthouse.js +123 -0
- package/build/src/tools/memory.js +5 -5
- package/build/src/tools/network.js +7 -7
- package/build/src/tools/pages.js +32 -32
- package/build/src/tools/performance.js +16 -14
- package/build/src/tools/screencast.js +5 -5
- package/build/src/tools/screenshot.js +6 -6
- package/build/src/tools/script.js +99 -49
- package/build/src/tools/slim/tools.js +18 -18
- package/build/src/tools/snapshot.js +5 -4
- package/build/src/tools/tools.js +2 -0
- package/build/src/types.js +6 -0
- package/build/src/utils/files.js +19 -0
- package/build/src/version.js +1 -1
- package/package.json +15 -9
- package/build/src/main.js +0 -203
|
@@ -5,9 +5,33 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
export function defineTool(definition) {
|
|
8
|
+
if (typeof definition === 'function') {
|
|
9
|
+
const factory = definition;
|
|
10
|
+
return (args) => {
|
|
11
|
+
return factory(args);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
8
14
|
return definition;
|
|
9
15
|
}
|
|
16
|
+
export function definePageTool(definition) {
|
|
17
|
+
if (typeof definition === 'function') {
|
|
18
|
+
return (args) => {
|
|
19
|
+
const tool = definition(args);
|
|
20
|
+
return {
|
|
21
|
+
...tool,
|
|
22
|
+
pageScoped: true,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...definition,
|
|
28
|
+
pageScoped: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
10
31
|
export const CLOSE_PAGE_ERROR = 'The last open page cannot be closed. It is fine to keep it open.';
|
|
32
|
+
export const pageIdSchema = {
|
|
33
|
+
pageId: zod.number().optional().describe('Targets a specific page by ID.'),
|
|
34
|
+
};
|
|
11
35
|
export const timeoutSchema = {
|
|
12
36
|
timeout: zod
|
|
13
37
|
.number()
|
|
@@ -18,3 +42,31 @@ export const timeoutSchema = {
|
|
|
18
42
|
return value && value <= 0 ? undefined : value;
|
|
19
43
|
}),
|
|
20
44
|
};
|
|
45
|
+
export function viewportTransform(arg) {
|
|
46
|
+
if (!arg) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const [dimensions, ...tags] = arg.split(',');
|
|
50
|
+
const isMobile = tags.includes('mobile');
|
|
51
|
+
const hasTouch = tags.includes('touch');
|
|
52
|
+
const isLandscape = tags.includes('landscape');
|
|
53
|
+
const [width, height, dpr] = dimensions.split('x').map(Number);
|
|
54
|
+
return {
|
|
55
|
+
width,
|
|
56
|
+
height,
|
|
57
|
+
deviceScaleFactor: dpr,
|
|
58
|
+
isMobile: isMobile,
|
|
59
|
+
isLandscape: isLandscape,
|
|
60
|
+
hasTouch: hasTouch,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function geolocationTransform(arg) {
|
|
64
|
+
if (!arg) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const [latitude, longitude] = arg.split('x').map(Number);
|
|
68
|
+
return {
|
|
69
|
+
latitude,
|
|
70
|
+
longitude,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import {
|
|
8
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
9
9
|
const FILTERABLE_MESSAGE_TYPES = [
|
|
10
10
|
'log',
|
|
11
11
|
'debug',
|
|
@@ -28,7 +28,7 @@ const FILTERABLE_MESSAGE_TYPES = [
|
|
|
28
28
|
'verbose',
|
|
29
29
|
'issue',
|
|
30
30
|
];
|
|
31
|
-
export const listConsoleMessages =
|
|
31
|
+
export const listConsoleMessages = definePageTool({
|
|
32
32
|
name: 'list_console_messages',
|
|
33
33
|
description: 'List all console messages for the currently selected page since the last navigation.',
|
|
34
34
|
annotations: {
|
|
@@ -67,7 +67,7 @@ export const listConsoleMessages = defineTool({
|
|
|
67
67
|
});
|
|
68
68
|
},
|
|
69
69
|
});
|
|
70
|
-
export const getConsoleMessage =
|
|
70
|
+
export const getConsoleMessage = definePageTool({
|
|
71
71
|
name: 'get_console_message',
|
|
72
72
|
description: `Gets a console message by its ID. You can get all messages by calling ${listConsoleMessages.name}.`,
|
|
73
73
|
annotations: {
|
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { zod, PredefinedNetworkConditions } from '../third_party/index.js';
|
|
8
8
|
import { ToolCategory } from './categories.js';
|
|
9
|
-
import {
|
|
9
|
+
import { definePageTool, geolocationTransform, viewportTransform, } from './ToolDefinition.js';
|
|
10
10
|
const throttlingOptions = [
|
|
11
|
-
'No emulation',
|
|
12
11
|
'Offline',
|
|
13
12
|
...Object.keys(PredefinedNetworkConditions),
|
|
14
13
|
];
|
|
15
|
-
export const emulate =
|
|
14
|
+
export const emulate = definePageTool({
|
|
16
15
|
name: 'emulate',
|
|
17
16
|
description: `Emulates various features on the selected page.`,
|
|
18
17
|
annotations: {
|
|
@@ -23,65 +22,34 @@ export const emulate = defineTool({
|
|
|
23
22
|
networkConditions: zod
|
|
24
23
|
.enum(throttlingOptions)
|
|
25
24
|
.optional()
|
|
26
|
-
.describe(`Throttle network.
|
|
25
|
+
.describe(`Throttle network. Omit to disable throttling.`),
|
|
27
26
|
cpuThrottlingRate: zod
|
|
28
27
|
.number()
|
|
29
28
|
.min(1)
|
|
30
29
|
.max(20)
|
|
31
30
|
.optional()
|
|
32
|
-
.describe('Represents the CPU slowdown factor.
|
|
31
|
+
.describe('Represents the CPU slowdown factor. Omit or set the rate to 1 to disable throttling'),
|
|
33
32
|
geolocation: zod
|
|
34
|
-
.
|
|
35
|
-
latitude: zod
|
|
36
|
-
.number()
|
|
37
|
-
.min(-90)
|
|
38
|
-
.max(90)
|
|
39
|
-
.describe('Latitude between -90 and 90.'),
|
|
40
|
-
longitude: zod
|
|
41
|
-
.number()
|
|
42
|
-
.min(-180)
|
|
43
|
-
.max(180)
|
|
44
|
-
.describe('Longitude between -180 and 180.'),
|
|
45
|
-
})
|
|
46
|
-
.nullable()
|
|
33
|
+
.string()
|
|
47
34
|
.optional()
|
|
48
|
-
.
|
|
35
|
+
.transform(geolocationTransform)
|
|
36
|
+
.describe('Geolocation (`<latitude>x<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override.'),
|
|
49
37
|
userAgent: zod
|
|
50
38
|
.string()
|
|
51
|
-
.nullable()
|
|
52
39
|
.optional()
|
|
53
|
-
.describe('User agent to emulate. Set to
|
|
40
|
+
.describe('User agent to emulate. Set to empty string to clear the user agent override.'),
|
|
54
41
|
colorScheme: zod
|
|
55
42
|
.enum(['dark', 'light', 'auto'])
|
|
56
43
|
.optional()
|
|
57
44
|
.describe('Emulate the dark or the light mode. Set to "auto" to reset to the default.'),
|
|
58
45
|
viewport: zod
|
|
59
|
-
.
|
|
60
|
-
width: zod.number().int().min(0).describe('Page width in pixels.'),
|
|
61
|
-
height: zod.number().int().min(0).describe('Page height in pixels.'),
|
|
62
|
-
deviceScaleFactor: zod
|
|
63
|
-
.number()
|
|
64
|
-
.min(0)
|
|
65
|
-
.optional()
|
|
66
|
-
.describe('Specify device scale factor (can be thought of as dpr).'),
|
|
67
|
-
isMobile: zod
|
|
68
|
-
.boolean()
|
|
69
|
-
.optional()
|
|
70
|
-
.describe('Whether the meta viewport tag is taken into account. Defaults to false.'),
|
|
71
|
-
hasTouch: zod
|
|
72
|
-
.boolean()
|
|
73
|
-
.optional()
|
|
74
|
-
.describe('Specifies if viewport supports touch events. This should be set to true for mobile devices.'),
|
|
75
|
-
isLandscape: zod
|
|
76
|
-
.boolean()
|
|
77
|
-
.optional()
|
|
78
|
-
.describe('Specifies if viewport is in landscape mode. Defaults to false.'),
|
|
79
|
-
})
|
|
80
|
-
.nullable()
|
|
46
|
+
.string()
|
|
81
47
|
.optional()
|
|
82
|
-
.
|
|
48
|
+
.transform(viewportTransform)
|
|
49
|
+
.describe(`Emulate device viewports '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'. 'touch' and 'mobile' to emulate mobile devices. 'landscape' to emulate landscape mode.`),
|
|
83
50
|
},
|
|
84
51
|
handler: async (request, _response, context) => {
|
|
85
|
-
|
|
52
|
+
const page = request.page;
|
|
53
|
+
await context.emulate(request.params, page.pptrPage);
|
|
86
54
|
},
|
|
87
55
|
});
|
|
@@ -77,3 +77,20 @@ export const reloadExtension = defineTool({
|
|
|
77
77
|
response.appendResponseLine('Extension reloaded.');
|
|
78
78
|
},
|
|
79
79
|
});
|
|
80
|
+
export const triggerExtensionAction = defineTool({
|
|
81
|
+
name: 'trigger_extension_action',
|
|
82
|
+
description: 'Triggers an action in a Chrome extension.',
|
|
83
|
+
annotations: {
|
|
84
|
+
category: ToolCategory.EXTENSIONS,
|
|
85
|
+
readOnlyHint: false,
|
|
86
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
87
|
+
},
|
|
88
|
+
schema: {
|
|
89
|
+
id: zod.string().describe('ID of the extension.'),
|
|
90
|
+
},
|
|
91
|
+
handler: async (request, response, context) => {
|
|
92
|
+
const { id } = request.params;
|
|
93
|
+
await context.triggerExtensionAction(id);
|
|
94
|
+
response.appendResponseLine(`Extension action triggered. Id: ${id}`);
|
|
95
|
+
},
|
|
96
|
+
});
|
package/build/src/tools/input.js
CHANGED
|
@@ -7,7 +7,7 @@ import { logger } from '../logger.js';
|
|
|
7
7
|
import { zod } from '../third_party/index.js';
|
|
8
8
|
import { parseKey } from '../utils/keyboard.js';
|
|
9
9
|
import { ToolCategory } from './categories.js';
|
|
10
|
-
import {
|
|
10
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
11
11
|
const dblClickSchema = zod
|
|
12
12
|
.boolean()
|
|
13
13
|
.optional()
|
|
@@ -26,7 +26,7 @@ function handleActionError(error, uid) {
|
|
|
26
26
|
cause: error,
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
-
export const click =
|
|
29
|
+
export const click = definePageTool({
|
|
30
30
|
name: 'click',
|
|
31
31
|
description: `Clicks on the provided element`,
|
|
32
32
|
annotations: {
|
|
@@ -42,7 +42,7 @@ export const click = defineTool({
|
|
|
42
42
|
},
|
|
43
43
|
handler: async (request, response, context) => {
|
|
44
44
|
const uid = request.params.uid;
|
|
45
|
-
const handle = await
|
|
45
|
+
const handle = await request.page.getElementByUid(uid);
|
|
46
46
|
try {
|
|
47
47
|
await context.waitForEventsAfterAction(async () => {
|
|
48
48
|
await handle.asLocator().click({
|
|
@@ -64,7 +64,7 @@ export const click = defineTool({
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
});
|
|
67
|
-
export const clickAt =
|
|
67
|
+
export const clickAt = definePageTool({
|
|
68
68
|
name: 'click_at',
|
|
69
69
|
description: `Clicks at the provided coordinates`,
|
|
70
70
|
annotations: {
|
|
@@ -79,9 +79,9 @@ export const clickAt = defineTool({
|
|
|
79
79
|
includeSnapshot: includeSnapshotSchema,
|
|
80
80
|
},
|
|
81
81
|
handler: async (request, response, context) => {
|
|
82
|
-
const page =
|
|
82
|
+
const page = request.page;
|
|
83
83
|
await context.waitForEventsAfterAction(async () => {
|
|
84
|
-
await page.mouse.click(request.params.x, request.params.y, {
|
|
84
|
+
await page.pptrPage.mouse.click(request.params.x, request.params.y, {
|
|
85
85
|
clickCount: request.params.dblClick ? 2 : 1,
|
|
86
86
|
});
|
|
87
87
|
});
|
|
@@ -93,7 +93,7 @@ export const clickAt = defineTool({
|
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
|
-
export const hover =
|
|
96
|
+
export const hover = definePageTool({
|
|
97
97
|
name: 'hover',
|
|
98
98
|
description: `Hover over the provided element`,
|
|
99
99
|
annotations: {
|
|
@@ -108,7 +108,7 @@ export const hover = defineTool({
|
|
|
108
108
|
},
|
|
109
109
|
handler: async (request, response, context) => {
|
|
110
110
|
const uid = request.params.uid;
|
|
111
|
-
const handle = await
|
|
111
|
+
const handle = await request.page.getElementByUid(uid);
|
|
112
112
|
try {
|
|
113
113
|
await context.waitForEventsAfterAction(async () => {
|
|
114
114
|
await handle.asLocator().hover();
|
|
@@ -163,8 +163,8 @@ async function selectOption(handle, aXNode, value) {
|
|
|
163
163
|
function hasOptionChildren(aXNode) {
|
|
164
164
|
return aXNode.children.some(child => child.role === 'option');
|
|
165
165
|
}
|
|
166
|
-
async function fillFormElement(uid, value, context) {
|
|
167
|
-
const handle = await
|
|
166
|
+
async function fillFormElement(uid, value, context, page) {
|
|
167
|
+
const handle = await page.getElementByUid(uid);
|
|
168
168
|
try {
|
|
169
169
|
const aXNode = context.getAXNodeByUid(uid);
|
|
170
170
|
// We assume that combobox needs to be handled as select if it has
|
|
@@ -175,8 +175,7 @@ async function fillFormElement(uid, value, context) {
|
|
|
175
175
|
else {
|
|
176
176
|
// Increase timeout for longer input values.
|
|
177
177
|
const timeoutPerChar = 10; // ms
|
|
178
|
-
const fillTimeout =
|
|
179
|
-
value.length * timeoutPerChar;
|
|
178
|
+
const fillTimeout = page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar;
|
|
180
179
|
await handle.asLocator().setTimeout(fillTimeout).fill(value);
|
|
181
180
|
}
|
|
182
181
|
}
|
|
@@ -187,7 +186,7 @@ async function fillFormElement(uid, value, context) {
|
|
|
187
186
|
void handle.dispose();
|
|
188
187
|
}
|
|
189
188
|
}
|
|
190
|
-
export const fill =
|
|
189
|
+
export const fill = definePageTool({
|
|
191
190
|
name: 'fill',
|
|
192
191
|
description: `Type text into a input, text area or select an option from a <select> element.`,
|
|
193
192
|
annotations: {
|
|
@@ -202,8 +201,9 @@ export const fill = defineTool({
|
|
|
202
201
|
includeSnapshot: includeSnapshotSchema,
|
|
203
202
|
},
|
|
204
203
|
handler: async (request, response, context) => {
|
|
204
|
+
const page = request.page;
|
|
205
205
|
await context.waitForEventsAfterAction(async () => {
|
|
206
|
-
await fillFormElement(request.params.uid, request.params.value, context);
|
|
206
|
+
await fillFormElement(request.params.uid, request.params.value, context, page);
|
|
207
207
|
});
|
|
208
208
|
response.appendResponseLine(`Successfully filled out the element`);
|
|
209
209
|
if (request.params.includeSnapshot) {
|
|
@@ -211,7 +211,7 @@ export const fill = defineTool({
|
|
|
211
211
|
}
|
|
212
212
|
},
|
|
213
213
|
});
|
|
214
|
-
export const typeText =
|
|
214
|
+
export const typeText = definePageTool({
|
|
215
215
|
name: 'type_text',
|
|
216
216
|
description: `Type text using keyboard into a previously focused input`,
|
|
217
217
|
annotations: {
|
|
@@ -223,17 +223,17 @@ export const typeText = defineTool({
|
|
|
223
223
|
submitKey: submitKeySchema,
|
|
224
224
|
},
|
|
225
225
|
handler: async (request, response, context) => {
|
|
226
|
+
const page = request.page;
|
|
226
227
|
await context.waitForEventsAfterAction(async () => {
|
|
227
|
-
|
|
228
|
-
await page.keyboard.type(request.params.text);
|
|
228
|
+
await page.pptrPage.keyboard.type(request.params.text);
|
|
229
229
|
if (request.params.submitKey) {
|
|
230
|
-
await page.keyboard.press(request.params.submitKey);
|
|
230
|
+
await page.pptrPage.keyboard.press(request.params.submitKey);
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
response.appendResponseLine(`Typed text "${request.params.text}${request.params.submitKey ? ` + ${request.params.submitKey}` : ''}"`);
|
|
234
234
|
},
|
|
235
235
|
});
|
|
236
|
-
export const drag =
|
|
236
|
+
export const drag = definePageTool({
|
|
237
237
|
name: 'drag',
|
|
238
238
|
description: `Drag an element onto another element`,
|
|
239
239
|
annotations: {
|
|
@@ -246,8 +246,8 @@ export const drag = defineTool({
|
|
|
246
246
|
includeSnapshot: includeSnapshotSchema,
|
|
247
247
|
},
|
|
248
248
|
handler: async (request, response, context) => {
|
|
249
|
-
const fromHandle = await
|
|
250
|
-
const toHandle = await
|
|
249
|
+
const fromHandle = await request.page.getElementByUid(request.params.from_uid);
|
|
250
|
+
const toHandle = await request.page.getElementByUid(request.params.to_uid);
|
|
251
251
|
try {
|
|
252
252
|
await context.waitForEventsAfterAction(async () => {
|
|
253
253
|
await fromHandle.drag(toHandle);
|
|
@@ -265,7 +265,7 @@ export const drag = defineTool({
|
|
|
265
265
|
}
|
|
266
266
|
},
|
|
267
267
|
});
|
|
268
|
-
export const fillForm =
|
|
268
|
+
export const fillForm = definePageTool({
|
|
269
269
|
name: 'fill_form',
|
|
270
270
|
description: `Fill out multiple form elements at once`,
|
|
271
271
|
annotations: {
|
|
@@ -282,9 +282,10 @@ export const fillForm = defineTool({
|
|
|
282
282
|
includeSnapshot: includeSnapshotSchema,
|
|
283
283
|
},
|
|
284
284
|
handler: async (request, response, context) => {
|
|
285
|
+
const page = request.page;
|
|
285
286
|
for (const element of request.params.elements) {
|
|
286
287
|
await context.waitForEventsAfterAction(async () => {
|
|
287
|
-
await fillFormElement(element.uid, element.value, context);
|
|
288
|
+
await fillFormElement(element.uid, element.value, context, page);
|
|
288
289
|
});
|
|
289
290
|
}
|
|
290
291
|
response.appendResponseLine(`Successfully filled out the form`);
|
|
@@ -293,7 +294,7 @@ export const fillForm = defineTool({
|
|
|
293
294
|
}
|
|
294
295
|
},
|
|
295
296
|
});
|
|
296
|
-
export const uploadFile =
|
|
297
|
+
export const uploadFile = definePageTool({
|
|
297
298
|
name: 'upload_file',
|
|
298
299
|
description: 'Upload a file through a provided element.',
|
|
299
300
|
annotations: {
|
|
@@ -307,9 +308,9 @@ export const uploadFile = defineTool({
|
|
|
307
308
|
filePath: zod.string().describe('The local path of the file to upload'),
|
|
308
309
|
includeSnapshot: includeSnapshotSchema,
|
|
309
310
|
},
|
|
310
|
-
handler: async (request, response
|
|
311
|
+
handler: async (request, response) => {
|
|
311
312
|
const { uid, filePath } = request.params;
|
|
312
|
-
const handle = (await
|
|
313
|
+
const handle = (await request.page.getElementByUid(uid));
|
|
313
314
|
try {
|
|
314
315
|
try {
|
|
315
316
|
await handle.uploadFile(filePath);
|
|
@@ -319,9 +320,8 @@ export const uploadFile = defineTool({
|
|
|
319
320
|
// a type=file element. In this case, we want to default to
|
|
320
321
|
// Page.waitForFileChooser() and upload the file this way.
|
|
321
322
|
try {
|
|
322
|
-
const page = context.getSelectedPage();
|
|
323
323
|
const [fileChooser] = await Promise.all([
|
|
324
|
-
page.waitForFileChooser({ timeout: 3000 }),
|
|
324
|
+
request.page.pptrPage.waitForFileChooser({ timeout: 3000 }),
|
|
325
325
|
handle.asLocator().click(),
|
|
326
326
|
]);
|
|
327
327
|
await fileChooser.accept([filePath]);
|
|
@@ -340,7 +340,7 @@ export const uploadFile = defineTool({
|
|
|
340
340
|
}
|
|
341
341
|
},
|
|
342
342
|
});
|
|
343
|
-
export const pressKey =
|
|
343
|
+
export const pressKey = definePageTool({
|
|
344
344
|
name: 'press_key',
|
|
345
345
|
description: `Press a key or key combination. Use this when other input methods like fill() cannot be used (e.g., keyboard shortcuts, navigation keys, or special key combinations).`,
|
|
346
346
|
annotations: {
|
|
@@ -354,16 +354,16 @@ export const pressKey = defineTool({
|
|
|
354
354
|
includeSnapshot: includeSnapshotSchema,
|
|
355
355
|
},
|
|
356
356
|
handler: async (request, response, context) => {
|
|
357
|
-
const page =
|
|
357
|
+
const page = request.page;
|
|
358
358
|
const tokens = parseKey(request.params.key);
|
|
359
359
|
const [key, ...modifiers] = tokens;
|
|
360
360
|
await context.waitForEventsAfterAction(async () => {
|
|
361
361
|
for (const modifier of modifiers) {
|
|
362
|
-
await page.keyboard.down(modifier);
|
|
362
|
+
await page.pptrPage.keyboard.down(modifier);
|
|
363
363
|
}
|
|
364
|
-
await page.keyboard.press(key);
|
|
364
|
+
await page.pptrPage.keyboard.press(key);
|
|
365
365
|
for (const modifier of modifiers.toReversed()) {
|
|
366
|
-
await page.keyboard.up(modifier);
|
|
366
|
+
await page.pptrPage.keyboard.up(modifier);
|
|
367
367
|
}
|
|
368
368
|
});
|
|
369
369
|
response.appendResponseLine(`Successfully pressed key: ${request.params.key}`);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { snapshot, navigation, generateReport, zod, } from '../third_party/index.js';
|
|
8
|
+
import { ToolCategory } from './categories.js';
|
|
9
|
+
import { startTrace } from './performance.js';
|
|
10
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
11
|
+
export const lighthouseAudit = definePageTool({
|
|
12
|
+
name: 'lighthouse_audit',
|
|
13
|
+
description: `Get Lighthouse score and reports for accessibility, SEO and best practices. This excludes performance. For performance audits, run ${startTrace.name}`,
|
|
14
|
+
annotations: {
|
|
15
|
+
category: ToolCategory.DEBUGGING,
|
|
16
|
+
readOnlyHint: true,
|
|
17
|
+
},
|
|
18
|
+
schema: {
|
|
19
|
+
mode: zod
|
|
20
|
+
.enum(['navigation', 'snapshot'])
|
|
21
|
+
.default('navigation')
|
|
22
|
+
.describe('"navigation" reloads & audits. "snapshot" analyzes current state.'),
|
|
23
|
+
device: zod
|
|
24
|
+
.enum(['desktop', 'mobile'])
|
|
25
|
+
.default('desktop')
|
|
26
|
+
.describe('Device to emulate.'),
|
|
27
|
+
outputDirPath: zod
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Directory for reports. If omitted, uses temporary files.'),
|
|
31
|
+
},
|
|
32
|
+
handler: async (request, response, context) => {
|
|
33
|
+
const page = request.page;
|
|
34
|
+
const categories = ['accessibility', 'seo', 'best-practices'];
|
|
35
|
+
const formats = ['json', 'html'];
|
|
36
|
+
const { mode = 'navigation', device = 'desktop', outputDirPath, } = request.params;
|
|
37
|
+
const flags = {
|
|
38
|
+
onlyCategories: categories,
|
|
39
|
+
output: formats,
|
|
40
|
+
// Default 30 second timeout for page load.
|
|
41
|
+
maxWaitForLoad: 30_000,
|
|
42
|
+
};
|
|
43
|
+
if (device === 'desktop') {
|
|
44
|
+
flags.formFactor = 'desktop';
|
|
45
|
+
flags.screenEmulation = {
|
|
46
|
+
mobile: false,
|
|
47
|
+
width: 1350,
|
|
48
|
+
height: 940,
|
|
49
|
+
deviceScaleFactor: 1,
|
|
50
|
+
disabled: false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
flags.formFactor = 'mobile';
|
|
55
|
+
flags.screenEmulation = {
|
|
56
|
+
mobile: true,
|
|
57
|
+
width: 412,
|
|
58
|
+
height: 823,
|
|
59
|
+
deviceScaleFactor: 1.75,
|
|
60
|
+
disabled: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
if (mode === 'navigation') {
|
|
66
|
+
result = await navigation(page.pptrPage, page.pptrPage.url(), {
|
|
67
|
+
flags,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
result = await snapshot(page.pptrPage, {
|
|
72
|
+
flags,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!result) {
|
|
76
|
+
throw new Error('Lighthouse audit failed.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await context.restoreEmulation(page);
|
|
81
|
+
}
|
|
82
|
+
const lhr = result.lhr;
|
|
83
|
+
const reportPaths = [];
|
|
84
|
+
const encoder = new TextEncoder();
|
|
85
|
+
for (const format of formats) {
|
|
86
|
+
const report = generateReport(lhr, format);
|
|
87
|
+
const data = encoder.encode(report);
|
|
88
|
+
if (outputDirPath) {
|
|
89
|
+
const reportPath = path.join(outputDirPath, `report.${format}`);
|
|
90
|
+
const { filename } = await context.saveFile(data, reportPath);
|
|
91
|
+
reportPaths.push(filename);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const { filepath } = await context.saveTemporaryFile(data, `report.${format}`);
|
|
95
|
+
reportPaths.push(filepath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const categoryScores = Object.values(lhr.categories).map(c => ({
|
|
99
|
+
id: c.id,
|
|
100
|
+
title: c.title,
|
|
101
|
+
score: c.score,
|
|
102
|
+
}));
|
|
103
|
+
const failedAudits = Object.values(lhr.audits).filter(a => a.score !== null && a.score < 1).length;
|
|
104
|
+
const passedAudits = Object.values(lhr.audits).filter(a => a.score === 1).length;
|
|
105
|
+
const output = {
|
|
106
|
+
summary: {
|
|
107
|
+
mode,
|
|
108
|
+
device,
|
|
109
|
+
url: lhr.mainDocumentUrl,
|
|
110
|
+
scores: categoryScores,
|
|
111
|
+
audits: {
|
|
112
|
+
failed: failedAudits,
|
|
113
|
+
passed: passedAudits,
|
|
114
|
+
},
|
|
115
|
+
timing: {
|
|
116
|
+
total: lhr.timing.total,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
reports: reportPaths,
|
|
120
|
+
};
|
|
121
|
+
response.attachLighthouseResult(output);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import {
|
|
9
|
-
export const takeMemorySnapshot =
|
|
8
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
9
|
+
export const takeMemorySnapshot = definePageTool({
|
|
10
10
|
name: 'take_memory_snapshot',
|
|
11
11
|
description: `Capture a memory heapsnapshot of the currently selected page to memory leak debugging`,
|
|
12
12
|
annotations: {
|
|
@@ -18,9 +18,9 @@ export const takeMemorySnapshot = defineTool({
|
|
|
18
18
|
.string()
|
|
19
19
|
.describe('A path to a .heapsnapshot file to save the heapsnapshot to.'),
|
|
20
20
|
},
|
|
21
|
-
handler: async (request, response,
|
|
22
|
-
const page =
|
|
23
|
-
await page.captureHeapSnapshot({
|
|
21
|
+
handler: async (request, response, _context) => {
|
|
22
|
+
const page = request.page;
|
|
23
|
+
await page.pptrPage.captureHeapSnapshot({
|
|
24
24
|
path: request.params.filePath,
|
|
25
25
|
});
|
|
26
26
|
response.appendResponseLine(`Heap snapshot saved to ${request.params.filePath}`);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { zod } from '../third_party/index.js';
|
|
7
7
|
import { ToolCategory } from './categories.js';
|
|
8
|
-
import {
|
|
8
|
+
import { definePageTool } from './ToolDefinition.js';
|
|
9
9
|
const FILTERABLE_RESOURCE_TYPES = [
|
|
10
10
|
'document',
|
|
11
11
|
'stylesheet',
|
|
@@ -27,7 +27,7 @@ const FILTERABLE_RESOURCE_TYPES = [
|
|
|
27
27
|
'fedcm',
|
|
28
28
|
'other',
|
|
29
29
|
];
|
|
30
|
-
export const listNetworkRequests =
|
|
30
|
+
export const listNetworkRequests = definePageTool({
|
|
31
31
|
name: 'list_network_requests',
|
|
32
32
|
description: `List all requests for the currently selected page since the last navigation.`,
|
|
33
33
|
annotations: {
|
|
@@ -58,10 +58,10 @@ export const listNetworkRequests = defineTool({
|
|
|
58
58
|
.describe('Set to true to return the preserved requests over the last 3 navigations.'),
|
|
59
59
|
},
|
|
60
60
|
handler: async (request, response, context) => {
|
|
61
|
-
const data = await context.getDevToolsData();
|
|
61
|
+
const data = await context.getDevToolsData(request.page);
|
|
62
62
|
response.attachDevToolsData(data);
|
|
63
63
|
const reqid = data?.cdpRequestId
|
|
64
|
-
? context.resolveCdpRequestId(data.cdpRequestId)
|
|
64
|
+
? context.resolveCdpRequestId(request.page, data.cdpRequestId)
|
|
65
65
|
: undefined;
|
|
66
66
|
response.setIncludeNetworkRequests(true, {
|
|
67
67
|
pageSize: request.params.pageSize,
|
|
@@ -72,7 +72,7 @@ export const listNetworkRequests = defineTool({
|
|
|
72
72
|
});
|
|
73
73
|
},
|
|
74
74
|
});
|
|
75
|
-
export const getNetworkRequest =
|
|
75
|
+
export const getNetworkRequest = definePageTool({
|
|
76
76
|
name: 'get_network_request',
|
|
77
77
|
description: `Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel.`,
|
|
78
78
|
annotations: {
|
|
@@ -101,10 +101,10 @@ export const getNetworkRequest = defineTool({
|
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
else {
|
|
104
|
-
const data = await context.getDevToolsData();
|
|
104
|
+
const data = await context.getDevToolsData(request.page);
|
|
105
105
|
response.attachDevToolsData(data);
|
|
106
106
|
const reqid = data?.cdpRequestId
|
|
107
|
-
? context.resolveCdpRequestId(data.cdpRequestId)
|
|
107
|
+
? context.resolveCdpRequestId(request.page, data.cdpRequestId)
|
|
108
108
|
: undefined;
|
|
109
109
|
if (reqid) {
|
|
110
110
|
response.attachNetworkRequest(reqid, {
|