fix(pageerror): report correct error message and stack (#1862)

The error stack matches the browser format.
This commit is contained in:
Dmitry Gozman 2020-04-20 11:37:02 -07:00 committed by GitHub
parent 4d8c057d9c
commit 649f37f885
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 40 additions and 16 deletions

View file

@ -91,9 +91,21 @@ export function toConsoleMessageLocation(stackTrace: Protocol.Runtime.StackTrace
} }
export function exceptionToError(exceptionDetails: Protocol.Runtime.ExceptionDetails): Error { export function exceptionToError(exceptionDetails: Protocol.Runtime.ExceptionDetails): Error {
const message = getExceptionMessage(exceptionDetails); const messageWithStack = getExceptionMessage(exceptionDetails);
const lines = messageWithStack.split('\n');
const firstStackTraceLine = lines.findIndex(line => line.startsWith(' at'));
let message = '';
let stack = '';
if (firstStackTraceLine === -1) {
message = messageWithStack;
} else {
message = lines.slice(0, firstStackTraceLine).join('\n');
stack = messageWithStack;
}
const match = message.match(/^[a-zA-Z0-0_]*Error: (.*)$/);
if (match)
message = match[1];
const err = new Error(message); const err = new Error(message);
// Don't report clientside error with a node stack attached err.stack = stack;
err.stack = 'Error: ' + err.message; // Stack is supposed to contain error message as the first line.
return err; return err;
} }

View file

@ -184,7 +184,8 @@ export class FFPage implements PageDelegate {
} }
_onUncaughtError(params: Protocol.Page.uncaughtErrorPayload) { _onUncaughtError(params: Protocol.Page.uncaughtErrorPayload) {
const error = new Error(params.message); const message = params.message.startsWith('Error: ') ? params.message.substring(7) : params.message;
const error = new Error(message);
error.stack = params.stack; error.stack = params.stack;
this._page.emit(Events.Page.PageError, error); this._page.emit(Events.Page.PageError, error);
} }

View file

@ -149,16 +149,13 @@ class Helper {
Helper.removeEventListeners([listener]); Helper.removeEventListeners([listener]);
clearTimeout(eventTimeout); clearTimeout(eventTimeout);
} }
const result = await Promise.race([promise, abortPromise]).then(r => { return await Promise.race([promise, abortPromise]).then(r => {
cleanup(); cleanup();
return r; return r;
}, e => { }, e => {
cleanup(); cleanup();
throw e; throw e;
}); });
if (result instanceof Error)
throw result;
return result;
} }
static async waitWithTimeout<T>(promise: Promise<T>, taskName: string, timeout: number): Promise<T> { static async waitWithTimeout<T>(promise: Promise<T>, taskName: string, timeout: number): Promise<T> {

View file

@ -454,8 +454,15 @@ export class WKPage implements PageDelegate {
return; return;
} }
if (level === 'error' && source === 'javascript') { if (level === 'error' && source === 'javascript') {
const error = new Error(text); const message = text.startsWith('Error: ') ? text.substring(7) : text;
error.stack = 'Error: ' + error.message; // Nullify stack. Stack is supposed to contain error message as the first line. const error = new Error(message);
if (event.message.stackTrace) {
error.stack = event.message.stackTrace.map(callFrame => {
return `${callFrame.functionName}@${callFrame.url}:${callFrame.lineNumber}:${callFrame.columnNumber}`;
}).join('\n');
} else {
error.stack = '';
}
this._page.emit(Events.Page.PageError, error); this._page.emit(Events.Page.PageError, error);
return; return;
} }

View file

@ -11,6 +11,8 @@ function b() {
} }
function c() { function c() {
throw new Error('Fancy error!'); window.e = new Error('Fancy error!');
throw window.e;
} }
//# sourceURL=myscript.js
</script> </script>

View file

@ -493,13 +493,18 @@ describe('Page.exposeFunction', function() {
describe('Page.Events.PageError', function() { describe('Page.Events.PageError', function() {
it('should fire', async({page, server}) => { it('should fire', async({page, server}) => {
let error = null; const [error] = await Promise.all([
page.once('pageerror', e => error = e); page.waitForEvent('pageerror'),
await Promise.all([
page.goto(server.PREFIX + '/error.html'), page.goto(server.PREFIX + '/error.html'),
new Promise(f => page.on('pageerror', f))
]); ]);
expect(error.message).toContain('Fancy'); expect(error.name).toBe('Error');
expect(error.message).toBe('Fancy error!');
let stack = await page.evaluate(() => window.e.stack);
// Note that WebKit does not use sourceURL for some reason and reports the stack of the 'throw' statement
// instead of the Error constructor call.
if (WEBKIT)
stack = stack.replace('14:25', '15:19');
expect(error.stack).toBe(stack);
}); });
}); });