-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core(gather-runner): add PageLoadError base artifact #9236
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -408,28 +408,6 @@ class GatherRunner { | |
log.timeEnd(apStatus); | ||
} | ||
|
||
/** | ||
* Generate a set of artfiacts for the given pass as if all the gatherers | ||
* failed with the given pageLoadError. | ||
* @param {LH.Gatherer.PassContext} passContext | ||
* @param {LHError} pageLoadError | ||
* @return {{pageLoadError: LHError, artifacts: Partial<LH.GathererArtifacts>}} | ||
*/ | ||
static generatePageLoadErrorArtifacts(passContext, pageLoadError) { | ||
/** @type {Partial<Record<keyof LH.GathererArtifacts, LHError>>} */ | ||
const errorArtifacts = {}; | ||
for (const gathererDefn of passContext.passConfig.gatherers) { | ||
const gatherer = gathererDefn.instance; | ||
errorArtifacts[gatherer.name] = pageLoadError; | ||
} | ||
|
||
return { | ||
pageLoadError, | ||
// @ts-ignore - TODO(bckenny): figure out how to usefully type errored artifacts. | ||
artifacts: errorArtifacts, | ||
}; | ||
} | ||
|
||
/** | ||
* Takes the results of each gatherer phase for each gatherer and uses the | ||
* last produced value (that's not undefined) as the artifact for that | ||
|
@@ -495,6 +473,7 @@ class GatherRunner { | |
settings: options.settings, | ||
URL: {requestedUrl: options.requestedUrl, finalUrl: options.requestedUrl}, | ||
Timing: [], | ||
PageLoadError: null, | ||
}; | ||
} | ||
|
||
|
@@ -594,21 +573,22 @@ class GatherRunner { | |
Object.assign(artifacts, passResults.artifacts); | ||
|
||
// If we encountered a pageLoadError, don't try to keep loading the page in future passes. | ||
if (passResults.pageLoadError) break; | ||
if (passResults.pageLoadError) { | ||
baseArtifacts.PageLoadError = passResults.pageLoadError; | ||
break; | ||
} | ||
|
||
if (isFirstPass) { | ||
await GatherRunner.populateBaseArtifacts(passContext); | ||
isFirstPass = false; | ||
} | ||
} | ||
|
||
await GatherRunner.disposeDriver(driver, options); | ||
GatherRunner.finalizeBaseArtifacts(baseArtifacts); | ||
return /** @type {LH.Artifacts} */ ({...baseArtifacts, ...artifacts}); // Cast to drop Partial<>. | ||
} catch (err) { | ||
// cleanup on error | ||
} finally { | ||
// Clean up regardless of error. | ||
GatherRunner.disposeDriver(driver, options); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the simplification, but now we can't await it in the success case :/ maybe it doesn't matter and we can add a comment calling out why it doesn't matter? worth noting that it does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good catch, that wasn't intentional. I wonder how bad it would be to await the error case... Presumably it was so errors in disposing didn't overwrite the originating error, which probably often go hand in hand? Kind of ridiculous we can't express what we mean here. I could also just change it back, it's not a huge win. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah maybe just change it back with a comment so we don't forget this again next time :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This pattern originally popped up way back in #639 (in the course of @wardpeet adding the multiple tabs check!), though originally we didn't wait for disconnect in the failure or success cases. Looking at #2359, where the successful path was integrated into the promise chain, I'm half convinced we should be doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm with you on |
||
throw err; | ||
} | ||
} | ||
|
||
|
@@ -660,14 +640,15 @@ class GatherRunner { | |
// Disable throttling so the afterPass analysis isn't throttled | ||
await driver.setThrottling(passContext.settings, {useThrottling: false}); | ||
|
||
// If there were any load errors, treat all gatherers as if they errored. | ||
// In case of load error, save log and trace with an error prefix, return no artifacts for this pass. | ||
const pageLoadError = GatherRunner.getPageLoadError(passContext, loadData, possibleNavError); | ||
if (pageLoadError) { | ||
log.error('GatherRunner', pageLoadError.friendlyMessage, passContext.url); | ||
passContext.LighthouseRunWarnings.push(pageLoadError.friendlyMessage); | ||
GatherRunner._addLoadDataToBaseArtifacts(passContext, loadData, | ||
`pageLoadError-${passConfig.passName}`); | ||
return GatherRunner.generatePageLoadErrorArtifacts(passContext, pageLoadError); | ||
|
||
return {artifacts: {}, pageLoadError}; | ||
} | ||
|
||
// If no error, save devtoolsLog and trace. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -631,13 +631,18 @@ describe('Runner', () => { | |
}); | ||
}); | ||
|
||
it('includes a top-level runtimeError when a gatherer throws one', async () => { | ||
describe('lhr.runtimeError', () => { | ||
const NO_FCP = LHError.errors.NO_FCP; | ||
class RuntimeErrorGatherer extends Gatherer { | ||
afterPass() { | ||
throw new LHError(NO_FCP); | ||
} | ||
} | ||
class RuntimeError2Gatherer extends Gatherer { | ||
afterPass() { | ||
throw new LHError(LHError.errors.NO_SCREENSHOTS); | ||
} | ||
} | ||
class WarningAudit extends Audit { | ||
static get meta() { | ||
return { | ||
|
@@ -652,19 +657,55 @@ describe('Runner', () => { | |
} | ||
} | ||
|
||
const config = new Config({ | ||
passes: [{gatherers: [RuntimeErrorGatherer]}], | ||
const configJson = { | ||
passes: [ | ||
{gatherers: [RuntimeErrorGatherer]}, | ||
{gatherers: [RuntimeError2Gatherer], passName: 'second'}, | ||
], | ||
audits: [WarningAudit], | ||
}); | ||
const {lhr} = await Runner.run(null, {url: 'https://example.com/', config, driverMock}); | ||
}; | ||
|
||
// Audit error included the runtimeError | ||
assert.strictEqual(lhr.audits['test-audit'].scoreDisplayMode, 'error'); | ||
assert.ok(lhr.audits['test-audit'].errorMessage.includes(NO_FCP.code)); | ||
// And it bubbled up to the runtimeError. | ||
assert.strictEqual(lhr.runtimeError.code, NO_FCP.code); | ||
expect(lhr.runtimeError.message) | ||
.toBeDisplayString(/Something .*\(NO_FCP\)/); | ||
it('includes a top-level runtimeError when a gatherer throws one', async () => { | ||
const config = new Config(configJson); | ||
const {lhr} = await Runner.run(null, {url: 'https://example.com/', config, driverMock}); | ||
|
||
// Audit error included the runtimeError | ||
expect(lhr.audits['test-audit'].scoreDisplayMode).toEqual('error'); | ||
expect(lhr.audits['test-audit'].errorMessage).toEqual(expect.stringContaining(NO_FCP.code)); | ||
|
||
// And it bubbled up to the runtimeError. | ||
expect(lhr.runtimeError.code).toEqual(NO_FCP.code); | ||
expect(lhr.runtimeError.message).toBeDisplayString(/Something .*\(NO_FCP\)/); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these are unchanged, just nested now to share the test gatherer/audit/config with the following test (and |
||
}); | ||
|
||
it('includes a pageLoadError runtimeError over any gatherer runtimeErrors', async () => { | ||
const url = 'https://www.reddit.com/r/nba'; | ||
let firstLoad = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yeah, I'm sorry! |
||
const errorDriverMock = Object.assign({}, driverMock, { | ||
online: true, | ||
// Loads the page successfully in the first pass, fails with PAGE_HUNG in the second. | ||
async gotoURL(url) { | ||
if (url.includes('blank')) return null; | ||
if (firstLoad) { | ||
firstLoad = false; | ||
return url; | ||
} else { | ||
throw new LHError(LHError.errors.PAGE_HUNG); | ||
} | ||
}, | ||
}); | ||
|
||
const config = new Config(configJson); | ||
const {lhr} = await Runner.run(null, {url, config, driverMock: errorDriverMock}); | ||
|
||
// Audit error still includes the gatherer runtimeError. | ||
expect(lhr.audits['test-audit'].scoreDisplayMode).toEqual('error'); | ||
expect(lhr.audits['test-audit'].errorMessage).toEqual(expect.stringContaining(NO_FCP.code)); | ||
|
||
// But top-level runtimeError is the pageLoadError. | ||
expect(lhr.runtimeError.code).toEqual(LHError.errors.PAGE_HUNG.code); | ||
expect(lhr.runtimeError.message).toBeDisplayString(/because the page stopped responding/); | ||
}); | ||
}); | ||
|
||
it('localized errors thrown from driver', async () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aw, no more
artfiacts
😢