chrome-devtools-mcp 0.25.0 → 1.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/README.md +55 -6
- package/build/src/DevtoolsUtils.js +13 -0
- package/build/src/HeapSnapshotManager.js +26 -2
- package/build/src/McpContext.js +3 -0
- package/build/src/McpResponse.js +51 -21
- package/build/src/ToolHandler.js +217 -0
- package/build/src/WaitForHelper.js +18 -4
- package/build/src/bin/check-latest-version.js +25 -1
- package/build/src/bin/chrome-devtools-cli-options.js +38 -2
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +2 -8
- package/build/src/bin/chrome-devtools-mcp-main.js +4 -3
- package/build/src/bin/chrome-devtools.js +0 -2
- package/build/src/daemon/client.js +12 -6
- package/build/src/formatters/HeapSnapshotFormatter.js +27 -6
- package/build/src/index.js +11 -164
- package/build/src/telemetry/ClearcutLogger.js +34 -118
- package/build/src/telemetry/errors.js +18 -0
- package/build/src/telemetry/flagUtils.js +4 -3
- package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +3 -3
- package/build/src/telemetry/persistence.js +20 -2
- package/build/src/telemetry/transformation.js +134 -0
- package/build/src/telemetry/types.js +0 -8
- package/build/src/third_party/THIRD_PARTY_NOTICES +140 -857
- package/build/src/third_party/bundled-packages.json +3 -3
- package/build/src/third_party/devtools-formatter-worker.js +475 -146
- package/build/src/third_party/devtools-heap-snapshot-worker.js +39 -44
- package/build/src/third_party/index.js +4055 -30401
- package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +4236 -4219
- package/build/src/tools/ToolDefinition.js +1 -1
- package/build/src/tools/emulation.js +3 -2
- package/build/src/tools/input.js +46 -16
- package/build/src/tools/lighthouse.js +7 -7
- package/build/src/tools/memory.js +24 -0
- package/build/src/tools/script.js +32 -10
- package/build/src/version.js +1 -1
- package/package.json +10 -7
- package/build/src/telemetry/metricUtils.js +0 -15
|
@@ -33,7 +33,7 @@ export const emulate = definePageTool({
|
|
|
33
33
|
.string()
|
|
34
34
|
.optional()
|
|
35
35
|
.transform(geolocationTransform)
|
|
36
|
-
.describe('Geolocation (`<latitude
|
|
36
|
+
.describe('Geolocation (`<latitude>,<longitude>`) to emulate. Latitude between -90 and 90. Longitude between -180 and 180. Omit to clear the geolocation override.'),
|
|
37
37
|
userAgent: zod
|
|
38
38
|
.string()
|
|
39
39
|
.optional()
|
|
@@ -49,9 +49,10 @@ export const emulate = definePageTool({
|
|
|
49
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.`),
|
|
50
50
|
},
|
|
51
51
|
blockedByDialog: true,
|
|
52
|
-
handler: async (request,
|
|
52
|
+
handler: async (request, response, context) => {
|
|
53
53
|
const page = request.page;
|
|
54
54
|
await context.emulate(request.params, page.pptrPage);
|
|
55
|
+
response.appendResponseLine('Emulation configured successfully');
|
|
55
56
|
},
|
|
56
57
|
});
|
|
57
58
|
//# sourceMappingURL=emulation.js.map
|
package/build/src/tools/input.js
CHANGED
|
@@ -85,7 +85,7 @@ export const click = definePageTool({
|
|
|
85
85
|
const aXNode = request.page.getAXNodeByUid(uid);
|
|
86
86
|
const shouldSelectNativeOption = !request.params.dblClick && aXNode?.role === 'option';
|
|
87
87
|
try {
|
|
88
|
-
await request.page.waitForEventsAfterAction(async () => {
|
|
88
|
+
const result = await request.page.waitForEventsAfterAction(async () => {
|
|
89
89
|
if (shouldSelectNativeOption &&
|
|
90
90
|
(await selectNativeSelectOption(handle))) {
|
|
91
91
|
return;
|
|
@@ -97,6 +97,7 @@ export const click = definePageTool({
|
|
|
97
97
|
response.appendResponseLine(request.params.dblClick
|
|
98
98
|
? `Successfully double clicked on the element`
|
|
99
99
|
: `Successfully clicked on the element`);
|
|
100
|
+
response.attachWaitForResult(result);
|
|
100
101
|
if (request.params.includeSnapshot) {
|
|
101
102
|
response.includeSnapshot();
|
|
102
103
|
}
|
|
@@ -126,14 +127,15 @@ export const clickAt = definePageTool({
|
|
|
126
127
|
blockedByDialog: true,
|
|
127
128
|
handler: async (request, response) => {
|
|
128
129
|
const page = request.page;
|
|
129
|
-
await page.waitForEventsAfterAction(async () => {
|
|
130
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
130
131
|
await page.pptrPage.mouse.click(request.params.x, request.params.y, {
|
|
131
|
-
|
|
132
|
+
count: request.params.dblClick ? 2 : 1,
|
|
132
133
|
});
|
|
133
134
|
});
|
|
134
135
|
response.appendResponseLine(request.params.dblClick
|
|
135
136
|
? `Successfully double clicked at the coordinates`
|
|
136
137
|
: `Successfully clicked at the coordinates`);
|
|
138
|
+
response.attachWaitForResult(result);
|
|
137
139
|
if (request.params.includeSnapshot) {
|
|
138
140
|
response.includeSnapshot();
|
|
139
141
|
}
|
|
@@ -157,10 +159,11 @@ export const hover = definePageTool({
|
|
|
157
159
|
const uid = request.params.uid;
|
|
158
160
|
const handle = await request.page.getElementByUid(uid);
|
|
159
161
|
try {
|
|
160
|
-
await request.page.waitForEventsAfterAction(async () => {
|
|
162
|
+
const result = await request.page.waitForEventsAfterAction(async () => {
|
|
161
163
|
await handle.asLocator().hover();
|
|
162
164
|
});
|
|
163
165
|
response.appendResponseLine(`Successfully hovered over the element`);
|
|
166
|
+
response.attachWaitForResult(result);
|
|
164
167
|
if (request.params.includeSnapshot) {
|
|
165
168
|
response.includeSnapshot();
|
|
166
169
|
}
|
|
@@ -220,10 +223,27 @@ async function fillFormElement(uid, value, context, page) {
|
|
|
220
223
|
await selectOption(handle, aXNode, value);
|
|
221
224
|
}
|
|
222
225
|
else {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
const isToggle = await handle.evaluate(el => {
|
|
227
|
+
if (el instanceof HTMLInputElement) {
|
|
228
|
+
return el.type === 'checkbox' || el.type === 'radio';
|
|
229
|
+
}
|
|
230
|
+
const role = el.getAttribute('role');
|
|
231
|
+
return role === 'checkbox' || role === 'radio' || role === 'switch';
|
|
232
|
+
});
|
|
233
|
+
if (isToggle) {
|
|
234
|
+
if (['true', 'false'].includes(value)) {
|
|
235
|
+
await handle.asLocator().fill(value === 'true');
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
throw new Error(`Checkboxes, radio boxes and toggles require "true" or "false" value, but ${value} was used`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Increase timeout for longer input values.
|
|
243
|
+
const timeoutPerChar = 10; // ms
|
|
244
|
+
const fillTimeout = page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar;
|
|
245
|
+
await handle.asLocator().setTimeout(fillTimeout).fill(value);
|
|
246
|
+
}
|
|
227
247
|
}
|
|
228
248
|
}
|
|
229
249
|
catch (error) {
|
|
@@ -244,16 +264,19 @@ export const fill = definePageTool({
|
|
|
244
264
|
uid: zod
|
|
245
265
|
.string()
|
|
246
266
|
.describe('The uid of an element on the page from the page content snapshot'),
|
|
247
|
-
value: zod
|
|
267
|
+
value: zod
|
|
268
|
+
.string()
|
|
269
|
+
.describe('The value to fill in. "true" or "false" for checkboxes and toggles, "true" for radio buttons.'),
|
|
248
270
|
includeSnapshot: includeSnapshotSchema,
|
|
249
271
|
},
|
|
250
272
|
blockedByDialog: true,
|
|
251
273
|
handler: async (request, response, context) => {
|
|
252
274
|
const page = request.page;
|
|
253
|
-
await page.waitForEventsAfterAction(async () => {
|
|
275
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
254
276
|
await fillFormElement(request.params.uid, request.params.value, context, page);
|
|
255
277
|
});
|
|
256
278
|
response.appendResponseLine(`Successfully filled out the element`);
|
|
279
|
+
response.attachWaitForResult(result);
|
|
257
280
|
if (request.params.includeSnapshot) {
|
|
258
281
|
response.includeSnapshot();
|
|
259
282
|
}
|
|
@@ -273,13 +296,14 @@ export const typeText = definePageTool({
|
|
|
273
296
|
blockedByDialog: true,
|
|
274
297
|
handler: async (request, response) => {
|
|
275
298
|
const page = request.page;
|
|
276
|
-
await page.waitForEventsAfterAction(async () => {
|
|
299
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
277
300
|
await page.pptrPage.keyboard.type(request.params.text);
|
|
278
301
|
if (request.params.submitKey) {
|
|
279
302
|
await page.pptrPage.keyboard.press(request.params.submitKey);
|
|
280
303
|
}
|
|
281
304
|
});
|
|
282
305
|
response.appendResponseLine(`Typed text "${request.params.text}${request.params.submitKey ? ` + ${request.params.submitKey}` : ''}"`);
|
|
306
|
+
response.attachWaitForResult(result);
|
|
283
307
|
},
|
|
284
308
|
});
|
|
285
309
|
export const drag = definePageTool({
|
|
@@ -299,12 +323,13 @@ export const drag = definePageTool({
|
|
|
299
323
|
const fromHandle = await request.page.getElementByUid(request.params.from_uid);
|
|
300
324
|
const toHandle = await request.page.getElementByUid(request.params.to_uid);
|
|
301
325
|
try {
|
|
302
|
-
await request.page.waitForEventsAfterAction(async () => {
|
|
326
|
+
const result = await request.page.waitForEventsAfterAction(async () => {
|
|
303
327
|
await fromHandle.drag(toHandle);
|
|
304
328
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
305
329
|
await toHandle.drop(fromHandle);
|
|
306
330
|
});
|
|
307
331
|
response.appendResponseLine(`Successfully dragged an element`);
|
|
332
|
+
response.attachWaitForResult(result);
|
|
308
333
|
if (request.params.includeSnapshot) {
|
|
309
334
|
response.includeSnapshot();
|
|
310
335
|
}
|
|
@@ -317,7 +342,7 @@ export const drag = definePageTool({
|
|
|
317
342
|
});
|
|
318
343
|
export const fillForm = definePageTool({
|
|
319
344
|
name: 'fill_form',
|
|
320
|
-
description: `Fill out multiple form elements at once
|
|
345
|
+
description: `Fill out multiple form elements (inputs, selects, checkboxes, radios) at once. ALWAYS prefer this tool over multiple individual 'fill' or 'click' calls when interacting with forms. It is significantly faster, more reliable, and reduces turn count. Example: Fill username, password, and check "Remember Me" in one call.`,
|
|
321
346
|
annotations: {
|
|
322
347
|
category: ToolCategory.INPUT,
|
|
323
348
|
readOnlyHint: false,
|
|
@@ -328,7 +353,9 @@ export const fillForm = definePageTool({
|
|
|
328
353
|
// eslint-disable-next-line @local/enforce-zod-schema
|
|
329
354
|
zod.object({
|
|
330
355
|
uid: zod.string().describe('The uid of the element to fill out'),
|
|
331
|
-
value: zod
|
|
356
|
+
value: zod
|
|
357
|
+
.string()
|
|
358
|
+
.describe('Value for the element. "true" or "false" for checkboxes and toggles, "true" for radio buttons.'),
|
|
332
359
|
}))
|
|
333
360
|
.describe('Elements from snapshot to fill out.'),
|
|
334
361
|
includeSnapshot: includeSnapshotSchema,
|
|
@@ -336,12 +363,14 @@ export const fillForm = definePageTool({
|
|
|
336
363
|
blockedByDialog: true,
|
|
337
364
|
handler: async (request, response, context) => {
|
|
338
365
|
const page = request.page;
|
|
366
|
+
let lastResult = {};
|
|
339
367
|
for (const element of request.params.elements) {
|
|
340
|
-
await page.waitForEventsAfterAction(async () => {
|
|
368
|
+
lastResult = await page.waitForEventsAfterAction(async () => {
|
|
341
369
|
await fillFormElement(element.uid, element.value, context, page);
|
|
342
370
|
});
|
|
343
371
|
}
|
|
344
372
|
response.appendResponseLine(`Successfully filled out the form`);
|
|
373
|
+
response.attachWaitForResult(lastResult);
|
|
345
374
|
if (request.params.includeSnapshot) {
|
|
346
375
|
response.includeSnapshot();
|
|
347
376
|
}
|
|
@@ -413,7 +442,7 @@ export const pressKey = definePageTool({
|
|
|
413
442
|
const page = request.page;
|
|
414
443
|
const tokens = parseKey(request.params.key);
|
|
415
444
|
const [key, ...modifiers] = tokens;
|
|
416
|
-
await page.waitForEventsAfterAction(async () => {
|
|
445
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
417
446
|
for (const modifier of modifiers) {
|
|
418
447
|
await page.pptrPage.keyboard.down(modifier);
|
|
419
448
|
}
|
|
@@ -423,6 +452,7 @@ export const pressKey = definePageTool({
|
|
|
423
452
|
}
|
|
424
453
|
});
|
|
425
454
|
response.appendResponseLine(`Successfully pressed key: ${request.params.key}`);
|
|
455
|
+
response.attachWaitForResult(result);
|
|
426
456
|
if (request.params.includeSnapshot) {
|
|
427
457
|
response.includeSnapshot();
|
|
428
458
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { snapshot, navigation, generateReport, zod,
|
|
7
|
+
import { snapshot, navigation, generateReport, zod, } from '../third_party/index.js';
|
|
8
8
|
import { ToolCategory } from './categories.js';
|
|
9
9
|
import { startTrace } from './performance.js';
|
|
10
10
|
import { definePageTool } from './ToolDefinition.js';
|
|
@@ -67,17 +67,17 @@ export const lighthouseAudit = definePageTool({
|
|
|
67
67
|
disabled: false,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
const options = {
|
|
71
|
-
flags,
|
|
72
|
-
config: agenticBrowsingConfig,
|
|
73
|
-
};
|
|
74
70
|
let result;
|
|
75
71
|
try {
|
|
76
72
|
if (mode === 'navigation') {
|
|
77
|
-
result = await navigation(page.pptrPage, page.pptrPage.url(),
|
|
73
|
+
result = await navigation(page.pptrPage, page.pptrPage.url(), {
|
|
74
|
+
flags,
|
|
75
|
+
});
|
|
78
76
|
}
|
|
79
77
|
else {
|
|
80
|
-
result = await snapshot(page.pptrPage,
|
|
78
|
+
result = await snapshot(page.pptrPage, {
|
|
79
|
+
flags,
|
|
80
|
+
});
|
|
81
81
|
}
|
|
82
82
|
if (!result) {
|
|
83
83
|
throw new Error('Lighthouse audit failed.');
|
|
@@ -103,4 +103,28 @@ export const getNodesByClass = defineTool({
|
|
|
103
103
|
});
|
|
104
104
|
},
|
|
105
105
|
});
|
|
106
|
+
export const getNodeRetainers = defineTool({
|
|
107
|
+
name: 'get_node_retainers',
|
|
108
|
+
description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID.',
|
|
109
|
+
annotations: {
|
|
110
|
+
category: ToolCategory.MEMORY,
|
|
111
|
+
readOnlyHint: true,
|
|
112
|
+
conditions: ['experimentalMemory'],
|
|
113
|
+
},
|
|
114
|
+
blockedByDialog: false,
|
|
115
|
+
schema: {
|
|
116
|
+
filePath: zod.string().describe('A path to a .heapsnapshot file to read.'),
|
|
117
|
+
nodeId: zod.number().describe('The stable node ID to get retainers for.'),
|
|
118
|
+
pageIdx: zod.number().optional().describe('The page index for pagination.'),
|
|
119
|
+
pageSize: zod.number().optional().describe('The page size for pagination.'),
|
|
120
|
+
},
|
|
121
|
+
handler: async (request, response, context) => {
|
|
122
|
+
context.validatePath(request.params.filePath);
|
|
123
|
+
const retainers = await context.getHeapSnapshotRetainers(request.params.filePath, request.params.nodeId);
|
|
124
|
+
response.setHeapSnapshotNodes(retainers, {
|
|
125
|
+
pageIdx: request.params.pageIdx,
|
|
126
|
+
pageSize: request.params.pageSize,
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
});
|
|
106
130
|
//# sourceMappingURL=memory.js.map
|
|
@@ -32,6 +32,10 @@ Example with arguments: \`(el) => {
|
|
|
32
32
|
.describe('The uid of an element on the page from the page content snapshot'))
|
|
33
33
|
.optional()
|
|
34
34
|
.describe(`An optional list of arguments to pass to the function.`),
|
|
35
|
+
filePath: zod
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('The absolute or relative path to a file to save the script output to. If omitted, the output is returned inline.'),
|
|
35
39
|
dialogAction: zod
|
|
36
40
|
.string()
|
|
37
41
|
.optional()
|
|
@@ -48,7 +52,8 @@ Example with arguments: \`(el) => {
|
|
|
48
52
|
},
|
|
49
53
|
blockedByDialog: true,
|
|
50
54
|
handler: async (request, response, context) => {
|
|
51
|
-
const { serviceWorkerId, args: uidArgs, function: fnString, pageId, dialogAction, } = request.params;
|
|
55
|
+
const { serviceWorkerId, args: uidArgs, function: fnString, pageId, dialogAction, filePath, } = request.params;
|
|
56
|
+
context.validatePath(filePath);
|
|
52
57
|
if (cliArgs?.categoryExtensions && serviceWorkerId) {
|
|
53
58
|
if (uidArgs && uidArgs.length > 0) {
|
|
54
59
|
throw new Error('args (element uids) cannot be used when evaluating in a service worker.');
|
|
@@ -57,9 +62,15 @@ Example with arguments: \`(el) => {
|
|
|
57
62
|
throw new Error('specify either a pageId or a serviceWorkerId.');
|
|
58
63
|
}
|
|
59
64
|
const worker = await getWebWorker(context, serviceWorkerId);
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
const result = await context
|
|
66
|
+
.getSelectedMcpPage()
|
|
67
|
+
.waitForEventsAfterAction(async () => {
|
|
68
|
+
await performEvaluation(worker, fnString, [], response, {
|
|
69
|
+
filePath,
|
|
70
|
+
context,
|
|
71
|
+
});
|
|
62
72
|
}, { handleDialog: dialogAction ?? 'accept' });
|
|
73
|
+
response.attachWaitForResult(result);
|
|
63
74
|
return;
|
|
64
75
|
}
|
|
65
76
|
const mcpPage = cliArgs?.experimentalPageIdRouting
|
|
@@ -75,9 +86,13 @@ Example with arguments: \`(el) => {
|
|
|
75
86
|
args.push(handle);
|
|
76
87
|
}
|
|
77
88
|
const evaluatable = await getPageOrFrame(page, frames);
|
|
78
|
-
await mcpPage.waitForEventsAfterAction(async () => {
|
|
79
|
-
await performEvaluation(evaluatable, fnString, args, response
|
|
89
|
+
const result = await mcpPage.waitForEventsAfterAction(async () => {
|
|
90
|
+
await performEvaluation(evaluatable, fnString, args, response, {
|
|
91
|
+
filePath,
|
|
92
|
+
context,
|
|
93
|
+
});
|
|
80
94
|
}, { handleDialog: dialogAction ?? 'accept' });
|
|
95
|
+
response.attachWaitForResult(result);
|
|
81
96
|
}
|
|
82
97
|
finally {
|
|
83
98
|
void Promise.allSettled(args.map(arg => arg.dispose()));
|
|
@@ -85,17 +100,24 @@ Example with arguments: \`(el) => {
|
|
|
85
100
|
},
|
|
86
101
|
};
|
|
87
102
|
});
|
|
88
|
-
const performEvaluation = async (evaluatable, fnString, args, response) => {
|
|
103
|
+
const performEvaluation = async (evaluatable, fnString, args, response, options) => {
|
|
89
104
|
const fn = await evaluatable.evaluateHandle(`(${fnString})`);
|
|
90
105
|
try {
|
|
91
106
|
const result = await evaluatable.evaluate(async (fn, ...args) => {
|
|
92
107
|
// @ts-expect-error no types for function fn
|
|
93
108
|
return JSON.stringify(await fn(...args));
|
|
94
109
|
}, fn, ...args);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
if (options?.filePath) {
|
|
111
|
+
const data = new TextEncoder().encode(result ?? 'undefined');
|
|
112
|
+
const { filename } = await options.context.saveFile(data, options.filePath, '.json');
|
|
113
|
+
response.appendResponseLine(`Script ran on page. Output saved to ${filename}.`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
response.appendResponseLine('Script ran on page and returned:');
|
|
117
|
+
response.appendResponseLine('```json');
|
|
118
|
+
response.appendResponseLine(`${result}`);
|
|
119
|
+
response.appendResponseLine('```');
|
|
120
|
+
}
|
|
99
121
|
}
|
|
100
122
|
finally {
|
|
101
123
|
void fn.dispose();
|
package/build/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server for Chrome DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"mcpName": "io.github.ChromeDevTools/chrome-devtools-mcp",
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "^9.35.0",
|
|
51
|
-
"@google/genai": "^
|
|
51
|
+
"@google/genai": "^2.0.1",
|
|
52
52
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
53
53
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
54
54
|
"@rollup/plugin-json": "^6.1.0",
|
|
@@ -62,21 +62,21 @@
|
|
|
62
62
|
"@types/yargs": "^17.0.33",
|
|
63
63
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
64
64
|
"@typescript-eslint/parser": "^8.43.0",
|
|
65
|
-
"chrome-devtools-frontend": "1.0.
|
|
65
|
+
"chrome-devtools-frontend": "1.0.1631386",
|
|
66
66
|
"core-js": "3.49.0",
|
|
67
67
|
"debug": "4.4.3",
|
|
68
68
|
"eslint": "^9.35.0",
|
|
69
69
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
70
70
|
"eslint-plugin-import": "^2.32.0",
|
|
71
71
|
"globals": "^17.0.0",
|
|
72
|
-
"lighthouse": "13.
|
|
72
|
+
"lighthouse": "13.3.0",
|
|
73
73
|
"prettier": "^3.6.2",
|
|
74
|
-
"puppeteer": "
|
|
75
|
-
"rollup": "4.60.
|
|
74
|
+
"puppeteer": "25.0.4",
|
|
75
|
+
"rollup": "4.60.4",
|
|
76
76
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
77
77
|
"rollup-plugin-license": "^3.6.0",
|
|
78
78
|
"semver": "^7.7.4",
|
|
79
|
-
"sinon": "^
|
|
79
|
+
"sinon": "^22.0.0",
|
|
80
80
|
"typescript": "^6.0.2",
|
|
81
81
|
"typescript-eslint": "^8.43.0",
|
|
82
82
|
"urlpattern-polyfill": "^10.1.0",
|
|
@@ -84,5 +84,8 @@
|
|
|
84
84
|
},
|
|
85
85
|
"engines": {
|
|
86
86
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
|
87
|
+
},
|
|
88
|
+
"overrides": {
|
|
89
|
+
"puppeteer-core": "$puppeteer"
|
|
87
90
|
}
|
|
88
91
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
const LATENCY_BUCKETS = [50, 100, 250, 500, 1000, 2500, 5000, 10000];
|
|
7
|
-
export function bucketizeLatency(latencyMs) {
|
|
8
|
-
for (const bucket of LATENCY_BUCKETS) {
|
|
9
|
-
if (latencyMs <= bucket) {
|
|
10
|
-
return bucket;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return LATENCY_BUCKETS[LATENCY_BUCKETS.length - 1];
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=metricUtils.js.map
|