diff --git a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html index 546aa58e01f4..86a823bfce31 100644 --- a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html +++ b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html @@ -280,5 +280,7 @@

Do better web tester page

} + + diff --git a/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js b/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js index c8ea19f45f32..de8f5412e3a3 100644 --- a/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js +++ b/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js @@ -49,7 +49,8 @@ class LinkBlockingFirstPaintAudit extends Audit { if (typeof artifact === 'undefined' || artifact.value === -1) { return { rawValue: -1, - debugString: 'TagsBlockingFirstPaint gatherer did not run' + debugString: (artifact && artifact.debugString) || + 'TagsBlockingFirstPaint gatherer did not run' }; } diff --git a/lighthouse-core/gather/driver.js b/lighthouse-core/gather/driver.js index 8b8c28685111..9592e1538c97 100644 --- a/lighthouse-core/gather/driver.js +++ b/lighthouse-core/gather/driver.js @@ -143,17 +143,49 @@ class Driver { (_ => reject(new Error('The asynchronous expression exceeded the allotted time of 60s'))), 60000 ); + + // The `exceptionDetails` provided by the debugger protocol does not contain the useful + // information such as name, message, and stack trace of the error when it's wrapped in a + // promise. Instead, map to a successful object that contains this information. + /* istanbul ignore next */ + const errorWrapper = err => { + err = err || new Error(); + const fallbackMessage = typeof err === 'string' ? err : 'unknown error'; + + resolve({ + __failedInBrowser: true, + name: err.name || 'Error', + message: err.message || fallbackMessage, + stack: err.stack || (new Error()).stack, + }); + }; + this.sendCommand('Runtime.evaluate', { - expression: asyncExpression, + expression: `(function wrapInNativePromise () { + var __nativePromise = window.__nativePromise || Promise; + return new __nativePromise(function (resolve) { + var wrapError = ${errorWrapper.toString()}; + try { + __nativePromise.resolve(${asyncExpression}).then(resolve, wrapError); + } catch (e) { + wrapError(e); + } + }); + }())`, includeCommandLineAPI: true, awaitPromise: true, returnByValue: true }).then(result => { clearTimeout(asyncTimeout); + const value = result.result.value; + if (result.exceptionDetails) { - reject(result.exceptionDetails.exception.value); + // An error occurred before we could even enter our try block, should be *very* rare + reject(new Error('an unknown driver error occurred')); + } if (value.__failedInBrowser) { + reject(Object.assign(new Error(), value)); } else { - resolve(result.result.value); + resolve(value); } }).catch(err => { clearTimeout(asyncTimeout); diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index 814bbce79ea8..83e8f24a63e8 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -90,6 +90,7 @@ class GatherRunner { return driver.assertNoSameOriginServiceWorkerClients(options.url) .then(_ => driver.beginEmulation(options.flags)) .then(_ => driver.enableRuntimeEvents()) + .then(_ => driver.evaluateScriptOnLoad('window.__nativePromise = Promise;')) .then(_ => driver.cleanAndDisableBrowserCaches()) .then(_ => driver.clearDataForOrigin(options.url)); } diff --git a/lighthouse-core/gather/gatherers/accessibility.js b/lighthouse-core/gather/gatherers/accessibility.js index 20292f4b9407..56b01b237033 100644 --- a/lighthouse-core/gather/gatherers/accessibility.js +++ b/lighthouse-core/gather/gatherers/accessibility.js @@ -43,9 +43,13 @@ class Accessibility extends Gatherer { afterPass(options) { const driver = options.driver; + const expression = `(function () { + ${axe}; + return (${runA11yChecks.toString()}()); + })()`; return driver - .evaluateAsync(`${axe};(${runA11yChecks.toString()}())`) + .evaluateAsync(expression) .then(returnedValue => { if (!returnedValue) { this.artifact = Accessibility._errorAccessibility('Unable to parse axe results'); diff --git a/lighthouse-core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js b/lighthouse-core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js index cb0603c19644..bd42536f5d63 100644 --- a/lighthouse-core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +++ b/lighthouse-core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js @@ -53,7 +53,8 @@ function collectTagsThatBlockFirstPaint() { }); resolve(tagList); } catch (e) { - reject(`Unable to gather Scripts/Stylesheets/HTML Imports on the page: ${e.message}`); + const friendly = 'Unable to gather Scripts/Stylesheets/HTML Imports on the page'; + reject(new Error(`${friendly}: ${e.message}`)); } }); } @@ -117,10 +118,10 @@ class TagsBlockingFirstPaint extends Gatherer { .then(artifact => { this.artifact = artifact; }) - .catch(debugString => { + .catch(err => { this.artifact = { value: -1, - debugString + debugString: err.toString() }; }); } diff --git a/lighthouse-core/test/gather/fake-driver.js b/lighthouse-core/test/gather/fake-driver.js index c27a06c40e5e..8ecc48c59a3b 100644 --- a/lighthouse-core/test/gather/fake-driver.js +++ b/lighthouse-core/test/gather/fake-driver.js @@ -38,6 +38,9 @@ module.exports = { enableRuntimeEvents() { return Promise.resolve(); }, + evaluateScriptOnLoad() { + return Promise.resolve(); + }, cleanAndDisableBrowserCaches() {}, clearDataForOrigin() {}, beginTrace() {