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
package/build/src/McpContext.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import fs from 'node:fs/promises';
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { extractUrlLikeFromDevToolsTitle, urlsEqual } from './DevtoolsUtils.js';
|
|
9
10
|
import { NetworkCollector, PageCollector } from './PageCollector.js';
|
|
11
|
+
import { Locator } from './third_party/index.js';
|
|
10
12
|
import { listPages } from './tools/pages.js';
|
|
11
13
|
import { takeSnapshot } from './tools/snapshot.js';
|
|
12
14
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
@@ -43,6 +45,7 @@ export class McpContext {
|
|
|
43
45
|
logger;
|
|
44
46
|
// The most recent page state.
|
|
45
47
|
#pages = [];
|
|
48
|
+
#pageToDevToolsPage = new Map();
|
|
46
49
|
#selectedPageIdx = 0;
|
|
47
50
|
// The most recent snapshot.
|
|
48
51
|
#textSnapshot = null;
|
|
@@ -54,22 +57,31 @@ export class McpContext {
|
|
|
54
57
|
#dialog;
|
|
55
58
|
#nextSnapshotId = 1;
|
|
56
59
|
#traceResults = [];
|
|
57
|
-
|
|
60
|
+
#locatorClass;
|
|
61
|
+
#options;
|
|
62
|
+
constructor(browser, logger, options, locatorClass) {
|
|
58
63
|
this.browser = browser;
|
|
59
64
|
this.logger = logger;
|
|
60
|
-
this.#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
this.#locatorClass = locatorClass;
|
|
66
|
+
this.#options = options;
|
|
67
|
+
this.#networkCollector = new NetworkCollector(this.browser, undefined, this.#options.experimentalIncludeAllPages);
|
|
68
|
+
this.#consoleCollector = new PageCollector(this.browser, collect => {
|
|
69
|
+
return {
|
|
70
|
+
console: event => {
|
|
71
|
+
collect(event);
|
|
72
|
+
},
|
|
73
|
+
pageerror: event => {
|
|
74
|
+
if (event instanceof Error) {
|
|
75
|
+
collect(event);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const error = new Error(`${event}`);
|
|
79
|
+
error.stack = undefined;
|
|
80
|
+
collect(error);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}, this.#options.experimentalIncludeAllPages);
|
|
73
85
|
}
|
|
74
86
|
async #init() {
|
|
75
87
|
await this.createPagesSnapshot();
|
|
@@ -77,18 +89,60 @@ export class McpContext {
|
|
|
77
89
|
await this.#networkCollector.init();
|
|
78
90
|
await this.#consoleCollector.init();
|
|
79
91
|
}
|
|
80
|
-
static async from(browser, logger
|
|
81
|
-
|
|
92
|
+
static async from(browser, logger, opts,
|
|
93
|
+
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
|
|
94
|
+
locatorClass = Locator) {
|
|
95
|
+
const context = new McpContext(browser, logger, opts, locatorClass);
|
|
82
96
|
await context.#init();
|
|
83
97
|
return context;
|
|
84
98
|
}
|
|
85
|
-
|
|
99
|
+
resolveCdpRequestId(cdpRequestId) {
|
|
100
|
+
const selectedPage = this.getSelectedPage();
|
|
101
|
+
if (!cdpRequestId) {
|
|
102
|
+
this.logger('no network request');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const request = this.#networkCollector.find(selectedPage, request => {
|
|
106
|
+
// @ts-expect-error id is internal.
|
|
107
|
+
return request.id === cdpRequestId;
|
|
108
|
+
});
|
|
109
|
+
if (!request) {
|
|
110
|
+
this.logger('no network request for ' + cdpRequestId);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
return this.#networkCollector.getIdForResource(request);
|
|
114
|
+
}
|
|
115
|
+
resolveCdpElementId(cdpBackendNodeId) {
|
|
116
|
+
if (!cdpBackendNodeId) {
|
|
117
|
+
this.logger('no cdpBackendNodeId');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// TODO: index by backendNodeId instead.
|
|
121
|
+
const queue = [this.#textSnapshot?.root];
|
|
122
|
+
while (queue.length) {
|
|
123
|
+
const current = queue.pop();
|
|
124
|
+
if (current.backendNodeId === cdpBackendNodeId) {
|
|
125
|
+
return current.id;
|
|
126
|
+
}
|
|
127
|
+
for (const child of current.children) {
|
|
128
|
+
queue.push(child);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
getNetworkRequests(includePreservedRequests) {
|
|
86
134
|
const page = this.getSelectedPage();
|
|
87
|
-
return this.#networkCollector.getData(page);
|
|
135
|
+
return this.#networkCollector.getData(page, includePreservedRequests);
|
|
88
136
|
}
|
|
89
|
-
getConsoleData() {
|
|
137
|
+
getConsoleData(includePreservedMessages) {
|
|
90
138
|
const page = this.getSelectedPage();
|
|
91
|
-
return this.#consoleCollector.getData(page);
|
|
139
|
+
return this.#consoleCollector.getData(page, includePreservedMessages);
|
|
140
|
+
}
|
|
141
|
+
getConsoleMessageStableId(message) {
|
|
142
|
+
return this.#consoleCollector.getIdForResource(message);
|
|
143
|
+
}
|
|
144
|
+
getConsoleMessageById(id) {
|
|
145
|
+
return this.#consoleCollector.getById(this.getSelectedPage(), id);
|
|
92
146
|
}
|
|
93
147
|
async newPage() {
|
|
94
148
|
const page = await this.browser.newPage();
|
|
@@ -106,17 +160,8 @@ export class McpContext {
|
|
|
106
160
|
this.setSelectedPageIdx(0);
|
|
107
161
|
await page.close({ runBeforeUnload: false });
|
|
108
162
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!requests.length) {
|
|
112
|
-
throw new Error('No requests found for selected page');
|
|
113
|
-
}
|
|
114
|
-
for (const request of requests) {
|
|
115
|
-
if (request.url() === url) {
|
|
116
|
-
return request;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
throw new Error('Request not found for selected page');
|
|
163
|
+
getNetworkRequestById(reqid) {
|
|
164
|
+
return this.#networkCollector.getById(this.getSelectedPage(), reqid);
|
|
120
165
|
}
|
|
121
166
|
setNetworkConditions(conditions) {
|
|
122
167
|
const page = this.getSelectedPage();
|
|
@@ -226,19 +271,88 @@ export class McpContext {
|
|
|
226
271
|
* Creates a snapshot of the pages.
|
|
227
272
|
*/
|
|
228
273
|
async createPagesSnapshot() {
|
|
229
|
-
|
|
274
|
+
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
275
|
+
this.#pages = allPages.filter(page => {
|
|
276
|
+
// If we allow debugging DevTools windows, return all pages.
|
|
277
|
+
// If we are in regular mode, the user should only see non-DevTools page.
|
|
278
|
+
return (this.#options.experimentalDevToolsDebugging ||
|
|
279
|
+
!page.url().startsWith('devtools://'));
|
|
280
|
+
});
|
|
281
|
+
await this.detectOpenDevToolsWindows();
|
|
230
282
|
return this.#pages;
|
|
231
283
|
}
|
|
284
|
+
async detectOpenDevToolsWindows() {
|
|
285
|
+
this.logger('Detecting open DevTools windows');
|
|
286
|
+
const pages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
287
|
+
this.#pageToDevToolsPage = new Map();
|
|
288
|
+
for (const devToolsPage of pages) {
|
|
289
|
+
if (devToolsPage.url().startsWith('devtools://')) {
|
|
290
|
+
try {
|
|
291
|
+
this.logger('Calling getTargetInfo for ' + devToolsPage.url());
|
|
292
|
+
const data = await devToolsPage
|
|
293
|
+
// @ts-expect-error no types for _client().
|
|
294
|
+
._client()
|
|
295
|
+
.send('Target.getTargetInfo');
|
|
296
|
+
const devtoolsPageTitle = data.targetInfo.title;
|
|
297
|
+
const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
|
|
298
|
+
if (!urlLike) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// TODO: lookup without a loop.
|
|
302
|
+
for (const page of this.#pages) {
|
|
303
|
+
if (urlsEqual(page.url(), urlLike)) {
|
|
304
|
+
this.#pageToDevToolsPage.set(page, devToolsPage);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.logger('Issue occurred while trying to find DevTools', error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
232
314
|
getPages() {
|
|
233
315
|
return this.#pages;
|
|
234
316
|
}
|
|
317
|
+
getDevToolsPage(page) {
|
|
318
|
+
return this.#pageToDevToolsPage.get(page);
|
|
319
|
+
}
|
|
320
|
+
async getDevToolsData() {
|
|
321
|
+
try {
|
|
322
|
+
this.logger('Getting DevTools UI data');
|
|
323
|
+
const selectedPage = this.getSelectedPage();
|
|
324
|
+
const devtoolsPage = this.getDevToolsPage(selectedPage);
|
|
325
|
+
if (!devtoolsPage) {
|
|
326
|
+
this.logger('No DevTools page detected');
|
|
327
|
+
return {};
|
|
328
|
+
}
|
|
329
|
+
const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
|
|
330
|
+
// @ts-expect-error no types
|
|
331
|
+
const UI = await import('/bundled/ui/legacy/legacy.js');
|
|
332
|
+
// @ts-expect-error no types
|
|
333
|
+
const SDK = await import('/bundled/core/sdk/sdk.js');
|
|
334
|
+
const request = UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest);
|
|
335
|
+
const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
|
|
336
|
+
return {
|
|
337
|
+
cdpRequestId: request?.requestId(),
|
|
338
|
+
cdpBackendNodeId: node?.backendNodeId(),
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
return { cdpBackendNodeId, cdpRequestId };
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
this.logger('error getting devtools data', err);
|
|
345
|
+
}
|
|
346
|
+
return {};
|
|
347
|
+
}
|
|
235
348
|
/**
|
|
236
349
|
* Creates a text snapshot of a page.
|
|
237
350
|
*/
|
|
238
|
-
async createTextSnapshot() {
|
|
351
|
+
async createTextSnapshot(verbose = false, devtoolsData = undefined) {
|
|
239
352
|
const page = this.getSelectedPage();
|
|
240
353
|
const rootNode = await page.accessibility.snapshot({
|
|
241
354
|
includeIframes: true,
|
|
355
|
+
interestingOnly: !verbose,
|
|
242
356
|
});
|
|
243
357
|
if (!rootNode) {
|
|
244
358
|
return;
|
|
@@ -273,6 +387,10 @@ export class McpContext {
|
|
|
273
387
|
snapshotId: String(snapshotId),
|
|
274
388
|
idToNode,
|
|
275
389
|
};
|
|
390
|
+
const data = devtoolsData ?? (await this.getDevToolsData());
|
|
391
|
+
if (data?.cdpBackendNodeId) {
|
|
392
|
+
this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
|
|
393
|
+
}
|
|
276
394
|
}
|
|
277
395
|
getTextSnapshot() {
|
|
278
396
|
return this.#textSnapshot;
|
|
@@ -316,4 +434,35 @@ export class McpContext {
|
|
|
316
434
|
const waitForHelper = this.getWaitForHelper(page, cpuMultiplier, networkMultiplier);
|
|
317
435
|
return waitForHelper.waitForEventsAfterAction(action);
|
|
318
436
|
}
|
|
437
|
+
getNetworkRequestStableId(request) {
|
|
438
|
+
return this.#networkCollector.getIdForResource(request);
|
|
439
|
+
}
|
|
440
|
+
waitForTextOnPage({ text, timeout, }) {
|
|
441
|
+
const page = this.getSelectedPage();
|
|
442
|
+
const frames = page.frames();
|
|
443
|
+
const locator = this.#locatorClass.race(frames.flatMap(frame => [
|
|
444
|
+
frame.locator(`aria/${text}`),
|
|
445
|
+
frame.locator(`text/${text}`),
|
|
446
|
+
]));
|
|
447
|
+
if (timeout) {
|
|
448
|
+
locator.setTimeout(timeout);
|
|
449
|
+
}
|
|
450
|
+
return locator.wait();
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* We need to ignore favicon request as they make our test flaky
|
|
454
|
+
*/
|
|
455
|
+
async setUpNetworkCollectorForTesting() {
|
|
456
|
+
this.#networkCollector = new NetworkCollector(this.browser, collect => {
|
|
457
|
+
return {
|
|
458
|
+
request: req => {
|
|
459
|
+
if (req.url().includes('favicon.ico')) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
collect(req);
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
await this.#networkCollector.init();
|
|
467
|
+
}
|
|
319
468
|
}
|
package/build/src/McpResponse.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
|
|
2
2
|
import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
3
|
-
import {
|
|
3
|
+
import { formatSnapshotNode } from './formatters/snapshotFormatter.js';
|
|
4
4
|
import { handleDialog } from './tools/pages.js';
|
|
5
5
|
import { paginate } from './utils/pagination.js';
|
|
6
6
|
export class McpResponse {
|
|
7
7
|
#includePages = false;
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
#snapshotParams;
|
|
9
|
+
#attachedNetworkRequestId;
|
|
10
|
+
#attachedConsoleMessageId;
|
|
11
11
|
#textResponseLines = [];
|
|
12
|
-
#formattedConsoleData;
|
|
13
12
|
#images = [];
|
|
14
13
|
#networkRequestsOptions;
|
|
14
|
+
#consoleDataOptions;
|
|
15
|
+
#devToolsData;
|
|
16
|
+
attachDevToolsData(data) {
|
|
17
|
+
this.#devToolsData = data;
|
|
18
|
+
}
|
|
15
19
|
setIncludePages(value) {
|
|
16
20
|
this.#includePages = value;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
this.#
|
|
22
|
+
includeSnapshot(params) {
|
|
23
|
+
this.#snapshotParams = params ?? {
|
|
24
|
+
verbose: false,
|
|
25
|
+
};
|
|
20
26
|
}
|
|
21
27
|
setIncludeNetworkRequests(value, options) {
|
|
22
28
|
if (!value) {
|
|
@@ -32,16 +38,33 @@ export class McpResponse {
|
|
|
32
38
|
}
|
|
33
39
|
: undefined,
|
|
34
40
|
resourceTypes: options?.resourceTypes,
|
|
41
|
+
includePreservedRequests: options?.includePreservedRequests,
|
|
42
|
+
networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI,
|
|
35
43
|
};
|
|
36
44
|
}
|
|
37
|
-
setIncludeConsoleData(value) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
setIncludeConsoleData(value, options) {
|
|
46
|
+
if (!value) {
|
|
47
|
+
this.#consoleDataOptions = undefined;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.#consoleDataOptions = {
|
|
51
|
+
include: value,
|
|
52
|
+
pagination: options?.pageSize || options?.pageIdx
|
|
53
|
+
? {
|
|
54
|
+
pageSize: options.pageSize,
|
|
55
|
+
pageIdx: options.pageIdx,
|
|
56
|
+
}
|
|
57
|
+
: undefined,
|
|
58
|
+
types: options?.types,
|
|
59
|
+
includePreservedMessages: options?.includePreservedMessages,
|
|
43
60
|
};
|
|
44
61
|
}
|
|
62
|
+
attachNetworkRequest(reqid) {
|
|
63
|
+
this.#attachedNetworkRequestId = reqid;
|
|
64
|
+
}
|
|
65
|
+
attachConsoleMessage(msgid) {
|
|
66
|
+
this.#attachedConsoleMessageId = msgid;
|
|
67
|
+
}
|
|
45
68
|
get includePages() {
|
|
46
69
|
return this.#includePages;
|
|
47
70
|
}
|
|
@@ -49,14 +72,20 @@ export class McpResponse {
|
|
|
49
72
|
return this.#networkRequestsOptions?.include ?? false;
|
|
50
73
|
}
|
|
51
74
|
get includeConsoleData() {
|
|
52
|
-
return this.#
|
|
75
|
+
return this.#consoleDataOptions?.include ?? false;
|
|
53
76
|
}
|
|
54
|
-
get
|
|
55
|
-
return this.#
|
|
77
|
+
get attachedNetworkRequestId() {
|
|
78
|
+
return this.#attachedNetworkRequestId;
|
|
56
79
|
}
|
|
57
80
|
get networkRequestsPageIdx() {
|
|
58
81
|
return this.#networkRequestsOptions?.pagination?.pageIdx;
|
|
59
82
|
}
|
|
83
|
+
get consoleMessagesPageIdx() {
|
|
84
|
+
return this.#consoleDataOptions?.pagination?.pageIdx;
|
|
85
|
+
}
|
|
86
|
+
get consoleMessagesTypes() {
|
|
87
|
+
return this.#consoleDataOptions?.types;
|
|
88
|
+
}
|
|
60
89
|
appendResponseLine(value) {
|
|
61
90
|
this.#textResponseLines.push(value);
|
|
62
91
|
}
|
|
@@ -69,37 +98,111 @@ export class McpResponse {
|
|
|
69
98
|
get images() {
|
|
70
99
|
return this.#images;
|
|
71
100
|
}
|
|
72
|
-
get
|
|
73
|
-
return this.#
|
|
101
|
+
get snapshotParams() {
|
|
102
|
+
return this.#snapshotParams;
|
|
74
103
|
}
|
|
75
104
|
async handle(toolName, context) {
|
|
76
105
|
if (this.#includePages) {
|
|
77
106
|
await context.createPagesSnapshot();
|
|
78
107
|
}
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
let formattedSnapshot;
|
|
109
|
+
if (this.#snapshotParams) {
|
|
110
|
+
await context.createTextSnapshot(this.#snapshotParams.verbose, this.#devToolsData);
|
|
111
|
+
const snapshot = context.getTextSnapshot();
|
|
112
|
+
if (snapshot) {
|
|
113
|
+
if (this.#snapshotParams.filePath) {
|
|
114
|
+
await context.saveFile(new TextEncoder().encode(formatSnapshotNode(snapshot.root, snapshot)), this.#snapshotParams.filePath);
|
|
115
|
+
formattedSnapshot = `Saved snapshot to ${this.#snapshotParams.filePath}.`;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
formattedSnapshot = formatSnapshotNode(snapshot.root, snapshot);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
81
121
|
}
|
|
82
|
-
|
|
83
|
-
if (this.#
|
|
84
|
-
const request = context.
|
|
85
|
-
|
|
86
|
-
await getFormattedRequestBody(request);
|
|
122
|
+
const bodies = {};
|
|
123
|
+
if (this.#attachedNetworkRequestId) {
|
|
124
|
+
const request = context.getNetworkRequestById(this.#attachedNetworkRequestId);
|
|
125
|
+
bodies.requestBody = await getFormattedRequestBody(request);
|
|
87
126
|
const response = request.response();
|
|
88
127
|
if (response) {
|
|
89
|
-
|
|
90
|
-
|
|
128
|
+
bodies.responseBody = await getFormattedResponseBody(response);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
let consoleData;
|
|
132
|
+
if (this.#attachedConsoleMessageId) {
|
|
133
|
+
const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
|
|
134
|
+
const consoleMessageStableId = this.#attachedConsoleMessageId;
|
|
135
|
+
if ('args' in message) {
|
|
136
|
+
const consoleMessage = message;
|
|
137
|
+
consoleData = {
|
|
138
|
+
consoleMessageStableId,
|
|
139
|
+
type: consoleMessage.type(),
|
|
140
|
+
message: consoleMessage.text(),
|
|
141
|
+
args: await Promise.all(consoleMessage.args().map(async (arg) => {
|
|
142
|
+
const stringArg = await arg.jsonValue().catch(() => {
|
|
143
|
+
// Ignore errors.
|
|
144
|
+
});
|
|
145
|
+
return typeof stringArg === 'object'
|
|
146
|
+
? JSON.stringify(stringArg)
|
|
147
|
+
: String(stringArg);
|
|
148
|
+
})),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
consoleData = {
|
|
153
|
+
consoleMessageStableId,
|
|
154
|
+
type: 'error',
|
|
155
|
+
message: message.message,
|
|
156
|
+
args: [],
|
|
157
|
+
};
|
|
91
158
|
}
|
|
92
159
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
160
|
+
let consoleListData;
|
|
161
|
+
if (this.#consoleDataOptions?.include) {
|
|
162
|
+
let messages = context.getConsoleData(this.#consoleDataOptions.includePreservedMessages);
|
|
163
|
+
if (this.#consoleDataOptions.types?.length) {
|
|
164
|
+
const normalizedTypes = new Set(this.#consoleDataOptions.types);
|
|
165
|
+
messages = messages.filter(message => {
|
|
166
|
+
if ('type' in message) {
|
|
167
|
+
return normalizedTypes.has(message.type());
|
|
168
|
+
}
|
|
169
|
+
return normalizedTypes.has('error');
|
|
170
|
+
});
|
|
98
171
|
}
|
|
172
|
+
consoleListData = await Promise.all(messages.map(async (item) => {
|
|
173
|
+
const consoleMessageStableId = context.getConsoleMessageStableId(item);
|
|
174
|
+
if ('args' in item) {
|
|
175
|
+
const consoleMessage = item;
|
|
176
|
+
return {
|
|
177
|
+
consoleMessageStableId,
|
|
178
|
+
type: consoleMessage.type(),
|
|
179
|
+
message: consoleMessage.text(),
|
|
180
|
+
args: await Promise.all(consoleMessage.args().map(async (arg) => {
|
|
181
|
+
const stringArg = await arg.jsonValue().catch(() => {
|
|
182
|
+
// Ignore errors.
|
|
183
|
+
});
|
|
184
|
+
return typeof stringArg === 'object'
|
|
185
|
+
? JSON.stringify(stringArg)
|
|
186
|
+
: String(stringArg);
|
|
187
|
+
})),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
consoleMessageStableId,
|
|
192
|
+
type: 'error',
|
|
193
|
+
message: item.message,
|
|
194
|
+
args: [],
|
|
195
|
+
};
|
|
196
|
+
}));
|
|
99
197
|
}
|
|
100
|
-
return this.format(toolName, context
|
|
198
|
+
return this.format(toolName, context, {
|
|
199
|
+
bodies,
|
|
200
|
+
consoleData,
|
|
201
|
+
consoleListData,
|
|
202
|
+
formattedSnapshot,
|
|
203
|
+
});
|
|
101
204
|
}
|
|
102
|
-
format(toolName, context) {
|
|
205
|
+
format(toolName, context, data) {
|
|
103
206
|
const response = [`# ${toolName} response`];
|
|
104
207
|
for (const line of this.#textResponseLines) {
|
|
105
208
|
response.push(line);
|
|
@@ -133,17 +236,14 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
133
236
|
}
|
|
134
237
|
response.push(...parts);
|
|
135
238
|
}
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const formattedSnapshot = formatA11ySnapshot(snapshot.root);
|
|
140
|
-
response.push('## Page content');
|
|
141
|
-
response.push(formattedSnapshot);
|
|
142
|
-
}
|
|
239
|
+
if (data.formattedSnapshot) {
|
|
240
|
+
response.push('## Page content');
|
|
241
|
+
response.push(data.formattedSnapshot);
|
|
143
242
|
}
|
|
144
|
-
response.push(...this.#
|
|
243
|
+
response.push(...this.#formatNetworkRequestData(context, data.bodies));
|
|
244
|
+
response.push(...this.#formatConsoleData(data.consoleData));
|
|
145
245
|
if (this.#networkRequestsOptions?.include) {
|
|
146
|
-
let requests = context.getNetworkRequests();
|
|
246
|
+
let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
|
|
147
247
|
// Apply resource type filtering if specified
|
|
148
248
|
if (this.#networkRequestsOptions.resourceTypes?.length) {
|
|
149
249
|
const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
|
|
@@ -157,17 +257,21 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
157
257
|
const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
|
|
158
258
|
response.push(...data.info);
|
|
159
259
|
for (const request of data.items) {
|
|
160
|
-
response.push(getShortDescriptionForRequest(request))
|
|
260
|
+
response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request), context.getNetworkRequestStableId(request) ===
|
|
261
|
+
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI));
|
|
161
262
|
}
|
|
162
263
|
}
|
|
163
264
|
else {
|
|
164
265
|
response.push('No requests found.');
|
|
165
266
|
}
|
|
166
267
|
}
|
|
167
|
-
if (this.#
|
|
268
|
+
if (this.#consoleDataOptions?.include) {
|
|
269
|
+
const messages = data.consoleListData ?? [];
|
|
168
270
|
response.push('## Console messages');
|
|
169
|
-
if (
|
|
170
|
-
|
|
271
|
+
if (messages.length) {
|
|
272
|
+
const data = this.#dataWithPagination(messages, this.#consoleDataOptions.pagination);
|
|
273
|
+
response.push(...data.info);
|
|
274
|
+
response.push(...data.items.map(message => formatConsoleEventShort(message)));
|
|
171
275
|
}
|
|
172
276
|
else {
|
|
173
277
|
response.push('<no console messages found>');
|
|
@@ -206,22 +310,30 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
206
310
|
items: paginationResult.items,
|
|
207
311
|
};
|
|
208
312
|
}
|
|
209
|
-
#
|
|
313
|
+
#formatConsoleData(data) {
|
|
314
|
+
const response = [];
|
|
315
|
+
if (!data) {
|
|
316
|
+
return response;
|
|
317
|
+
}
|
|
318
|
+
response.push(formatConsoleEventVerbose(data));
|
|
319
|
+
return response;
|
|
320
|
+
}
|
|
321
|
+
#formatNetworkRequestData(context, data) {
|
|
210
322
|
const response = [];
|
|
211
|
-
const
|
|
212
|
-
if (!
|
|
323
|
+
const id = this.#attachedNetworkRequestId;
|
|
324
|
+
if (!id) {
|
|
213
325
|
return response;
|
|
214
326
|
}
|
|
215
|
-
const httpRequest = context.
|
|
327
|
+
const httpRequest = context.getNetworkRequestById(id);
|
|
216
328
|
response.push(`## Request ${httpRequest.url()}`);
|
|
217
329
|
response.push(`Status: ${getStatusFromRequest(httpRequest)}`);
|
|
218
330
|
response.push(`### Request Headers`);
|
|
219
331
|
for (const line of getFormattedHeaderValue(httpRequest.headers())) {
|
|
220
332
|
response.push(line);
|
|
221
333
|
}
|
|
222
|
-
if (
|
|
334
|
+
if (data.requestBody) {
|
|
223
335
|
response.push(`### Request Body`);
|
|
224
|
-
response.push(
|
|
336
|
+
response.push(data.requestBody);
|
|
225
337
|
}
|
|
226
338
|
const httpResponse = httpRequest.response();
|
|
227
339
|
if (httpResponse) {
|
|
@@ -230,9 +342,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
230
342
|
response.push(line);
|
|
231
343
|
}
|
|
232
344
|
}
|
|
233
|
-
if (
|
|
345
|
+
if (data.responseBody) {
|
|
234
346
|
response.push(`### Response Body`);
|
|
235
|
-
response.push(
|
|
347
|
+
response.push(data.responseBody);
|
|
236
348
|
}
|
|
237
349
|
const httpFailure = httpRequest.failure();
|
|
238
350
|
if (httpFailure) {
|
|
@@ -244,7 +356,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
244
356
|
response.push(`### Redirect chain`);
|
|
245
357
|
let indent = 0;
|
|
246
358
|
for (const request of redirectChain.reverse()) {
|
|
247
|
-
response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request)}`);
|
|
359
|
+
response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`);
|
|
248
360
|
indent++;
|
|
249
361
|
}
|
|
250
362
|
}
|