Skip to content
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): don't save trace on pass with pageLoadError #9198

Merged
merged 4 commits into from
Jun 19, 2019
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
3 changes: 1 addition & 2 deletions lighthouse-cli/test/smokehouse/seo/expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,7 @@ module.exports = [
},
audits: {
'http-status-code': {
score: 0,
displayValue: '403',
score: null,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this audit is useless at this point now, candidate for deletion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this audit is useless at this point now, candidate for deletion?

I'd say yes (are there cases when non 200 status codes shouldn't be a runtimeError?)

},
'viewport': {
score: null,
Expand Down
24 changes: 18 additions & 6 deletions lighthouse-core/gather/gather-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,18 @@ class GatherRunner {
return !settings.disableStorageReset && passConfig.recordTrace && passConfig.useThrottling;
}

/**
* Save the devtoolsLog and trace (if applicable) to baseArtifacts.
* @param {LH.Gatherer.PassContext} passContext
* @param {LH.Gatherer.LoadData} loadData
* @param {string} passName
*/
static _addLoadDataToBaseArtifacts(passContext, loadData, passName) {
const baseArtifacts = passContext.baseArtifacts;
baseArtifacts.devtoolsLogs[passName] = loadData.devtoolsLog;
if (loadData.trace) baseArtifacts.traces[passName] = loadData.trace;
}

/**
* Starting from about:blank, load the page and run gatherers for this pass.
* @param {LH.Gatherer.PassContext} passContext
Expand All @@ -648,20 +660,20 @@ class GatherRunner {
// Disable throttling so the afterPass analysis isn't throttled
await driver.setThrottling(passContext.settings, {useThrottling: false});

// Save devtoolsLog and trace.
const baseArtifacts = passContext.baseArtifacts;
baseArtifacts.devtoolsLogs[passConfig.passName] = loadData.devtoolsLog;
if (loadData.trace) baseArtifacts.traces[passConfig.passName] = loadData.trace;

// If there were any load errors, treat all gatherers as if they errored.
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);
}

// If no error, run `afterPass()` on gatherers and return collected artifacts.
// If no error, save devtoolsLog and trace.
GatherRunner._addLoadDataToBaseArtifacts(passContext, loadData, passConfig.passName);

// Run `afterPass()` on gatherers and return collected artifacts.
await GatherRunner.afterPass(passContext, loadData, gathererResults);
return GatherRunner.collectArtifacts(gathererResults);
}
Expand Down
79 changes: 79 additions & 0 deletions lighthouse-core/test/gather/gather-runner-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,85 @@ describe('GatherRunner', function() {
});
});

it('saves trace and devtoolsLog with error prefix when there was a runtime error', async () => {
const requestedUrl = 'https://example.com';
const driver = Object.assign({}, fakeDriver, {
// resolved URL here does not match any request in the network records, causing a runtime error.
gotoURL: async _ => requestedUrl,
online: true,
});

const config = new Config({
passes: [{
passName: 'firstPass',
recordTrace: true,
gatherers: [{instance: new TestGatherer()}],
}],
});
const options = {driver, requestedUrl, settings: config.settings};
const artifacts = await GatherRunner.run(config.passes, options);

expect(artifacts.TestGatherer.code).toEqual('NO_DOCUMENT_REQUEST');

// The only loadData available should be prefixed with `pageLoadError-`.
expect(Object.keys(artifacts.traces)).toEqual(['pageLoadError-firstPass']);
expect(Object.keys(artifacts.devtoolsLogs)).toEqual(['pageLoadError-firstPass']);
});

it('does not run additional passes after a runtime error', async () => {
const t1 = new (class Test1 extends TestGatherer {})();
const t2 = new (class Test2 extends TestGatherer {})();
const t3 = new (class Test3 extends TestGatherer {})();
const config = new Config({
passes: [{
passName: 'firstPass',
recordTrace: true,
gatherers: [{instance: t1}],
}, {
passName: 'secondPass',
recordTrace: true,
gatherers: [{instance: t2}],
}, {
passName: 'thirdPass',
recordTrace: true,
gatherers: [{instance: t3}],
}],
});

const requestedUrl = 'https://www.reddit.com/r/nba';
let firstLoad = true;
const driver = Object.assign({}, fakeDriver, {
// Loads the page successfully in the first pass, fails with NO_FCP in the second.
async gotoURL(url) {
if (url.includes('blank')) return null;
if (firstLoad) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you feel about jest.fn()?

IMO, it would be a bit easier to read our mock setups if we constructed them consistently with the creators. Right now we kinda have to figure out each situation on a case-by-case basis

jest.fn()
  .mockResolvedValueOnce(requestedUrl)
  .mockRejectedValueOnce(new LHError(LHError.errors.NO_FCP));

could go whole hog if we wanted

const gotoBlankFn = jest.fn().mockResolvedValue(null);
const gotoPageURLFn = jest.fn()
  .mockResolvedValueOnce(requestedUrl)
  .mockRejectedValueOnce(new LHError(LHError.errors.NO_FCP));
const gotoURL = url => url.includes('blank') ? gotoBlankFn() : gotoPageUrlFn();
const driver = Object.assign({}, fakeDriver, {gotoURL});

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you feel about jest.fn()?

I like it sometimes and have no problem with it, but in this case no matter what I try the readability comes out slightly worse (the url.includes('blank') really gums up the works). I think it reads ok for this one.

firstLoad = false;
return requestedUrl;
} else {
throw new LHError(LHError.errors.NO_FCP);
}
},
online: true,
});
const options = {driver, requestedUrl, settings: config.settings};
const artifacts = await GatherRunner.run(config.passes, options);

// t1.pass() and t2.pass() called; t3.pass(), after the error, was not.
expect(t1.called).toBe(true);
expect(t2.called).toBe(true);
expect(t3.called).toBe(false);

// But only t1 has a valid artifact, t2 has an error artifact, and t3 never ran.
expect(artifacts.Test1).toBe('MyArtifact');
expect(artifacts.Test2).toBeInstanceOf(LHError);
expect(artifacts.Test2.code).toEqual('NO_FCP');
expect(artifacts.Test3).toBeUndefined();

// firstPass has a saved trace and devtoolsLog, secondPass has an error trace and log.
expect(Object.keys(artifacts.traces)).toEqual(['firstPass', 'pageLoadError-secondPass']);
expect(Object.keys(artifacts.devtoolsLogs)).toEqual(['firstPass', 'pageLoadError-secondPass']);
});

describe('#getNetworkError', () => {
it('passes when the page is loaded', () => {
const url = 'http://the-page.com';
Expand Down