chrome-devtools-mcp 0.8.1 → 0.10.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 +69 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Console.js +1 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +8 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +10 -20
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SegmentedRange.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +4 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/StringOutputStream.js +1 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Worker.js +10 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +19 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/DispatchHttpRequestClient.js +54 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +6 -51
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHost.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostAPI.js +32 -29
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +15 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/host.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +17 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/ConnectionTransport.js +12 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +81 -213
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +3 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +45 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +3 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +14 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSRule.js +34 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSStyleDeclaration.js +4 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +8 -33
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +10 -47
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +3 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +17 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +371 -53
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +56 -13
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +133 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/{models/source_map_scopes → core/sdk}/ScopeTreeCache.js +9 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +50 -14
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +8 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +131 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TargetManager.js +0 -21
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +9 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk-meta.js +8 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1301 -174
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +9 -45
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +74 -19
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +50 -34
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +46 -45
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +2 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +10 -25
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +45 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/ProfileTreeModel.js +6 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +14 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +5 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +1 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +8 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +6 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +10 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +4 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +12 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Cache.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DOMSize.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/FontDisplay.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ForcedReflow.js +3 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LegacyJavaScript.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/SlowCSSSelector.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ThirdParties.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Viewport.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/mcp/mcp.js +14 -0
- package/build/src/DevToolsConnectionAdapter.js +33 -0
- package/build/src/DevtoolsUtils.js +44 -0
- package/build/src/McpContext.js +182 -33
- package/build/src/McpResponse.js +169 -57
- package/build/src/PageCollector.js +123 -27
- package/build/src/WaitForHelper.js +5 -0
- package/build/src/browser.js +24 -12
- package/build/src/cli.js +87 -6
- package/build/src/formatters/consoleFormatter.js +29 -62
- package/build/src/formatters/networkFormatter.js +5 -6
- package/build/src/formatters/snapshotFormatter.js +28 -11
- package/build/src/logger.js +1 -1
- package/build/src/main.js +24 -6
- package/build/src/polyfill.js +2 -2
- package/build/src/third_party/THIRD_PARTY_NOTICES +1413 -0
- package/build/src/third_party/index.js +82791 -0
- package/build/src/tools/ToolDefinition.js +2 -2
- package/build/src/tools/categories.js +17 -9
- package/build/src/tools/console.js +71 -6
- package/build/src/tools/emulation.js +40 -48
- package/build/src/tools/input.js +57 -27
- package/build/src/tools/network.js +43 -13
- package/build/src/tools/pages.js +75 -49
- package/build/src/tools/performance.js +13 -10
- package/build/src/tools/screenshot.js +10 -9
- package/build/src/tools/script.js +29 -15
- package/build/src/tools/snapshot.js +27 -23
- package/build/src/trace-processing/parse.js +6 -16
- package/build/src/utils/keyboard.js +291 -0
- package/build/src/utils/types.js +6 -0
- package/package.json +16 -12
|
@@ -3,21 +3,35 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
function createIdGenerator() {
|
|
7
|
+
let i = 1;
|
|
8
|
+
return () => {
|
|
9
|
+
if (i === Number.MAX_SAFE_INTEGER) {
|
|
10
|
+
i = 0;
|
|
11
|
+
}
|
|
12
|
+
return i++;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export const stableIdSymbol = Symbol('stableIdSymbol');
|
|
6
16
|
export class PageCollector {
|
|
7
17
|
#browser;
|
|
8
|
-
#
|
|
18
|
+
#listenersInitializer;
|
|
19
|
+
#listeners = new WeakMap();
|
|
20
|
+
#maxNavigationSaved = 3;
|
|
21
|
+
#includeAllPages;
|
|
9
22
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
23
|
+
* This maps a Page to a list of navigations with a sub-list
|
|
24
|
+
* of all collected resources.
|
|
25
|
+
* The newer navigations come first.
|
|
13
26
|
*/
|
|
14
27
|
storage = new WeakMap();
|
|
15
|
-
constructor(browser,
|
|
28
|
+
constructor(browser, listeners, includeAllPages) {
|
|
16
29
|
this.#browser = browser;
|
|
17
|
-
this.#
|
|
30
|
+
this.#listenersInitializer = listeners;
|
|
31
|
+
this.#includeAllPages = includeAllPages;
|
|
18
32
|
}
|
|
19
33
|
async init() {
|
|
20
|
-
const pages = await this.#browser.pages();
|
|
34
|
+
const pages = await this.#browser.pages(this.#includeAllPages);
|
|
21
35
|
for (const page of pages) {
|
|
22
36
|
this.#initializePage(page);
|
|
23
37
|
}
|
|
@@ -28,6 +42,13 @@ export class PageCollector {
|
|
|
28
42
|
}
|
|
29
43
|
this.#initializePage(page);
|
|
30
44
|
});
|
|
45
|
+
this.#browser.on('targetdestroyed', async (target) => {
|
|
46
|
+
const page = await target.page();
|
|
47
|
+
if (!page) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.#cleanupPageDestroyed(page);
|
|
51
|
+
});
|
|
31
52
|
}
|
|
32
53
|
addPage(page) {
|
|
33
54
|
this.#initializePage(page);
|
|
@@ -36,36 +57,105 @@ export class PageCollector {
|
|
|
36
57
|
if (this.storage.has(page)) {
|
|
37
58
|
return;
|
|
38
59
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
const idGenerator = createIdGenerator();
|
|
61
|
+
const storedLists = [[]];
|
|
62
|
+
this.storage.set(page, storedLists);
|
|
63
|
+
const listeners = this.#listenersInitializer(value => {
|
|
64
|
+
const withId = value;
|
|
65
|
+
withId[stableIdSymbol] = idGenerator();
|
|
66
|
+
const navigations = this.storage.get(page) ?? [[]];
|
|
67
|
+
navigations[0].push(withId);
|
|
68
|
+
});
|
|
69
|
+
listeners['framenavigated'] = (frame) => {
|
|
70
|
+
// Only split the storage on main frame navigation
|
|
43
71
|
if (frame !== page.mainFrame()) {
|
|
44
72
|
return;
|
|
45
73
|
}
|
|
46
|
-
this.
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
74
|
+
this.splitAfterNavigation(page);
|
|
75
|
+
};
|
|
76
|
+
for (const [name, listener] of Object.entries(listeners)) {
|
|
77
|
+
page.on(name, listener);
|
|
78
|
+
}
|
|
79
|
+
this.#listeners.set(page, listeners);
|
|
51
80
|
}
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
collection.length = 0;
|
|
81
|
+
splitAfterNavigation(page) {
|
|
82
|
+
const navigations = this.storage.get(page);
|
|
83
|
+
if (!navigations) {
|
|
84
|
+
return;
|
|
57
85
|
}
|
|
86
|
+
// Add the latest navigation first
|
|
87
|
+
navigations.unshift([]);
|
|
88
|
+
navigations.splice(this.#maxNavigationSaved);
|
|
58
89
|
}
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
#cleanupPageDestroyed(page) {
|
|
91
|
+
const listeners = this.#listeners.get(page);
|
|
92
|
+
if (listeners) {
|
|
93
|
+
for (const [name, listener] of Object.entries(listeners)) {
|
|
94
|
+
page.off(name, listener);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.storage.delete(page);
|
|
98
|
+
}
|
|
99
|
+
getData(page, includePreservedData) {
|
|
100
|
+
const navigations = this.storage.get(page);
|
|
101
|
+
if (!navigations) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
if (!includePreservedData) {
|
|
105
|
+
return navigations[0];
|
|
106
|
+
}
|
|
107
|
+
const data = [];
|
|
108
|
+
for (let index = this.#maxNavigationSaved; index >= 0; index--) {
|
|
109
|
+
if (navigations[index]) {
|
|
110
|
+
data.push(...navigations[index]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return data;
|
|
114
|
+
}
|
|
115
|
+
getIdForResource(resource) {
|
|
116
|
+
return resource[stableIdSymbol] ?? -1;
|
|
117
|
+
}
|
|
118
|
+
getById(page, stableId) {
|
|
119
|
+
const navigations = this.storage.get(page);
|
|
120
|
+
if (!navigations) {
|
|
121
|
+
throw new Error('No requests found for selected page');
|
|
122
|
+
}
|
|
123
|
+
const item = this.find(page, item => item[stableIdSymbol] === stableId);
|
|
124
|
+
if (item) {
|
|
125
|
+
return item;
|
|
126
|
+
}
|
|
127
|
+
throw new Error('Request not found for selected page');
|
|
128
|
+
}
|
|
129
|
+
find(page, filter) {
|
|
130
|
+
const navigations = this.storage.get(page);
|
|
131
|
+
if (!navigations) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
for (const navigation of navigations) {
|
|
135
|
+
const item = navigation.find(filter);
|
|
136
|
+
if (item) {
|
|
137
|
+
return item;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
61
141
|
}
|
|
62
142
|
}
|
|
63
143
|
export class NetworkCollector extends PageCollector {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
144
|
+
constructor(browser, listeners = collect => {
|
|
145
|
+
return {
|
|
146
|
+
request: req => {
|
|
147
|
+
collect(req);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}, includeAllPages) {
|
|
151
|
+
super(browser, listeners, includeAllPages);
|
|
152
|
+
}
|
|
153
|
+
splitAfterNavigation(page) {
|
|
154
|
+
const navigations = this.storage.get(page) ?? [];
|
|
155
|
+
if (!navigations) {
|
|
67
156
|
return;
|
|
68
157
|
}
|
|
158
|
+
const requests = navigations[0];
|
|
69
159
|
const lastRequestIdx = requests.findLastIndex(request => {
|
|
70
160
|
return request.frame() === page.mainFrame()
|
|
71
161
|
? request.isNavigationRequest()
|
|
@@ -74,6 +164,12 @@ export class NetworkCollector extends PageCollector {
|
|
|
74
164
|
// Keep all requests since the last navigation request including that
|
|
75
165
|
// navigation request itself.
|
|
76
166
|
// Keep the reference
|
|
77
|
-
|
|
167
|
+
if (lastRequestIdx !== -1) {
|
|
168
|
+
const fromCurrentNavigation = requests.splice(lastRequestIdx);
|
|
169
|
+
navigations.unshift(fromCurrentNavigation);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
navigations.unshift([]);
|
|
173
|
+
}
|
|
78
174
|
}
|
|
79
175
|
}
|
package/build/src/browser.js
CHANGED
|
@@ -6,17 +6,15 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import
|
|
9
|
+
import { logger } from './logger.js';
|
|
10
|
+
import { puppeteer } from './third_party/index.js';
|
|
10
11
|
let browser;
|
|
11
|
-
function makeTargetFilter(
|
|
12
|
+
function makeTargetFilter() {
|
|
12
13
|
const ignoredPrefixes = new Set([
|
|
13
14
|
'chrome://',
|
|
14
15
|
'chrome-extension://',
|
|
15
16
|
'chrome-untrusted://',
|
|
16
17
|
]);
|
|
17
|
-
if (!devtools) {
|
|
18
|
-
ignoredPrefixes.add('devtools://');
|
|
19
|
-
}
|
|
20
18
|
return function targetFilter(target) {
|
|
21
19
|
if (target.url() === 'chrome://newtab/') {
|
|
22
20
|
return true;
|
|
@@ -33,12 +31,26 @@ export async function ensureBrowserConnected(options) {
|
|
|
33
31
|
if (browser?.connected) {
|
|
34
32
|
return browser;
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
targetFilter: makeTargetFilter(
|
|
38
|
-
browserURL: options.browserURL,
|
|
34
|
+
const connectOptions = {
|
|
35
|
+
targetFilter: makeTargetFilter(),
|
|
39
36
|
defaultViewport: null,
|
|
40
|
-
handleDevToolsAsPage:
|
|
41
|
-
}
|
|
37
|
+
handleDevToolsAsPage: true,
|
|
38
|
+
};
|
|
39
|
+
if (options.wsEndpoint) {
|
|
40
|
+
connectOptions.browserWSEndpoint = options.wsEndpoint;
|
|
41
|
+
if (options.wsHeaders) {
|
|
42
|
+
connectOptions.headers = options.wsHeaders;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (options.browserURL) {
|
|
46
|
+
connectOptions.browserURL = options.browserURL;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
throw new Error('Either browserURL or wsEndpoint must be provided');
|
|
50
|
+
}
|
|
51
|
+
logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
|
|
52
|
+
browser = await puppeteer.connect(connectOptions);
|
|
53
|
+
logger('Connected Puppeteer');
|
|
42
54
|
return browser;
|
|
43
55
|
}
|
|
44
56
|
export async function launch(options) {
|
|
@@ -73,7 +85,7 @@ export async function launch(options) {
|
|
|
73
85
|
try {
|
|
74
86
|
const browser = await puppeteer.launch({
|
|
75
87
|
channel: puppeteerChannel,
|
|
76
|
-
targetFilter: makeTargetFilter(
|
|
88
|
+
targetFilter: makeTargetFilter(),
|
|
77
89
|
executablePath,
|
|
78
90
|
defaultViewport: null,
|
|
79
91
|
userDataDir,
|
|
@@ -81,7 +93,7 @@ export async function launch(options) {
|
|
|
81
93
|
headless,
|
|
82
94
|
args,
|
|
83
95
|
acceptInsecureCerts: options.acceptInsecureCerts,
|
|
84
|
-
handleDevToolsAsPage:
|
|
96
|
+
handleDevToolsAsPage: true,
|
|
85
97
|
});
|
|
86
98
|
if (options.logFile) {
|
|
87
99
|
// FIXME: we are probably subscribing too late to catch startup logs. We
|
package/build/src/cli.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import yargs from '
|
|
7
|
-
import { hideBin } from 'yargs/helpers';
|
|
6
|
+
import { yargs, hideBin } from './third_party/index.js';
|
|
8
7
|
export const cliOptions = {
|
|
9
8
|
browserUrl: {
|
|
10
9
|
type: 'string',
|
|
11
10
|
description: 'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.',
|
|
12
11
|
alias: 'u',
|
|
12
|
+
conflicts: 'wsEndpoint',
|
|
13
13
|
coerce: (url) => {
|
|
14
14
|
if (!url) {
|
|
15
15
|
return;
|
|
@@ -23,6 +23,50 @@ export const cliOptions = {
|
|
|
23
23
|
return url;
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
|
+
wsEndpoint: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
|
|
29
|
+
alias: 'w',
|
|
30
|
+
conflicts: 'browserUrl',
|
|
31
|
+
coerce: (url) => {
|
|
32
|
+
if (!url) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {
|
|
38
|
+
throw new Error(`Provided wsEndpoint ${url} must use ws:// or wss:// protocol.`);
|
|
39
|
+
}
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.message.includes('ws://')) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Provided wsEndpoint ${url} is not valid URL.`);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
wsHeaders: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Custom headers for WebSocket connection in JSON format (e.g., \'{"Authorization":"Bearer token"}\'). Only works with --wsEndpoint.',
|
|
53
|
+
implies: 'wsEndpoint',
|
|
54
|
+
coerce: (val) => {
|
|
55
|
+
if (!val) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(val);
|
|
60
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
61
|
+
throw new Error('Headers must be a JSON object');
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new Error(`Invalid JSON for wsHeaders: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
26
70
|
headless: {
|
|
27
71
|
type: 'boolean',
|
|
28
72
|
description: 'Whether to run in headless (no UI) mode.',
|
|
@@ -31,7 +75,7 @@ export const cliOptions = {
|
|
|
31
75
|
executablePath: {
|
|
32
76
|
type: 'string',
|
|
33
77
|
description: 'Path to custom Chrome executable.',
|
|
34
|
-
conflicts: 'browserUrl',
|
|
78
|
+
conflicts: ['browserUrl', 'wsEndpoint'],
|
|
35
79
|
alias: 'e',
|
|
36
80
|
},
|
|
37
81
|
isolated: {
|
|
@@ -43,7 +87,7 @@ export const cliOptions = {
|
|
|
43
87
|
type: 'string',
|
|
44
88
|
description: 'Specify a different Chrome channel that should be used. The default is the stable channel version.',
|
|
45
89
|
choices: ['stable', 'canary', 'beta', 'dev'],
|
|
46
|
-
conflicts: ['browserUrl', 'executablePath'],
|
|
90
|
+
conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
|
|
47
91
|
},
|
|
48
92
|
logFile: {
|
|
49
93
|
type: 'string',
|
|
@@ -79,10 +123,30 @@ export const cliOptions = {
|
|
|
79
123
|
describe: 'Whether to enable automation over DevTools targets',
|
|
80
124
|
hidden: true,
|
|
81
125
|
},
|
|
126
|
+
experimentalIncludeAllPages: {
|
|
127
|
+
type: 'boolean',
|
|
128
|
+
describe: 'Whether to include all kinds of pages such as webviews or background pages as pages.',
|
|
129
|
+
hidden: true,
|
|
130
|
+
},
|
|
82
131
|
chromeArg: {
|
|
83
132
|
type: 'array',
|
|
84
133
|
describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
|
|
85
134
|
},
|
|
135
|
+
categoryEmulation: {
|
|
136
|
+
type: 'boolean',
|
|
137
|
+
default: true,
|
|
138
|
+
describe: 'Set to false to exclude tools related to emulation.',
|
|
139
|
+
},
|
|
140
|
+
categoryPerformance: {
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
default: true,
|
|
143
|
+
describe: 'Set to false to exclude tools related to performance.',
|
|
144
|
+
},
|
|
145
|
+
categoryNetwork: {
|
|
146
|
+
type: 'boolean',
|
|
147
|
+
default: true,
|
|
148
|
+
describe: 'Set to false to exclude tools related to network.',
|
|
149
|
+
},
|
|
86
150
|
};
|
|
87
151
|
export function parseArguments(version, argv = process.argv) {
|
|
88
152
|
const yargsInstance = yargs(hideBin(argv))
|
|
@@ -91,7 +155,10 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
91
155
|
.check(args => {
|
|
92
156
|
// We can't set default in the options else
|
|
93
157
|
// Yargs will complain
|
|
94
|
-
if (!args.channel &&
|
|
158
|
+
if (!args.channel &&
|
|
159
|
+
!args.browserUrl &&
|
|
160
|
+
!args.wsEndpoint &&
|
|
161
|
+
!args.executablePath) {
|
|
95
162
|
args.channel = 'stable';
|
|
96
163
|
}
|
|
97
164
|
return true;
|
|
@@ -99,7 +166,15 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
99
166
|
.example([
|
|
100
167
|
[
|
|
101
168
|
'$0 --browserUrl http://127.0.0.1:9222',
|
|
102
|
-
'Connect to an existing browser instance',
|
|
169
|
+
'Connect to an existing browser instance via HTTP',
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
'$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123',
|
|
173
|
+
'Connect to an existing browser instance via WebSocket',
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
`$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123 --wsHeaders '{"Authorization":"Bearer token"}'`,
|
|
177
|
+
'Connect via WebSocket with custom headers',
|
|
103
178
|
],
|
|
104
179
|
['$0 --channel beta', 'Use Chrome Beta installed on this system'],
|
|
105
180
|
['$0 --channel canary', 'Use Chrome Canary installed on this system'],
|
|
@@ -115,6 +190,12 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
115
190
|
`$0 --chrome-arg='--no-sandbox' --chrome-arg='--disable-setuid-sandbox'`,
|
|
116
191
|
'Launch Chrome without sandboxes. Use with caution.',
|
|
117
192
|
],
|
|
193
|
+
['$0 --no-category-emulation', 'Disable tools in the emulation category'],
|
|
194
|
+
[
|
|
195
|
+
'$0 --no-category-performance',
|
|
196
|
+
'Disable tools in the performance category',
|
|
197
|
+
],
|
|
198
|
+
['$0 --no-category-network', 'Disable tools in the network category'],
|
|
118
199
|
]);
|
|
119
200
|
return yargsInstance
|
|
120
201
|
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
|
@@ -3,71 +3,38 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
warning: 'Warning',
|
|
10
|
-
error: 'Error',
|
|
11
|
-
exception: 'Exception',
|
|
12
|
-
assert: 'Assert',
|
|
13
|
-
};
|
|
14
|
-
export async function formatConsoleEvent(event) {
|
|
15
|
-
// Check if the event object has the .type() method, which is unique to ConsoleMessage
|
|
16
|
-
if ('type' in event) {
|
|
17
|
-
return await formatConsoleMessage(event);
|
|
18
|
-
}
|
|
19
|
-
return `Error: ${event.message}`;
|
|
6
|
+
// The short format for a console message, based on a previous format.
|
|
7
|
+
export function formatConsoleEventShort(msg) {
|
|
8
|
+
return `msgid=${msg.consoleMessageStableId} [${msg.type}] ${msg.message} (${msg.args?.length ?? 0} args)`;
|
|
20
9
|
}
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
if (msg.text() === 'JSHandle@error') {
|
|
27
|
-
const errorHandle = args[0];
|
|
28
|
-
message += await errorHandle
|
|
29
|
-
.evaluate(error => {
|
|
30
|
-
return error.toString();
|
|
31
|
-
})
|
|
32
|
-
.catch(() => {
|
|
33
|
-
return 'Error occurred';
|
|
34
|
-
});
|
|
35
|
-
void errorHandle.dispose().catch();
|
|
36
|
-
const formattedArgs = await formatArgs(args.slice(1));
|
|
37
|
-
if (formattedArgs) {
|
|
38
|
-
message += ` ${formattedArgs}`;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
message += msg.text();
|
|
43
|
-
const formattedArgs = await formatArgs(args);
|
|
44
|
-
if (formattedArgs) {
|
|
45
|
-
message += ` ${formattedArgs}`;
|
|
46
|
-
}
|
|
47
|
-
for (const frame of msg.stackTrace()) {
|
|
48
|
-
message += '\n' + formatStackFrame(frame);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return message;
|
|
10
|
+
function getArgs(msg) {
|
|
11
|
+
const args = [...(msg.args ?? [])];
|
|
12
|
+
// If there is no text, the first argument serves as text (see formatMessage).
|
|
13
|
+
if (!msg.message) {
|
|
14
|
+
args.shift();
|
|
52
15
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
16
|
+
return args;
|
|
17
|
+
}
|
|
18
|
+
// The verbose format for a console message, including all details.
|
|
19
|
+
export function formatConsoleEventVerbose(msg) {
|
|
20
|
+
const result = [
|
|
21
|
+
`ID: ${msg.consoleMessageStableId}`,
|
|
22
|
+
`Message: ${msg.type}> ${msg.message}`,
|
|
23
|
+
formatArgs(msg),
|
|
24
|
+
].filter(line => !!line);
|
|
25
|
+
return result.join('\n');
|
|
56
26
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Ignore errors
|
|
60
|
-
})));
|
|
61
|
-
return argValues
|
|
62
|
-
.map(value => {
|
|
63
|
-
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
64
|
-
})
|
|
65
|
-
.join(' ');
|
|
27
|
+
function formatArg(arg) {
|
|
28
|
+
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
|
|
66
29
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
30
|
+
function formatArgs(consoleData) {
|
|
31
|
+
const args = getArgs(consoleData);
|
|
32
|
+
if (!args.length) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
const result = ['### Arguments'];
|
|
36
|
+
for (const [key, arg] of args.entries()) {
|
|
37
|
+
result.push(`Arg #${key}: ${formatArg(arg)}`);
|
|
70
38
|
}
|
|
71
|
-
|
|
72
|
-
return `${filename}:${stackFrame.lineNumber}:${stackFrame.columnNumber}`;
|
|
39
|
+
return result.join('\n');
|
|
73
40
|
}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isUtf8 } from 'node:buffer';
|
|
7
7
|
const BODY_CONTEXT_SIZE_LIMIT = 10000;
|
|
8
|
-
export function getShortDescriptionForRequest(request) {
|
|
9
|
-
|
|
8
|
+
export function getShortDescriptionForRequest(request, id, selectedInDevToolsUI = false) {
|
|
9
|
+
// TODO truncate the URL
|
|
10
|
+
return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}${selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
|
|
10
11
|
}
|
|
11
12
|
export function getStatusFromRequest(request) {
|
|
12
13
|
const httpResponse = request.response();
|
|
@@ -47,8 +48,7 @@ export async function getFormattedResponseBody(httpResponse, sizeLimit = BODY_CO
|
|
|
47
48
|
return `<binary data>`;
|
|
48
49
|
}
|
|
49
50
|
catch {
|
|
50
|
-
|
|
51
|
-
return;
|
|
51
|
+
return `<not available anymore>`;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
export async function getFormattedRequestBody(httpRequest, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
|
|
@@ -64,8 +64,7 @@ export async function getFormattedRequestBody(httpRequest, sizeLimit = BODY_CONT
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
catch {
|
|
67
|
-
|
|
68
|
-
return;
|
|
67
|
+
return `<not available anymore>`;
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
70
|
return;
|
|
@@ -1,20 +1,37 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function formatSnapshotNode(root, snapshot, depth = 0) {
|
|
2
2
|
let result = '';
|
|
3
|
-
const attributes = getAttributes(
|
|
4
|
-
const line = ' '.repeat(depth * 2) +
|
|
3
|
+
const attributes = getAttributes(root);
|
|
4
|
+
const line = ' '.repeat(depth * 2) +
|
|
5
|
+
attributes.join(' ') +
|
|
6
|
+
(root.id === snapshot?.selectedElementUid
|
|
7
|
+
? ' [selected in the DevTools Elements panel]'
|
|
8
|
+
: '') +
|
|
9
|
+
'\n';
|
|
5
10
|
result += line;
|
|
6
|
-
for (const child of
|
|
7
|
-
result +=
|
|
11
|
+
for (const child of root.children) {
|
|
12
|
+
result += formatSnapshotNode(child, snapshot, depth + 1);
|
|
8
13
|
}
|
|
9
14
|
return result;
|
|
10
15
|
}
|
|
11
16
|
function getAttributes(serializedAXNodeRoot) {
|
|
12
|
-
const attributes = [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const attributes = [`uid=${serializedAXNodeRoot.id}`];
|
|
18
|
+
if (serializedAXNodeRoot.role) {
|
|
19
|
+
// To match representation in DevTools.
|
|
20
|
+
attributes.push(serializedAXNodeRoot.role === 'none'
|
|
21
|
+
? 'ignored'
|
|
22
|
+
: serializedAXNodeRoot.role);
|
|
23
|
+
}
|
|
24
|
+
if (serializedAXNodeRoot.name) {
|
|
25
|
+
attributes.push(`"${serializedAXNodeRoot.name}"`);
|
|
26
|
+
}
|
|
27
|
+
const excluded = new Set([
|
|
28
|
+
'id',
|
|
29
|
+
'role',
|
|
30
|
+
'name',
|
|
31
|
+
'elementHandle',
|
|
32
|
+
'children',
|
|
33
|
+
'backendNodeId',
|
|
34
|
+
]);
|
|
18
35
|
const booleanPropertyMap = {
|
|
19
36
|
disabled: 'disableable',
|
|
20
37
|
expanded: 'expandable',
|
package/build/src/logger.js
CHANGED