@nimbus21.ai/chrome-devtools-mcp 0.12.3 → 0.17.4
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 +119 -24
- package/build/src/DevtoolsUtils.js +174 -42
- package/build/src/McpContext.js +127 -29
- package/build/src/McpResponse.js +245 -159
- package/build/src/PageCollector.js +18 -2
- package/build/src/WaitForHelper.js +2 -2
- package/build/src/browser.js +8 -13
- package/build/src/cli.js +65 -3
- package/build/src/formatters/ConsoleFormatter.js +240 -0
- package/build/src/formatters/IssueFormatter.js +190 -0
- package/build/src/formatters/NetworkFormatter.js +218 -0
- package/build/src/formatters/SnapshotFormatter.js +134 -0
- package/build/src/logger.js +9 -0
- package/build/src/main.js +66 -8
- package/build/src/telemetry/ClearcutLogger.js +102 -0
- package/build/src/telemetry/WatchdogClient.js +60 -0
- package/build/src/telemetry/flagUtils.js +45 -0
- package/build/src/telemetry/metricUtils.js +14 -0
- package/build/src/telemetry/persistence.js +53 -0
- package/build/src/telemetry/types.js +33 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +201 -0
- package/build/src/telemetry/watchdog/main.js +127 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +10 -9
- package/build/src/third_party/bundled-packages.json +8 -0
- package/build/src/third_party/devtools-formatter-worker.js +15449 -0
- package/build/src/third_party/index.js +4093 -2367
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +12 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +7 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
- package/build/src/tools/categories.js +2 -0
- package/build/src/tools/emulation.js +83 -1
- package/build/src/tools/extensions.js +79 -0
- package/build/src/tools/input.js +93 -13
- package/build/src/tools/network.js +17 -3
- package/build/src/tools/pages.js +134 -54
- package/build/src/tools/performance.js +68 -27
- package/build/src/tools/script.js +2 -2
- package/build/src/tools/tools.js +2 -0
- package/build/src/utils/ExtensionRegistry.js +35 -0
- package/build/src/utils/string.js +36 -0
- package/package.json +17 -16
- package/build/src/formatters/consoleFormatter.js +0 -121
- package/build/src/formatters/networkFormatter.js +0 -77
- package/build/src/formatters/snapshotFormatter.js +0 -73
- package/build/src/third_party/devtools.js +0 -6
- package/build/src/third_party/issue-descriptions/SameSiteInvalidSameParty.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataHttpNotFound.md +0 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataInvalidResponse.md +0 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataNoResponse.md +0 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Chrome DevTools for reliable automation, in-depth debugging, and performance ana
|
|
|
25
25
|
DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record
|
|
26
26
|
traces and extract actionable performance insights.
|
|
27
27
|
- **Advanced browser debugging**: Analyze network requests, take screenshots and
|
|
28
|
-
check
|
|
28
|
+
check browser console messages (with source-mapped stack traces).
|
|
29
29
|
- **Reliable automation**. Uses
|
|
30
30
|
[puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in
|
|
31
31
|
Chrome and automatically wait for action results.
|
|
@@ -37,6 +37,28 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
|
|
|
37
37
|
Avoid sharing sensitive or personal information that you don't want to share with
|
|
38
38
|
MCP clients.
|
|
39
39
|
|
|
40
|
+
Performance tools may send trace URLs to the Google CrUX API to fetch real-user
|
|
41
|
+
experience data. This helps provide a holistic performance picture by
|
|
42
|
+
presenting field data alongside lab data. This data is collected by the [Chrome
|
|
43
|
+
User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable
|
|
44
|
+
this, run with the `--no-performance-crux` flag.
|
|
45
|
+
|
|
46
|
+
## **Usage statistics**
|
|
47
|
+
|
|
48
|
+
Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
|
|
49
|
+
|
|
50
|
+
Data collection is **enabled by default**. You can opt-out by passing the `--no-usage-statistics` flag when starting the server:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
"args": ["-y", "chrome-devtools-mcp@latest", "--no-usage-statistics"]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Google handles this data in accordance with the [Google Privacy Policy](https://policies.google.com/privacy).
|
|
57
|
+
|
|
58
|
+
Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa.
|
|
59
|
+
|
|
60
|
+
Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
|
|
61
|
+
|
|
40
62
|
## Requirements
|
|
41
63
|
|
|
42
64
|
- [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
|
|
@@ -76,7 +98,7 @@ amp mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
|
|
|
76
98
|
<details>
|
|
77
99
|
<summary>Antigravity</summary>
|
|
78
100
|
|
|
79
|
-
To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs
|
|
101
|
+
To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs</a> to install a custom MCP server. Add the following config to the MCP servers config:
|
|
80
102
|
|
|
81
103
|
```bash
|
|
82
104
|
{
|
|
@@ -101,12 +123,34 @@ Chrome DevTools MCP will not start the browser instance automatically using this
|
|
|
101
123
|
|
|
102
124
|
<details>
|
|
103
125
|
<summary>Claude Code</summary>
|
|
104
|
-
|
|
126
|
+
|
|
127
|
+
**Install via CLI (MCP only)**
|
|
128
|
+
|
|
129
|
+
Use the Claude Code CLI to add the Chrome DevTools MCP server (<a href="https://code.claude.com/docs/en/mcp">guide</a>):
|
|
105
130
|
|
|
106
131
|
```bash
|
|
107
|
-
claude mcp add chrome-devtools npx @nimbus21.ai/chrome-devtools-mcp@latest
|
|
132
|
+
claude mcp add chrome-devtools --scope user npx @nimbus21.ai/chrome-devtools-mcp@latest
|
|
108
133
|
```
|
|
109
134
|
|
|
135
|
+
**Install as a Plugin (MCP + Skills)**
|
|
136
|
+
|
|
137
|
+
> [!NOTE]
|
|
138
|
+
> If you already had Chrome DevTools MCP installed previously for Claude Code, make sure to remove it first from your installation and configuration files.
|
|
139
|
+
|
|
140
|
+
To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
/plugin marketplace add ChromeDevTools/chrome-devtools-mcp
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Then, install the plugin:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
/plugin install chrome-devtools-mcp
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Restart Claude Code to have the MCP server and skills load (check with `/skills`).
|
|
153
|
+
|
|
110
154
|
</details>
|
|
111
155
|
|
|
112
156
|
<details>
|
|
@@ -116,7 +160,7 @@ claude mcp add chrome-devtools npx @nimbus21.ai/chrome-devtools-mcp@latest
|
|
|
116
160
|
|
|
117
161
|
<details>
|
|
118
162
|
<summary>Codex</summary>
|
|
119
|
-
Follow the <a href="https://
|
|
163
|
+
Follow the <a href="https://developers.openai.com/codex/mcp/#configure-with-the-cli">configure MCP guide</a>
|
|
120
164
|
using the standard config from above. You can also install the Chrome DevTools MCP server using the Codex CLI:
|
|
121
165
|
|
|
122
166
|
```bash
|
|
@@ -208,7 +252,10 @@ Install the Chrome DevTools MCP server using the Gemini CLI.
|
|
|
208
252
|
**Project wide:**
|
|
209
253
|
|
|
210
254
|
```bash
|
|
255
|
+
# Either MCP only:
|
|
211
256
|
gemini mcp add chrome-devtools npx @nimbus21.ai/chrome-devtools-mcp@latest
|
|
257
|
+
# Or as a Gemini extension (MCP+Skills):
|
|
258
|
+
gemini extensions install --auto-update https://github.com/ChromeDevTools/chrome-devtools-mcp
|
|
212
259
|
```
|
|
213
260
|
|
|
214
261
|
**Globally:**
|
|
@@ -244,6 +291,49 @@ Or, from the IDE **Activity Bar** > `Kiro` > `MCP Servers` > `Click Open MCP Con
|
|
|
244
291
|
|
|
245
292
|
</details>
|
|
246
293
|
|
|
294
|
+
<details>
|
|
295
|
+
<summary>Katalon Studio</summary>
|
|
296
|
+
|
|
297
|
+
The Chrome DevTools MCP server can be used with <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-chrome-devtools-mcp-server-for-studioassist">Katalon StudioAssist</a> via an MCP proxy.
|
|
298
|
+
|
|
299
|
+
**Step 1:** Install the MCP proxy by following the <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-mcp-proxy-for-stdio-mcp-servers">MCP proxy setup guide</a>.
|
|
300
|
+
|
|
301
|
+
**Step 2:** Start the Chrome DevTools MCP server with the proxy:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
mcp-proxy --transport streamablehttp --port 8080 -- npx -y chrome-devtools-mcp@latest
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Note:** You may need to pick another port if 8080 is already in use.
|
|
308
|
+
|
|
309
|
+
**Step 3:** In Katalon Studio, add the server to StudioAssist with the following settings:
|
|
310
|
+
|
|
311
|
+
- **Connection URL:** `http://127.0.0.1:8080/mcp`
|
|
312
|
+
- **Transport type:** `HTTP`
|
|
313
|
+
|
|
314
|
+
Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
|
|
315
|
+
|
|
316
|
+
</details>
|
|
317
|
+
|
|
318
|
+
<details>
|
|
319
|
+
<summary>OpenCode</summary>
|
|
320
|
+
|
|
321
|
+
Add the following configuration to your `opencode.json` file. If you don't have one, create it at `~/.config/opencode/opencode.json` (<a href="https://opencode.ai/docs/mcp-servers">guide</a>):
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"$schema": "https://opencode.ai/config.json",
|
|
326
|
+
"mcp": {
|
|
327
|
+
"chrome-devtools": {
|
|
328
|
+
"type": "local",
|
|
329
|
+
"command": ["npx", "-y", "chrome-devtools-mcp@latest"]
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
</details>
|
|
336
|
+
|
|
247
337
|
<details>
|
|
248
338
|
<summary>Qoder</summary>
|
|
249
339
|
|
|
@@ -354,7 +444,7 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
354
444
|
<!-- BEGIN AUTO GENERATED OPTIONS -->
|
|
355
445
|
|
|
356
446
|
- **`--autoConnect`/ `--auto-connect`**
|
|
357
|
-
If specified, automatically connects to a browser (Chrome
|
|
447
|
+
If specified, automatically connects to a browser (Chrome 144+) running in the user data directory identified by the channel param. Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
|
|
358
448
|
- **Type:** boolean
|
|
359
449
|
- **Default:** `false`
|
|
360
450
|
|
|
@@ -421,6 +511,10 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
421
511
|
Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
|
|
422
512
|
- **Type:** array
|
|
423
513
|
|
|
514
|
+
- **`--ignoreDefaultChromeArg`/ `--ignore-default-chrome-arg`**
|
|
515
|
+
Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
|
|
516
|
+
- **Type:** array
|
|
517
|
+
|
|
424
518
|
- **`--categoryEmulation`/ `--category-emulation`**
|
|
425
519
|
Set to false to exclude tools related to emulation.
|
|
426
520
|
- **Type:** boolean
|
|
@@ -436,6 +530,16 @@ The Chrome DevTools MCP server supports the following configuration option:
|
|
|
436
530
|
- **Type:** boolean
|
|
437
531
|
- **Default:** `true`
|
|
438
532
|
|
|
533
|
+
- **`--performanceCrux`/ `--performance-crux`**
|
|
534
|
+
Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
|
|
535
|
+
- **Type:** boolean
|
|
536
|
+
- **Default:** `true`
|
|
537
|
+
|
|
538
|
+
- **`--usageStatistics`/ `--usage-statistics`**
|
|
539
|
+
Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
|
|
540
|
+
- **Type:** boolean
|
|
541
|
+
- **Default:** `true`
|
|
542
|
+
|
|
439
543
|
<!-- END AUTO GENERATED OPTIONS -->
|
|
440
544
|
|
|
441
545
|
Pass them via the `args` property in the JSON configuration. For example:
|
|
@@ -511,7 +615,7 @@ In these cases, start Chrome first and let the Chrome DevTools MCP server connec
|
|
|
511
615
|
|
|
512
616
|
**Step 1:** Set up remote debugging in Chrome
|
|
513
617
|
|
|
514
|
-
In Chrome, do the following to set up remote debugging:
|
|
618
|
+
In Chrome (\>= M144), do the following to set up remote debugging:
|
|
515
619
|
|
|
516
620
|
1. Navigate to `chrome://inspect/#remote-debugging` to enable remote debugging.
|
|
517
621
|
2. Follow the dialog UI to allow or disallow incoming debugging connections.
|
|
@@ -528,19 +632,12 @@ The following code snippet is an example configuration for gemini-cli:
|
|
|
528
632
|
"mcpServers": {
|
|
529
633
|
"chrome-devtools": {
|
|
530
634
|
"command": "npx",
|
|
531
|
-
"args": [
|
|
532
|
-
"chrome-devtools-mcp@latest",
|
|
533
|
-
"--autoConnect",
|
|
534
|
-
"--channel=canary"
|
|
535
|
-
]
|
|
635
|
+
"args": ["chrome-devtools-mcp@latest", "--autoConnect"]
|
|
536
636
|
}
|
|
537
637
|
}
|
|
538
638
|
}
|
|
539
639
|
```
|
|
540
640
|
|
|
541
|
-
Note: you have to specify `--channel=canary` until Chrome M144 has reached the
|
|
542
|
-
stable channel.
|
|
543
|
-
|
|
544
641
|
**Step 3:** Test your setup
|
|
545
642
|
|
|
546
643
|
Make sure your browser is running. Open gemini-cli and run the following prompt:
|
|
@@ -549,7 +646,8 @@ Make sure your browser is running. Open gemini-cli and run the following prompt:
|
|
|
549
646
|
Check the performance of https://developers.chrome.com
|
|
550
647
|
```
|
|
551
648
|
|
|
552
|
-
|
|
649
|
+
> [!NOTE]
|
|
650
|
+
> The <code>autoConnect</code> option requires the user to start Chrome. If the user has multiple active profiles, the MCP server will connect to the default profile (as determined by Chrome). The MCP server has access to all open windows for the selected profile.
|
|
553
651
|
|
|
554
652
|
The Chrome DevTools MCP server will try to connect to your running Chrome
|
|
555
653
|
instance. It shows a dialog asking for user permission.
|
|
@@ -623,13 +721,10 @@ If you hit VM-to-host port forwarding issues, see the “Remote debugging betwee
|
|
|
623
721
|
|
|
624
722
|
For more details on remote debugging, see the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/remote-debugging/).
|
|
625
723
|
|
|
626
|
-
|
|
724
|
+
### Debugging Chrome on Android
|
|
627
725
|
|
|
628
|
-
|
|
726
|
+
Please consult [these instructions](./docs/debugging-android.md).
|
|
727
|
+
|
|
728
|
+
## Known limitations
|
|
629
729
|
|
|
630
|
-
|
|
631
|
-
containers. If sandboxes are enabled, `chrome-devtools-mcp` is not able to start
|
|
632
|
-
Chrome that requires permissions to create its own sandboxes. As a workaround,
|
|
633
|
-
either disable sandboxing for `chrome-devtools-mcp` in your MCP client or use
|
|
634
|
-
`--browser-url` to connect to a Chrome instance that you start manually outside
|
|
635
|
-
of the MCP client sandbox.
|
|
730
|
+
See [Troubleshooting](./docs/troubleshooting.md).
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
|
|
7
|
-
import { ISSUE_UTILS } from './issue-descriptions.js';
|
|
8
|
-
import { logger } from './logger.js';
|
|
9
7
|
import { Mutex } from './Mutex.js';
|
|
10
8
|
import { DevTools } from './third_party/index.js';
|
|
11
9
|
export function extractUrlLikeFromDevToolsTitle(title) {
|
|
@@ -64,46 +62,6 @@ export class FakeIssuesManager extends DevTools.Common.ObjectWrapper
|
|
|
64
62
|
return [];
|
|
65
63
|
}
|
|
66
64
|
}
|
|
67
|
-
export function mapIssueToMessageObject(issue) {
|
|
68
|
-
const count = issue.getAggregatedIssuesCount();
|
|
69
|
-
const markdownDescription = issue.getDescription();
|
|
70
|
-
const filename = markdownDescription?.file;
|
|
71
|
-
if (!markdownDescription) {
|
|
72
|
-
logger(`no description found for issue:` + issue.code);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
const rawMarkdown = filename
|
|
76
|
-
? ISSUE_UTILS.getIssueDescription(filename)
|
|
77
|
-
: null;
|
|
78
|
-
if (!rawMarkdown) {
|
|
79
|
-
logger(`no markdown ${filename} found for issue:` + issue.code);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
let processedMarkdown;
|
|
83
|
-
let title;
|
|
84
|
-
try {
|
|
85
|
-
processedMarkdown =
|
|
86
|
-
DevTools.MarkdownIssueDescription.substitutePlaceholders(rawMarkdown, markdownDescription.substitutions);
|
|
87
|
-
const markdownAst = DevTools.Marked.Marked.lexer(processedMarkdown);
|
|
88
|
-
title =
|
|
89
|
-
DevTools.MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst);
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
logger('error parsing markdown for issue ' + issue.code());
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
if (!title) {
|
|
96
|
-
logger('cannot read issue title from ' + filename);
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
type: 'issue',
|
|
101
|
-
item: issue,
|
|
102
|
-
message: title,
|
|
103
|
-
count,
|
|
104
|
-
description: processedMarkdown,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
65
|
// DevTools CDP errors can get noisy.
|
|
108
66
|
DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
|
|
109
67
|
DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
|
|
@@ -115,6 +73,11 @@ DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
|
|
|
115
73
|
},
|
|
116
74
|
});
|
|
117
75
|
DevTools.I18n.i18n.registerLocaleDataForTest('en-US', {});
|
|
76
|
+
DevTools.Formatter.FormatterWorkerPool.FormatterWorkerPool.instance({
|
|
77
|
+
forceNew: true,
|
|
78
|
+
entrypointURL: import.meta
|
|
79
|
+
.resolve('./third_party/devtools-formatter-worker.js'),
|
|
80
|
+
});
|
|
118
81
|
export class UniverseManager {
|
|
119
82
|
#browser;
|
|
120
83
|
#createUniverseFor;
|
|
@@ -206,3 +169,172 @@ const SKIP_ALL_PAUSES = {
|
|
|
206
169
|
// Do nothing.
|
|
207
170
|
},
|
|
208
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* Constructed from Runtime.ExceptionDetails of an uncaught error.
|
|
174
|
+
*
|
|
175
|
+
* TODO: Also construct from a RemoteObject of subtype 'error'.
|
|
176
|
+
*
|
|
177
|
+
* Consists of the message, a fully resolved stack trace and a fully resolved 'cause' chain.
|
|
178
|
+
*/
|
|
179
|
+
export class SymbolizedError {
|
|
180
|
+
message;
|
|
181
|
+
stackTrace;
|
|
182
|
+
cause;
|
|
183
|
+
constructor(message, stackTrace, cause) {
|
|
184
|
+
this.message = message;
|
|
185
|
+
this.stackTrace = stackTrace;
|
|
186
|
+
this.cause = cause;
|
|
187
|
+
}
|
|
188
|
+
static async fromDetails(opts) {
|
|
189
|
+
const message = SymbolizedError.#getMessage(opts.details);
|
|
190
|
+
if (!opts.includeStackAndCause || !opts.devTools) {
|
|
191
|
+
return new SymbolizedError(message, opts.resolvedStackTraceForTesting, opts.resolvedCauseForTesting);
|
|
192
|
+
}
|
|
193
|
+
let stackTrace;
|
|
194
|
+
if (opts.resolvedStackTraceForTesting) {
|
|
195
|
+
stackTrace = opts.resolvedStackTraceForTesting;
|
|
196
|
+
}
|
|
197
|
+
else if (opts.details.stackTrace) {
|
|
198
|
+
try {
|
|
199
|
+
stackTrace = await createStackTrace(opts.devTools, opts.details.stackTrace, opts.targetId);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ignore
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
|
|
206
|
+
// If its an Error, recursively create a SymbolizedError.
|
|
207
|
+
let cause;
|
|
208
|
+
if (opts.resolvedCauseForTesting) {
|
|
209
|
+
cause = opts.resolvedCauseForTesting;
|
|
210
|
+
}
|
|
211
|
+
else if (opts.details.exception) {
|
|
212
|
+
try {
|
|
213
|
+
const causeRemoteObj = await SymbolizedError.#lookupCause(opts.devTools, opts.details.exception, opts.targetId);
|
|
214
|
+
if (causeRemoteObj) {
|
|
215
|
+
cause = await SymbolizedError.fromError({
|
|
216
|
+
devTools: opts.devTools,
|
|
217
|
+
error: causeRemoteObj,
|
|
218
|
+
targetId: opts.targetId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Ignore
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return new SymbolizedError(message, stackTrace, cause);
|
|
227
|
+
}
|
|
228
|
+
static async fromError(opts) {
|
|
229
|
+
const details = await SymbolizedError.#getExceptionDetails(opts.devTools, opts.error, opts.targetId);
|
|
230
|
+
if (details) {
|
|
231
|
+
return SymbolizedError.fromDetails({
|
|
232
|
+
details,
|
|
233
|
+
devTools: opts.devTools,
|
|
234
|
+
targetId: opts.targetId,
|
|
235
|
+
includeStackAndCause: true,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return new SymbolizedError(SymbolizedError.#getMessageFromException(opts.error));
|
|
239
|
+
}
|
|
240
|
+
static #getMessage(details) {
|
|
241
|
+
// For Runtime.exceptionThrown with a present exception object, `details.text` will be "Uncaught" and
|
|
242
|
+
// we have to manually parse out the error text from the exception description.
|
|
243
|
+
// In the case of Runtime.getExceptionDetails, `details.text` has the Error.message.
|
|
244
|
+
if (details.text === 'Uncaught' && details.exception) {
|
|
245
|
+
return ('Uncaught ' +
|
|
246
|
+
SymbolizedError.#getMessageFromException(details.exception));
|
|
247
|
+
}
|
|
248
|
+
return details.text;
|
|
249
|
+
}
|
|
250
|
+
static #getMessageFromException(error) {
|
|
251
|
+
const messageWithRest = error.description?.split('\n at ', 2) ?? [];
|
|
252
|
+
return messageWithRest[0] ?? '';
|
|
253
|
+
}
|
|
254
|
+
static async #getExceptionDetails(devTools, error, targetId) {
|
|
255
|
+
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
259
|
+
const target = targetId
|
|
260
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
261
|
+
: devTools.target;
|
|
262
|
+
const model = target.model(DevTools.RuntimeModel);
|
|
263
|
+
return ((await model.getExceptionDetails(error.objectId)) ?? null);
|
|
264
|
+
}
|
|
265
|
+
static async #lookupCause(devTools, error, targetId) {
|
|
266
|
+
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
270
|
+
const target = targetId
|
|
271
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
272
|
+
: devTools.target;
|
|
273
|
+
const properties = await target.runtimeAgent().invoke_getProperties({
|
|
274
|
+
objectId: error.objectId,
|
|
275
|
+
});
|
|
276
|
+
if (properties.getError()) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
|
|
280
|
+
}
|
|
281
|
+
static createForTesting(message, stackTrace, cause) {
|
|
282
|
+
return new SymbolizedError(message, stackTrace, cause);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
|
|
286
|
+
const message = consoleMessage;
|
|
287
|
+
const rawStackTrace = message._rawStackTrace();
|
|
288
|
+
if (rawStackTrace) {
|
|
289
|
+
return createStackTrace(devTools, rawStackTrace, message._targetId());
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
export async function createStackTrace(devTools, rawStackTrace, targetId) {
|
|
294
|
+
const targetManager = devTools.universe.context.get(DevTools.TargetManager);
|
|
295
|
+
const target = targetId
|
|
296
|
+
? targetManager.targetById(targetId) || devTools.target
|
|
297
|
+
: devTools.target;
|
|
298
|
+
const model = target.model(DevTools.DebuggerModel);
|
|
299
|
+
// DevTools doesn't wait for source maps to attach before building a stack trace, rather it'll send
|
|
300
|
+
// an update event once a source map was attached and the stack trace retranslated. This doesn't
|
|
301
|
+
// work in the MCP case, so we'll collect all script IDs upfront and wait for any pending source map
|
|
302
|
+
// loads before creating the stack trace. We might also have to wait for Debugger.ScriptParsed events if
|
|
303
|
+
// the stack trace is created particularly early.
|
|
304
|
+
const scriptIds = new Set();
|
|
305
|
+
for (const frame of rawStackTrace.callFrames) {
|
|
306
|
+
scriptIds.add(frame.scriptId);
|
|
307
|
+
}
|
|
308
|
+
for (let asyncStack = rawStackTrace.parent; asyncStack; asyncStack = asyncStack.parent) {
|
|
309
|
+
for (const frame of asyncStack.callFrames) {
|
|
310
|
+
scriptIds.add(frame.scriptId);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const signal = AbortSignal.timeout(1_000);
|
|
314
|
+
await Promise.all([...scriptIds].map(id => waitForScript(model, id, signal)
|
|
315
|
+
.then(script => model.sourceMapManager().sourceMapForClientPromise(script))
|
|
316
|
+
.catch()));
|
|
317
|
+
const binding = devTools.universe.context.get(DevTools.DebuggerWorkspaceBinding);
|
|
318
|
+
// DevTools uses branded types for ScriptId and others. Casting the puppeteer protocol type to the DevTools protocol type is safe.
|
|
319
|
+
return binding.createStackTraceFromProtocolRuntime(rawStackTrace, target);
|
|
320
|
+
}
|
|
321
|
+
// Waits indefinitely for the script so pair it with Promise.race.
|
|
322
|
+
async function waitForScript(model, scriptId, signal) {
|
|
323
|
+
while (true) {
|
|
324
|
+
if (signal.aborted) {
|
|
325
|
+
throw signal.reason;
|
|
326
|
+
}
|
|
327
|
+
const script = model.scriptForId(scriptId);
|
|
328
|
+
if (script) {
|
|
329
|
+
return script;
|
|
330
|
+
}
|
|
331
|
+
await new Promise((resolve, reject) => {
|
|
332
|
+
signal.addEventListener('abort', () => reject(signal.reason), {
|
|
333
|
+
once: true,
|
|
334
|
+
});
|
|
335
|
+
void model
|
|
336
|
+
.once('ParsedScriptSource')
|
|
337
|
+
.then(resolve);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|