Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/DevtoolsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,23 @@ export class SymbolizedError {
let cause: SymbolizedError | undefined;
if (opts.resolvedCauseForTesting) {
cause = opts.resolvedCauseForTesting;
} else if (opts.details.exception) {
try {
const causeRemoteObj = await SymbolizedError.#lookupCause(
opts.devTools,
opts.details.exception,
opts.targetId,
);
if (causeRemoteObj) {
cause = await SymbolizedError.fromError({
devTools: opts.devTools,
error: causeRemoteObj,
targetId: opts.targetId,
});
}
} catch {
// Ignore
}
}
return new SymbolizedError(message, stackTrace, cause);
}
Expand Down Expand Up @@ -355,6 +372,30 @@ export class SymbolizedError {
);
}

static async #lookupCause(
devTools: TargetUniverse | undefined,
error: Protocol.Runtime.RemoteObject,
targetId: string,
): Promise<Protocol.Runtime.RemoteObject | null> {
if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
return null;
}

const targetManager = devTools.universe.context.get(DevTools.TargetManager);
const target = targetId
? targetManager.targetById(targetId) || devTools.target
: devTools.target;

const properties = await target.runtimeAgent().invoke_getProperties({
objectId: error.objectId as DevTools.Protocol.Runtime.RemoteObjectId,
});
if (properties.getError()) {
return null;
}

return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
}

static createForTesting(
message: string,
stackTrace?: DevTools.StackTrace.StackTrace.StackTrace,
Expand Down
41 changes: 41 additions & 0 deletions tests/tools/console.test.js.snapshot
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
exports[`console > get_console_message > applies source maps to stack traces of Error object (with cause) console.log arguments 1`] = `
# test response
ID: 1
Message: log> foo failed JSHandle@error
### Arguments
Arg #0: foo failed
Arg #1: Error: bar failed
at foo (main.js:10:11)
at Iife (main.js:16:5)
at <anonymous> (main.js:14:1)
Caused by: Error: b00m!
at bar (main.js:3:9)
at foo (main.js:8:5)
at Iife (main.js:16:5)
at <anonymous> (main.js:14:1)
Note: line and column numbers use 1-based indexing
### Stack trace
at Iife (main.js:18:13)
at <anonymous> (main.js:14:1)
Note: line and column numbers use 1-based indexing
`;

exports[`console > get_console_message > applies source maps to stack traces of Error object console.log arguments 1`] = `
# test response
ID: 1
Expand Down Expand Up @@ -42,6 +64,25 @@ at <anonymous> (main.js:10:1)
Note: line and column numbers use 1-based indexing
`;

exports[`console > get_console_message > applies source maps to stack traces of uncaught exceptions with cause 1`] = `
# test response
ID: 1
Message: error> Uncaught Error: foo failed
### Stack trace
at Iife (main.js:18:11)
at <anonymous> (main.js:14:1)
Caused by: Error: bar failed
at foo (main.js:10:11)
at Iife (main.js:16:5)
at <anonymous> (main.js:14:1)
Caused by: Error: b00m!
at bar (main.js:3:9)
at foo (main.js:8:5)
at Iife (main.js:16:5)
at <anonymous> (main.js:14:1)
Note: line and column numbers use 1-based indexing
`;

exports[`console > get_console_message > issues type > gets issue details with node id parsing 1`] = `
# test response
ID: 1
Expand Down
58 changes: 58 additions & 0 deletions tests/tools/console.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,5 +315,63 @@ describe('console', () => {
t.assert.snapshot?.(rawText);
});
});

it('applies source maps to stack traces of uncaught exceptions with cause', async t => {
server.addRoute('/main.min.js', (_req, res) => {
res.setHeader('Content-Type', 'text/javascript');
res.statusCode = 200;
res.end(`function r(){throw new Error("b00m!")}function o(){try{r()}catch(r){throw new Error("bar failed",{cause:r})}}(function r(){try{o()}catch(r){throw new Error("foo failed",{cause:r})}})();
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsImUiLCJjYXVzZSIsIklpZmUiXSwic291cmNlcyI6WyIuL21haW4uanMiXSwic291cmNlc0NvbnRlbnQiOlsiXG5mdW5jdGlvbiBiYXIoKSB7XG4gIHRocm93IG5ldyBFcnJvcignYjAwbSEnKTtcbn1cblxuZnVuY3Rpb24gZm9vKCkge1xuICB0cnkge1xuICAgIGJhcigpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdiYXIgZmFpbGVkJywgeyBjYXVzZTogZSB9KTtcbiAgfVxufVxuXG4oZnVuY3Rpb24gSWlmZSgpIHtcbiAgdHJ5IHtcbiAgICBmb28oKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignZm9vIGZhaWxlZCcsIHsgY2F1c2U6IGUgfSk7XG4gIH1cbn0pKCk7XG5cbiJdLCJtYXBwaW5ncyI6IkFBQ0EsU0FBU0EsSUFDUCxNQUFNLElBQUlDLE1BQU0sUUFDbEIsQ0FFQSxTQUFTQyxJQUNQLElBQ0VGLEdBQ0YsQ0FBRSxNQUFPRyxHQUNQLE1BQU0sSUFBSUYsTUFBTSxhQUFjLENBQUVHLE1BQU9ELEdBQ3pDLENBQ0YsRUFFQSxTQUFVRSxJQUNSLElBQ0VILEdBQ0YsQ0FBRSxNQUFPQyxHQUNQLE1BQU0sSUFBSUYsTUFBTSxhQUFjLENBQUVHLE1BQU9ELEdBQ3pDLENBQ0QsRUFORCIsImlnbm9yZUxpc3QiOltdfQ==
`);
});
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);
});
});

it('applies source maps to stack traces of Error object (with cause) console.log arguments', async t => {
server.addRoute('/main.min.js', (_req, res) => {
res.setHeader('Content-Type', 'text/javascript');
res.statusCode = 200;
res.end(`function o(){throw new Error("b00m!")}function r(){try{o()}catch(o){throw new Error("bar failed",{cause:o})}}(function o(){try{r()}catch(o){console.log("foo failed",o)}})();
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiYXIiLCJFcnJvciIsImZvbyIsImUiLCJjYXVzZSIsIklpZmUiLCJjb25zb2xlIiwibG9nIl0sInNvdXJjZXMiOlsiLi9tYWluLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIlxuZnVuY3Rpb24gYmFyKCkge1xuICB0aHJvdyBuZXcgRXJyb3IoJ2IwMG0hJyk7XG59XG5cbmZ1bmN0aW9uIGZvbygpIHtcbiAgdHJ5IHtcbiAgICBiYXIoKTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignYmFyIGZhaWxlZCcsIHsgY2F1c2U6IGUgfSk7XG4gIH1cbn1cblxuKGZ1bmN0aW9uIElpZmUoKSB7XG4gIHRyeSB7XG4gICAgZm9vKCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zb2xlLmxvZygnZm9vIGZhaWxlZCcsIGUpO1xuICB9XG59KSgpO1xuXG4iXSwibWFwcGluZ3MiOiJBQUNBLFNBQVNBLElBQ1AsTUFBTSxJQUFJQyxNQUFNLFFBQ2xCLENBRUEsU0FBU0MsSUFDUCxJQUNFRixHQUNGLENBQUUsTUFBT0csR0FDUCxNQUFNLElBQUlGLE1BQU0sYUFBYyxDQUFFRyxNQUFPRCxHQUN6QyxDQUNGLEVBRUEsU0FBVUUsSUFDUixJQUNFSCxHQUNGLENBQUUsTUFBT0MsR0FDUEcsUUFBUUMsSUFBSSxhQUFjSixFQUM1QixDQUNELEVBTkQiLCJpZ25vcmVMaXN0IjpbXX0=
`);
});
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);
});
});
});
});