diff --git a/lib/helper.js b/lib/helper.js index 0c5f87ff3ee..d5400818015 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -19,6 +19,39 @@ const debugError = require('debug')(`puppeteer:error`); /** @type {?Map} */ let apiCoverage = null; +/** + * @param {!Object} classType + * @param {string=} publicName + */ +function traceAPICoverage(classType, publicName) { + if (!apiCoverage) + return; + + let className = publicName || classType.prototype.constructor.name; + className = className.substring(0, 1).toLowerCase() + className.substring(1); + for (const methodName of Reflect.ownKeys(classType.prototype)) { + const method = Reflect.get(classType.prototype, methodName); + if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function') + continue; + apiCoverage.set(`${className}.${methodName}`, false); + Reflect.set(classType.prototype, methodName, function(...args) { + apiCoverage.set(`${className}.${methodName}`, true); + return method.call(this, ...args); + }); + } + + if (classType.Events) { + for (const event of Object.values(classType.Events)) + apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false); + const method = Reflect.get(classType.prototype, 'emit'); + Reflect.set(classType.prototype, 'emit', function(event, ...args) { + if (this.listenerCount(event)) + apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true); + return method.call(this, event, ...args); + }); + } +} + class Helper { /** * @param {Function|string} fun @@ -103,32 +136,23 @@ class Helper { * @param {string=} publicName */ static tracePublicAPI(classType, publicName) { - if (!apiCoverage) - return; - - let className = publicName || classType.prototype.constructor.name; - className = className.substring(0, 1).toLowerCase() + className.substring(1); for (const methodName of Reflect.ownKeys(classType.prototype)) { const method = Reflect.get(classType.prototype, methodName); - if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function') + if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction') continue; - apiCoverage.set(`${className}.${methodName}`, false); Reflect.set(classType.prototype, methodName, function(...args) { - apiCoverage.set(`${className}.${methodName}`, true); - return method.call(this, ...args); + const syncStack = new Error(); + return method.call(this, ...args).catch(e => { + const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1); + const clientStack = stack.substring(stack.indexOf('\n')); + if (!e.stack.includes(clientStack)) + e.stack += '\n -- ASYNC --\n' + stack; + throw e; + }); }); } - if (classType.Events) { - for (const event of Object.values(classType.Events)) - apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false); - const method = Reflect.get(classType.prototype, 'emit'); - Reflect.set(classType.prototype, 'emit', function(event, ...args) { - if (this.listenerCount(event)) - apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true); - return method.call(this, event, ...args); - }); - } + traceAPICoverage(classType, publicName); } /** diff --git a/test/page.spec.js b/test/page.spec.js index 553d96973db..74dfb93a1a5 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -65,6 +65,25 @@ module.exports.addTests = function({testRunner, expect, headless}) { }); }); + let asyncawait = true; + try { + new Function('async function foo() {await 1}'); + } catch (e) { + asyncawait = false; + } + (asyncawait ? describe : xdescribe)('Async stacks', () => { + it('should work', async({page, server}) => { + server.setRoute('/empty.html', (req, res) => { + res.statusCode = 204; + res.end(); + }); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).not.toBe(null); + expect(error.message).toContain('net::ERR_ABORTED'); + }); + }); + describe('Page.Events.error', function() { it('should throw when page crashes', async({page}) => { let error = null;