mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
feat: include source-mapped stack trace for uncaught errors (#876)
Note that we replace the existing "pageerror" handler with our new `UncaughtError` handler. The latter has a structured stack trace attached which we can source map via DevTools. This regresses the current functionality slightly, as we lose "pageerror"s for workers and oopif.
This commit is contained in:
1 parent
a08f550b28
commit
ecef712e70
5 files changed
+53
-18
No files matched your search
+6
-12
@@ -14,7 +14,7 @@ import {
|
||||
UniverseManager,
|
||||
urlsEqual,
|
||||
} from './DevtoolsUtils.js';
|
||||
import type {ListenerMap} from './PageCollector.js';
|
||||
import type {ListenerMap, UncaughtError} from './PageCollector.js';
|
||||
import {NetworkCollector, ConsoleCollector} from './PageCollector.js';
|
||||
import {Locator} from './third_party/index.js';
|
||||
import type {DevTools} from './third_party/index.js';
|
||||
@@ -156,14 +156,8 @@ export class McpContext implements Context {
|
||||
console: event => {
|
||||
collect(event);
|
||||
},
|
||||
pageerror: event => {
|
||||
if (event instanceof Error) {
|
||||
collect(event);
|
||||
} else {
|
||||
const error = new Error(`${event}`);
|
||||
error.stack = undefined;
|
||||
collect(error);
|
||||
}
|
||||
uncaughtError: event => {
|
||||
collect(event);
|
||||
},
|
||||
issue: event => {
|
||||
collect(event);
|
||||
@@ -245,7 +239,7 @@ export class McpContext implements Context {
|
||||
|
||||
getConsoleData(
|
||||
includePreservedMessages?: boolean,
|
||||
): Array<ConsoleMessage | Error | DevTools.AggregatedIssue> {
|
||||
): Array<ConsoleMessage | Error | DevTools.AggregatedIssue | UncaughtError> {
|
||||
const page = this.getSelectedPage();
|
||||
return this.#consoleCollector.getData(page, includePreservedMessages);
|
||||
}
|
||||
@@ -255,14 +249,14 @@ export class McpContext implements Context {
|
||||
}
|
||||
|
||||
getConsoleMessageStableId(
|
||||
message: ConsoleMessage | Error | DevTools.AggregatedIssue,
|
||||
message: ConsoleMessage | Error | DevTools.AggregatedIssue | UncaughtError,
|
||||
): number {
|
||||
return this.#consoleCollector.getIdForResource(message);
|
||||
}
|
||||
|
||||
getConsoleMessageById(
|
||||
id: number,
|
||||
): ConsoleMessage | Error | DevTools.AggregatedIssue {
|
||||
): ConsoleMessage | Error | DevTools.AggregatedIssue | UncaughtError {
|
||||
return this.#consoleCollector.getById(this.getSelectedPage(), id);
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -9,6 +9,7 @@ import {IssueFormatter} from './formatters/IssueFormatter.js';
|
||||
import {NetworkFormatter} from './formatters/NetworkFormatter.js';
|
||||
import {SnapshotFormatter} from './formatters/SnapshotFormatter.js';
|
||||
import type {McpContext} from './McpContext.js';
|
||||
import {UncaughtError} from './PageCollector.js';
|
||||
import {DevTools} from './third_party/index.js';
|
||||
import type {
|
||||
ConsoleMessage,
|
||||
@@ -276,8 +277,8 @@ export class McpResponse implements Response {
|
||||
this.#attachedConsoleMessageId,
|
||||
);
|
||||
const consoleMessageStableId = this.#attachedConsoleMessageId;
|
||||
if ('args' in message) {
|
||||
const consoleMessage = message as ConsoleMessage;
|
||||
if ('args' in message || message instanceof UncaughtError) {
|
||||
const consoleMessage = message as ConsoleMessage | UncaughtError;
|
||||
const devTools = context.getDevToolsUniverse();
|
||||
detailedConsoleMessage = await ConsoleFormatter.from(consoleMessage, {
|
||||
id: consoleMessageStableId,
|
||||
@@ -332,8 +333,8 @@ export class McpResponse implements Response {
|
||||
async (item): Promise<ConsoleFormatter | IssueFormatter | null> => {
|
||||
const consoleMessageStableId =
|
||||
context.getConsoleMessageStableId(item);
|
||||
if ('args' in item) {
|
||||
const consoleMessage = item as ConsoleMessage;
|
||||
if ('args' in item || item instanceof UncaughtError) {
|
||||
const consoleMessage = item as ConsoleMessage | UncaughtError;
|
||||
const devTools = context.getDevToolsUniverse();
|
||||
return await ConsoleFormatter.from(consoleMessage, {
|
||||
id: consoleMessageStableId,
|
||||
|
||||
@@ -236,7 +236,7 @@ export class PageCollector<T> {
|
||||
}
|
||||
|
||||
export class ConsoleCollector extends PageCollector<
|
||||
ConsoleMessage | Error | DevTools.AggregatedIssue
|
||||
ConsoleMessage | Error | DevTools.AggregatedIssue | UncaughtError
|
||||
> {
|
||||
#subscribedPages = new WeakMap<Page, PageEventSubscriber>();
|
||||
|
||||
|
||||
@@ -11,6 +11,17 @@ at Iife (main.js:10:2)
|
||||
at <anonymous> (main.js:9:0)
|
||||
`;
|
||||
|
||||
exports[`console > get_console_message > applies source maps to stack traces of uncaught exceptions 1`] = `
|
||||
# test response
|
||||
ID: 1
|
||||
Message: error> Uncaught Error: b00m!
|
||||
### Stack trace
|
||||
at bar (main.js:2:8)
|
||||
at foo (main.js:6:2)
|
||||
at Iife (main.js:10:2)
|
||||
at <anonymous> (main.js:9:0)
|
||||
`;
|
||||
|
||||
exports[`console > get_console_message > issues type > gets issue details with node id parsing 1`] = `
|
||||
# test response
|
||||
ID: 1
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('console', () => {
|
||||
await listConsoleMessages.handler({params: {}}, response, context);
|
||||
const formattedResponse = await response.handle('test', context);
|
||||
const textContent = getTextContent(formattedResponse.content[0]);
|
||||
assert.ok(textContent.includes('msgid=1 [error] undefined (0 args)'));
|
||||
assert.ok(textContent.includes('msgid=1 [error] Uncaught (0 args)'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -257,5 +257,34 @@ describe('console', () => {
|
||||
t.assert.snapshot?.(rawText);
|
||||
});
|
||||
});
|
||||
|
||||
it('applies source maps to stack traces of uncaught exceptions', async t => {
|
||||
server.addRoute('/main.min.js', (_req, res) => {
|
||||
res.setHeader('Content-Type', 'text/javascript');
|
||||
res.statusCode = 200;
|
||||
res.end(`function n(){throw new Error("b00m!")}function o(){n()}(function n(){o()})();
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsIklpZmUiXSwic291cmNlcyI6WyIuL21haW4uanMiXSwic291cmNlc0NvbnRlbnQiOlsiXG5mdW5jdGlvbiBiYXIoKSB7XG4gIHRocm93IG5ldyBFcnJvcignYjAwbSEnKTtcbn1cblxuZnVuY3Rpb24gZm9vKCkge1xuICBiYXIoKTtcbn1cblxuKGZ1bmN0aW9uIElpZmUoKSB7XG4gIGZvbygpO1xufSkoKTtcblxuIl0sIm1hcHBpbmdzIjoiQUFDQSxTQUFTQSxJQUNQLE1BQU0sSUFBSUMsTUFBTSxRQUNsQixDQUVBLFNBQVNDLElBQ1BGLEdBQ0YsRUFFQSxTQUFVRyxJQUNSRCxHQUNELEVBRkQiLCJpZ25vcmVMaXN0IjpbXX0=
|
||||
`);
|
||||
});
|
||||
server.addHtmlRoute(
|
||||
'/index.html',
|
||||
`<script src="${server.getRoute('/main.min.js')}"></script>`,
|
||||
);
|
||||
|
||||
await withMcpContext(async (response, context) => {
|
||||
const page = await context.newPage();
|
||||
await page.goto(server.getRoute('/index.html'));
|
||||
|
||||
await getConsoleMessage.handler(
|
||||
{params: {msgid: 1}},
|
||||
response,
|
||||
context,
|
||||
);
|
||||
const formattedResponse = await response.handle('test', context);
|
||||
const rawText = getTextContent(formattedResponse.content[0]);
|
||||
|
||||
t.assert.snapshot?.(rawText);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user