chrome-devtools-mcp 0.8.0 → 0.9.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.
Files changed (87) hide show
  1. package/README.md +79 -5
  2. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +8 -6
  3. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +1 -1
  4. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Worker.js +10 -2
  5. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +2 -1
  6. package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +1 -1
  7. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/ConnectionTransport.js +12 -0
  8. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +15 -27
  9. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +2 -8
  10. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +42 -7
  11. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSRule.js +34 -6
  12. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +3 -0
  13. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +2 -2
  14. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +3 -0
  15. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +2 -1
  16. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +336 -40
  17. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +56 -13
  18. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +32 -7
  19. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
  20. package/build/node_modules/chrome-devtools-frontend/front_end/{models/source_map_scopes → core/sdk}/ScopeTreeCache.js +9 -5
  21. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +48 -11
  22. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +8 -2
  23. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +131 -8
  24. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TargetManager.js +0 -21
  25. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +9 -6
  26. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk.js +2 -1
  27. package/build/node_modules/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1301 -174
  28. package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +7 -0
  29. package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +8 -6
  30. package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +16 -19
  31. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +50 -34
  32. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +2 -3
  33. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +45 -2
  34. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +14 -0
  35. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +5 -11
  36. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +1 -2
  37. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +8 -0
  38. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +6 -3
  39. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +10 -3
  40. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +4 -1
  41. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +1 -1
  42. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +2 -1
  43. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Cache.js +2 -1
  44. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DOMSize.js +2 -1
  45. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +2 -1
  46. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +2 -1
  47. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/FontDisplay.js +2 -1
  48. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ForcedReflow.js +3 -2
  49. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +2 -1
  50. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +2 -1
  51. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -1
  52. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -1
  53. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LegacyJavaScript.js +2 -1
  54. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +2 -1
  55. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +2 -1
  56. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +2 -1
  57. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/SlowCSSSelector.js +2 -1
  58. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ThirdParties.js +2 -1
  59. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Viewport.js +2 -1
  60. package/build/src/DevToolsConnectionAdapter.js +32 -0
  61. package/build/src/McpContext.js +81 -31
  62. package/build/src/McpResponse.js +149 -45
  63. package/build/src/PageCollector.js +110 -26
  64. package/build/src/WaitForHelper.js +5 -0
  65. package/build/src/browser.js +17 -7
  66. package/build/src/cli.js +82 -6
  67. package/build/src/formatters/consoleFormatter.js +29 -62
  68. package/build/src/formatters/networkFormatter.js +5 -6
  69. package/build/src/formatters/snapshotFormatter.js +23 -51
  70. package/build/src/logger.js +1 -1
  71. package/build/src/main.js +27 -26
  72. package/build/src/polyfill.js +2 -2
  73. package/build/src/third_party/THIRD_PARTY_NOTICES +1393 -0
  74. package/build/src/third_party/index.js +76159 -0
  75. package/build/src/tools/ToolDefinition.js +2 -2
  76. package/build/src/tools/categories.js +17 -9
  77. package/build/src/tools/console.js +71 -6
  78. package/build/src/tools/emulation.js +6 -7
  79. package/build/src/tools/input.js +78 -41
  80. package/build/src/tools/network.js +18 -10
  81. package/build/src/tools/pages.js +21 -21
  82. package/build/src/tools/performance.js +8 -8
  83. package/build/src/tools/screenshot.js +8 -8
  84. package/build/src/tools/script.js +29 -15
  85. package/build/src/tools/snapshot.js +15 -20
  86. package/build/src/utils/types.js +6 -0
  87. package/package.json +17 -14
@@ -1,4 +1,4 @@
1
- import { formatConsoleEvent } from './formatters/consoleFormatter.js';
1
+ import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
2
2
  import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
3
3
  import { formatA11ySnapshot } from './formatters/snapshotFormatter.js';
4
4
  import { handleDialog } from './tools/pages.js';
@@ -6,17 +6,19 @@ import { paginate } from './utils/pagination.js';
6
6
  export class McpResponse {
7
7
  #includePages = false;
8
8
  #includeSnapshot = false;
9
- #attachedNetworkRequestData;
10
- #includeConsoleData = false;
9
+ #includeVerboseSnapshot = false;
10
+ #attachedNetworkRequestId;
11
+ #attachedConsoleMessageId;
11
12
  #textResponseLines = [];
12
- #formattedConsoleData;
13
13
  #images = [];
14
14
  #networkRequestsOptions;
15
+ #consoleDataOptions;
15
16
  setIncludePages(value) {
16
17
  this.#includePages = value;
17
18
  }
18
- setIncludeSnapshot(value) {
19
+ setIncludeSnapshot(value, verbose = false) {
19
20
  this.#includeSnapshot = value;
21
+ this.#includeVerboseSnapshot = verbose;
20
22
  }
21
23
  setIncludeNetworkRequests(value, options) {
22
24
  if (!value) {
@@ -32,16 +34,32 @@ export class McpResponse {
32
34
  }
33
35
  : undefined,
34
36
  resourceTypes: options?.resourceTypes,
37
+ includePreservedRequests: options?.includePreservedRequests,
35
38
  };
36
39
  }
37
- setIncludeConsoleData(value) {
38
- this.#includeConsoleData = value;
39
- }
40
- attachNetworkRequest(url) {
41
- this.#attachedNetworkRequestData = {
42
- networkRequestUrl: url,
40
+ setIncludeConsoleData(value, options) {
41
+ if (!value) {
42
+ this.#consoleDataOptions = undefined;
43
+ return;
44
+ }
45
+ this.#consoleDataOptions = {
46
+ include: value,
47
+ pagination: options?.pageSize || options?.pageIdx
48
+ ? {
49
+ pageSize: options.pageSize,
50
+ pageIdx: options.pageIdx,
51
+ }
52
+ : undefined,
53
+ types: options?.types,
54
+ includePreservedMessages: options?.includePreservedMessages,
43
55
  };
44
56
  }
57
+ attachNetworkRequest(reqid) {
58
+ this.#attachedNetworkRequestId = reqid;
59
+ }
60
+ attachConsoleMessage(msgid) {
61
+ this.#attachedConsoleMessageId = msgid;
62
+ }
45
63
  get includePages() {
46
64
  return this.#includePages;
47
65
  }
@@ -49,14 +67,20 @@ export class McpResponse {
49
67
  return this.#networkRequestsOptions?.include ?? false;
50
68
  }
51
69
  get includeConsoleData() {
52
- return this.#includeConsoleData;
70
+ return this.#consoleDataOptions?.include ?? false;
53
71
  }
54
- get attachedNetworkRequestUrl() {
55
- return this.#attachedNetworkRequestData?.networkRequestUrl;
72
+ get attachedNetworkRequestId() {
73
+ return this.#attachedNetworkRequestId;
56
74
  }
57
75
  get networkRequestsPageIdx() {
58
76
  return this.#networkRequestsOptions?.pagination?.pageIdx;
59
77
  }
78
+ get consoleMessagesPageIdx() {
79
+ return this.#consoleDataOptions?.pagination?.pageIdx;
80
+ }
81
+ get consoleMessagesTypes() {
82
+ return this.#consoleDataOptions?.types;
83
+ }
60
84
  appendResponseLine(value) {
61
85
  this.#textResponseLines.push(value);
62
86
  }
@@ -72,34 +96,99 @@ export class McpResponse {
72
96
  get includeSnapshot() {
73
97
  return this.#includeSnapshot;
74
98
  }
99
+ get includeVersboseSnapshot() {
100
+ return this.#includeVerboseSnapshot;
101
+ }
75
102
  async handle(toolName, context) {
76
103
  if (this.#includePages) {
77
104
  await context.createPagesSnapshot();
78
105
  }
79
106
  if (this.#includeSnapshot) {
80
- await context.createTextSnapshot();
107
+ await context.createTextSnapshot(this.#includeVerboseSnapshot);
81
108
  }
82
- let formattedConsoleMessages;
83
- if (this.#attachedNetworkRequestData?.networkRequestUrl) {
84
- const request = context.getNetworkRequestByUrl(this.#attachedNetworkRequestData.networkRequestUrl);
85
- this.#attachedNetworkRequestData.requestBody =
86
- await getFormattedRequestBody(request);
109
+ const bodies = {};
110
+ if (this.#attachedNetworkRequestId) {
111
+ const request = context.getNetworkRequestById(this.#attachedNetworkRequestId);
112
+ bodies.requestBody = await getFormattedRequestBody(request);
87
113
  const response = request.response();
88
114
  if (response) {
89
- this.#attachedNetworkRequestData.responseBody =
90
- await getFormattedResponseBody(response);
115
+ bodies.responseBody = await getFormattedResponseBody(response);
91
116
  }
92
117
  }
93
- if (this.#includeConsoleData) {
94
- const consoleMessages = context.getConsoleData();
95
- if (consoleMessages) {
96
- formattedConsoleMessages = await Promise.all(consoleMessages.map(message => formatConsoleEvent(message)));
97
- this.#formattedConsoleData = formattedConsoleMessages;
118
+ let consoleData;
119
+ if (this.#attachedConsoleMessageId) {
120
+ const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
121
+ const consoleMessageStableId = this.#attachedConsoleMessageId;
122
+ if ('args' in message) {
123
+ const consoleMessage = message;
124
+ consoleData = {
125
+ consoleMessageStableId,
126
+ type: consoleMessage.type(),
127
+ message: consoleMessage.text(),
128
+ args: await Promise.all(consoleMessage.args().map(async (arg) => {
129
+ const stringArg = await arg.jsonValue().catch(() => {
130
+ // Ignore errors.
131
+ });
132
+ return typeof stringArg === 'object'
133
+ ? JSON.stringify(stringArg)
134
+ : String(stringArg);
135
+ })),
136
+ };
137
+ }
138
+ else {
139
+ consoleData = {
140
+ consoleMessageStableId,
141
+ type: 'error',
142
+ message: message.message,
143
+ args: [],
144
+ };
98
145
  }
99
146
  }
100
- return this.format(toolName, context);
147
+ let consoleListData;
148
+ if (this.#consoleDataOptions?.include) {
149
+ let messages = context.getConsoleData(this.#consoleDataOptions.includePreservedMessages);
150
+ if (this.#consoleDataOptions.types?.length) {
151
+ const normalizedTypes = new Set(this.#consoleDataOptions.types);
152
+ messages = messages.filter(message => {
153
+ if ('type' in message) {
154
+ return normalizedTypes.has(message.type());
155
+ }
156
+ return normalizedTypes.has('error');
157
+ });
158
+ }
159
+ consoleListData = await Promise.all(messages.map(async (item) => {
160
+ const consoleMessageStableId = context.getConsoleMessageStableId(item);
161
+ if ('args' in item) {
162
+ const consoleMessage = item;
163
+ return {
164
+ consoleMessageStableId,
165
+ type: consoleMessage.type(),
166
+ message: consoleMessage.text(),
167
+ args: await Promise.all(consoleMessage.args().map(async (arg) => {
168
+ const stringArg = await arg.jsonValue().catch(() => {
169
+ // Ignore errors.
170
+ });
171
+ return typeof stringArg === 'object'
172
+ ? JSON.stringify(stringArg)
173
+ : String(stringArg);
174
+ })),
175
+ };
176
+ }
177
+ return {
178
+ consoleMessageStableId,
179
+ type: 'error',
180
+ message: item.message,
181
+ args: [],
182
+ };
183
+ }));
184
+ }
185
+ return this.format(toolName, context, {
186
+ bodies,
187
+ consoleData,
188
+ consoleListData,
189
+ });
101
190
  }
102
- format(toolName, context) {
191
+ format(toolName, context, data) {
103
192
  const response = [`# ${toolName} response`];
104
193
  for (const line of this.#textResponseLines) {
105
194
  response.push(line);
@@ -117,8 +206,11 @@ export class McpResponse {
117
206
  }
118
207
  const dialog = context.getDialog();
119
208
  if (dialog) {
209
+ const defaultValueIfNeeded = dialog.type() === 'prompt'
210
+ ? ` (default value: "${dialog.defaultValue()}")`
211
+ : '';
120
212
  response.push(`# Open dialog
121
- ${dialog.type()}: ${dialog.message()} (default value: ${dialog.message()}).
213
+ ${dialog.type()}: ${dialog.message()}${defaultValueIfNeeded}.
122
214
  Call ${handleDialog.name} to handle it before continuing.`);
123
215
  }
124
216
  if (this.#includePages) {
@@ -138,9 +230,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
138
230
  response.push(formattedSnapshot);
139
231
  }
140
232
  }
141
- response.push(...this.#getIncludeNetworkRequestsData(context));
233
+ response.push(...this.#formatNetworkRequestData(context, data.bodies));
234
+ response.push(...this.#formatConsoleData(data.consoleData));
142
235
  if (this.#networkRequestsOptions?.include) {
143
- let requests = context.getNetworkRequests();
236
+ let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
144
237
  // Apply resource type filtering if specified
145
238
  if (this.#networkRequestsOptions.resourceTypes?.length) {
146
239
  const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
@@ -154,17 +247,20 @@ Call ${handleDialog.name} to handle it before continuing.`);
154
247
  const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
155
248
  response.push(...data.info);
156
249
  for (const request of data.items) {
157
- response.push(getShortDescriptionForRequest(request));
250
+ response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request)));
158
251
  }
159
252
  }
160
253
  else {
161
254
  response.push('No requests found.');
162
255
  }
163
256
  }
164
- if (this.#includeConsoleData && this.#formattedConsoleData) {
257
+ if (this.#consoleDataOptions?.include) {
258
+ const messages = data.consoleListData ?? [];
165
259
  response.push('## Console messages');
166
- if (this.#formattedConsoleData.length) {
167
- response.push(...this.#formattedConsoleData);
260
+ if (messages.length) {
261
+ const data = this.#dataWithPagination(messages, this.#consoleDataOptions.pagination);
262
+ response.push(...data.info);
263
+ response.push(...data.items.map(message => formatConsoleEventShort(message)));
168
264
  }
169
265
  else {
170
266
  response.push('<no console messages found>');
@@ -203,22 +299,30 @@ Call ${handleDialog.name} to handle it before continuing.`);
203
299
  items: paginationResult.items,
204
300
  };
205
301
  }
206
- #getIncludeNetworkRequestsData(context) {
302
+ #formatConsoleData(data) {
303
+ const response = [];
304
+ if (!data) {
305
+ return response;
306
+ }
307
+ response.push(formatConsoleEventVerbose(data));
308
+ return response;
309
+ }
310
+ #formatNetworkRequestData(context, data) {
207
311
  const response = [];
208
- const url = this.#attachedNetworkRequestData?.networkRequestUrl;
209
- if (!url) {
312
+ const id = this.#attachedNetworkRequestId;
313
+ if (!id) {
210
314
  return response;
211
315
  }
212
- const httpRequest = context.getNetworkRequestByUrl(url);
316
+ const httpRequest = context.getNetworkRequestById(id);
213
317
  response.push(`## Request ${httpRequest.url()}`);
214
318
  response.push(`Status: ${getStatusFromRequest(httpRequest)}`);
215
319
  response.push(`### Request Headers`);
216
320
  for (const line of getFormattedHeaderValue(httpRequest.headers())) {
217
321
  response.push(line);
218
322
  }
219
- if (this.#attachedNetworkRequestData?.requestBody) {
323
+ if (data.requestBody) {
220
324
  response.push(`### Request Body`);
221
- response.push(this.#attachedNetworkRequestData.requestBody);
325
+ response.push(data.requestBody);
222
326
  }
223
327
  const httpResponse = httpRequest.response();
224
328
  if (httpResponse) {
@@ -227,9 +331,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
227
331
  response.push(line);
228
332
  }
229
333
  }
230
- if (this.#attachedNetworkRequestData?.responseBody) {
334
+ if (data.responseBody) {
231
335
  response.push(`### Response Body`);
232
- response.push(this.#attachedNetworkRequestData.responseBody);
336
+ response.push(data.responseBody);
233
337
  }
234
338
  const httpFailure = httpRequest.failure();
235
339
  if (httpFailure) {
@@ -241,7 +345,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
241
345
  response.push(`### Redirect chain`);
242
346
  let indent = 0;
243
347
  for (const request of redirectChain.reverse()) {
244
- response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request)}`);
348
+ response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`);
245
349
  indent++;
246
350
  }
247
351
  }
@@ -3,18 +3,30 @@
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
- #initializer;
18
+ #listenersInitializer;
19
+ #listeners = new WeakMap();
20
+ #maxNavigationSaved = 3;
9
21
  /**
10
- * The Array in this map should only be set once
11
- * As we use the reference to it.
12
- * Use methods that manipulate the array in place.
22
+ * This maps a Page to a list of navigations with a sub-list
23
+ * of all collected resources.
24
+ * The newer navigations come first.
13
25
  */
14
26
  storage = new WeakMap();
15
- constructor(browser, initializer) {
27
+ constructor(browser, listeners) {
16
28
  this.#browser = browser;
17
- this.#initializer = initializer;
29
+ this.#listenersInitializer = listeners;
18
30
  }
19
31
  async init() {
20
32
  const pages = await this.#browser.pages();
@@ -28,6 +40,13 @@ export class PageCollector {
28
40
  }
29
41
  this.#initializePage(page);
30
42
  });
43
+ this.#browser.on('targetdestroyed', async (target) => {
44
+ const page = await target.page();
45
+ if (!page) {
46
+ return;
47
+ }
48
+ this.#cleanupPageDestroyed(page);
49
+ });
31
50
  }
32
51
  addPage(page) {
33
52
  this.#initializePage(page);
@@ -36,36 +55,95 @@ export class PageCollector {
36
55
  if (this.storage.has(page)) {
37
56
  return;
38
57
  }
39
- const stored = [];
40
- this.storage.set(page, stored);
41
- page.on('framenavigated', frame => {
42
- // Only reset the storage on main frame navigation
58
+ const idGenerator = createIdGenerator();
59
+ const storedLists = [[]];
60
+ this.storage.set(page, storedLists);
61
+ const listeners = this.#listenersInitializer(value => {
62
+ const withId = value;
63
+ withId[stableIdSymbol] = idGenerator();
64
+ const navigations = this.storage.get(page) ?? [[]];
65
+ navigations[0].push(withId);
66
+ });
67
+ listeners['framenavigated'] = (frame) => {
68
+ // Only split the storage on main frame navigation
43
69
  if (frame !== page.mainFrame()) {
44
70
  return;
45
71
  }
46
- this.cleanup(page);
47
- });
48
- this.#initializer(page, value => {
49
- stored.push(value);
50
- });
72
+ this.splitAfterNavigation(page);
73
+ };
74
+ for (const [name, listener] of Object.entries(listeners)) {
75
+ page.on(name, listener);
76
+ }
77
+ this.#listeners.set(page, listeners);
51
78
  }
52
- cleanup(page) {
53
- const collection = this.storage.get(page);
54
- if (collection) {
55
- // Keep the reference alive
56
- collection.length = 0;
79
+ splitAfterNavigation(page) {
80
+ const navigations = this.storage.get(page);
81
+ if (!navigations) {
82
+ return;
57
83
  }
84
+ // Add the latest navigation first
85
+ navigations.unshift([]);
86
+ navigations.splice(this.#maxNavigationSaved);
58
87
  }
59
- getData(page) {
60
- return this.storage.get(page) ?? [];
88
+ #cleanupPageDestroyed(page) {
89
+ const listeners = this.#listeners.get(page);
90
+ if (listeners) {
91
+ for (const [name, listener] of Object.entries(listeners)) {
92
+ page.off(name, listener);
93
+ }
94
+ }
95
+ this.storage.delete(page);
96
+ }
97
+ getData(page, includePreservedData) {
98
+ const navigations = this.storage.get(page);
99
+ if (!navigations) {
100
+ return [];
101
+ }
102
+ if (!includePreservedData) {
103
+ return navigations[0];
104
+ }
105
+ const data = [];
106
+ for (let index = this.#maxNavigationSaved; index >= 0; index--) {
107
+ if (navigations[index]) {
108
+ data.push(...navigations[index]);
109
+ }
110
+ }
111
+ return data;
112
+ }
113
+ getIdForResource(resource) {
114
+ return resource[stableIdSymbol] ?? -1;
115
+ }
116
+ getById(page, stableId) {
117
+ const navigations = this.storage.get(page);
118
+ if (!navigations) {
119
+ throw new Error('No requests found for selected page');
120
+ }
121
+ for (const navigation of navigations) {
122
+ for (const collected of navigation) {
123
+ if (collected[stableIdSymbol] === stableId) {
124
+ return collected;
125
+ }
126
+ }
127
+ }
128
+ throw new Error('Request not found for selected page');
61
129
  }
62
130
  }
63
131
  export class NetworkCollector extends PageCollector {
64
- cleanup(page) {
65
- const requests = this.storage.get(page) ?? [];
66
- if (!requests) {
132
+ constructor(browser, listeners = collect => {
133
+ return {
134
+ request: req => {
135
+ collect(req);
136
+ },
137
+ };
138
+ }) {
139
+ super(browser, listeners);
140
+ }
141
+ splitAfterNavigation(page) {
142
+ const navigations = this.storage.get(page) ?? [];
143
+ if (!navigations) {
67
144
  return;
68
145
  }
146
+ const requests = navigations[0];
69
147
  const lastRequestIdx = requests.findLastIndex(request => {
70
148
  return request.frame() === page.mainFrame()
71
149
  ? request.isNavigationRequest()
@@ -74,6 +152,12 @@ export class NetworkCollector extends PageCollector {
74
152
  // Keep all requests since the last navigation request including that
75
153
  // navigation request itself.
76
154
  // Keep the reference
77
- requests.splice(0, Math.max(lastRequestIdx, 0));
155
+ if (lastRequestIdx !== -1) {
156
+ const fromCurrentNavigation = requests.splice(lastRequestIdx);
157
+ navigations.unshift(fromCurrentNavigation);
158
+ }
159
+ else {
160
+ navigations.unshift([]);
161
+ }
78
162
  }
79
163
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
1
6
  import { logger } from './logger.js';
2
7
  export class WaitForHelper {
3
8
  #abortController = new AbortController();
@@ -6,7 +6,7 @@
6
6
  import fs from 'node:fs';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
- import puppeteer from 'puppeteer-core';
9
+ import { puppeteer } from './third_party/index.js';
10
10
  let browser;
11
11
  function makeTargetFilter(devtools) {
12
12
  const ignoredPrefixes = new Set([
@@ -33,12 +33,24 @@ export async function ensureBrowserConnected(options) {
33
33
  if (browser?.connected) {
34
34
  return browser;
35
35
  }
36
- browser = await puppeteer.connect({
36
+ const connectOptions = {
37
37
  targetFilter: makeTargetFilter(options.devtools),
38
- browserURL: options.browserURL,
39
38
  defaultViewport: null,
40
39
  handleDevToolsAsPage: options.devtools,
41
- });
40
+ };
41
+ if (options.wsEndpoint) {
42
+ connectOptions.browserWSEndpoint = options.wsEndpoint;
43
+ if (options.wsHeaders) {
44
+ connectOptions.headers = options.wsHeaders;
45
+ }
46
+ }
47
+ else if (options.browserURL) {
48
+ connectOptions.browserURL = options.browserURL;
49
+ }
50
+ else {
51
+ throw new Error('Either browserURL or wsEndpoint must be provided');
52
+ }
53
+ browser = await puppeteer.connect(connectOptions);
42
54
  return browser;
43
55
  }
44
56
  export async function launch(options) {
@@ -101,9 +113,7 @@ export async function launch(options) {
101
113
  }
102
114
  catch (error) {
103
115
  if (userDataDir &&
104
- (error.message.includes('The browser is already running') ||
105
- error.message.includes('Target closed') ||
106
- error.message.includes('Connection closed'))) {
116
+ error.message.includes('The browser is already running')) {
107
117
  throw new Error(`The browser is already running for ${userDataDir}. Use --isolated to run multiple browser instances.`, {
108
118
  cause: error,
109
119
  });