-
Notifications
You must be signed in to change notification settings - Fork 9.3k
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
Return artifacts from runner and move assets/artifacts saving to cli #1163
Return artifacts from runner and move assets/artifacts saving to cli #1163
Conversation
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.
definitely liking this in the CLI instead of runner
@@ -67,6 +67,7 @@ const cliFlags = yargs | |||
'mobile', | |||
'save-assets', | |||
'save-artifacts', | |||
'exclude-artifacts', |
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.
we should try to pick a flag name with a default false value (so that undefined
or anything falsy will be coerced to it). Maybe just --include-artifacts
?
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.
I guess the question is, what does this flag observably do? At this point it won't affect the html report, so users need to choose json to see the artifacts in the output (and they could use --save-artifacts
for that).
Maybe it makes sense not to have a CLI flag, and for now always clear results.artifacts
in bin.ts. Then future PRs can add flags for their own behavior (like #1130 adding --view
), which can control if the artifacts
property is cleared or not.
That should help with later orthogonality of flags, so we don't have to figure out things like, what if the user sets --view
and --include-artifacts=false
// Process URLs once at a time | ||
const address = addresses.shift(); | ||
if (!address) { | ||
return; | ||
} | ||
|
||
return lighthouse(address, flags, config) | ||
.then((results: Results) => { | ||
if (flags.saveArtifacts) { | ||
return assetSaver.saveArtifacts(results.artifacts).then((_: Object) => results); |
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.
assetSaver.saveArtifacts
is sync, so won't return a promise (feel free to alter here or the function itself :)
}) | ||
.then((results: Results) => { | ||
if (flags.saveAssets) { | ||
return assetSaver.saveAssets({url: results.url}, results.artifacts).then((_:Object) => results); |
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.
assetSaver.saveAssets
returns Promise<undefined>
, so easier to do .then(() => results)
// Run each audit sequentially, the auditResults array has all our fine work | ||
const auditResults = []; | ||
run = run.then(artifacts => config.audits.reduce((chain, audit) => { | ||
return chain.then(_ => { | ||
return Runner._runAudit(audit, artifacts); | ||
}).then(ret => auditResults.push(ret)); | ||
}, Promise.resolve()).then(_ => auditResults)); | ||
}, Promise.resolve()).then(_ => { |
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.
can you re-indent this block (moving config.audits.reduce((chain, audit) => {
from above to its own line) to make it clearer that artifacts
is still in scope?
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.
I updated this to use "for .. of loop" and make those audit runs become a "standard run" (i.e. resolves to artifacts). Hopefully, this will increase readability.
}) | ||
.then((results: Results) => { | ||
if (flags.excludeArtifacts) { | ||
delete results.artifacts; |
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.
doing results.artifacts = undefined;
here instead should be ok. Using delete
tempts slow objects and we're about to do a bunch of processing on the results, and e.g. JSON.stringify
drops undefined properties anyway
} else if (config.auditResults) { | ||
// If there are existing audit results, surface those here. | ||
run = run.then(_ => config.auditResults); | ||
run = run.then(_ => { | ||
return {artifacts: config.artifacts, auditResults: config.auditResults}; |
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.
do you still want the default {}
if there's no config.artifacts
or is undefined
ok in that case?
@@ -119,7 +108,9 @@ class Runner { | |||
|
|||
// Format and aggregate results before returning. | |||
run = run | |||
.then(auditResults => { | |||
.then(auditData => { |
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.
doesn't really matter, but maybe runResults
?
@@ -119,7 +108,9 @@ class Runner { | |||
|
|||
// Format and aggregate results before returning. | |||
run = run | |||
.then(auditResults => { | |||
.then(auditData => { | |||
const artifacts = auditData.artifacts; |
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.
nit: just inline these below rather than declaring separate variables
should also add some simple tests to runner-test.js that artifacts produced by gatherers or passed in on |
88fe221
to
8b47bc4
Compare
Fixed some bugs and coding style. Still working on test cases. |
Added test cases to ensure artifacts is returned when 1. auditResults is generated in Runner.run, 2. existing auditResults is given to Runner.run |
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.
more review :)
(sorry about the rebase)
// Process URLs once at a time | ||
const address = addresses.shift(); | ||
if (!address) { | ||
return; | ||
} | ||
|
||
return lighthouse(address, flags, config) | ||
.then((results: Results) => { | ||
if (flags.saveArtifacts) { |
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.
maybe combine these two saveArtifacts
and saveAssets
handlers into one then
block? (and maybe even pull into a separate function? not sure about that one, though)
return results; | ||
}) | ||
.then((results: Results) => { | ||
results.artifacts = undefined; |
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.
should add a comment explaining why we're doing this. Maybe something simple like // don't include artifacts in printed output by default.
or something?
// Process URLs once at a time | ||
const address = addresses.shift(); | ||
if (!address) { | ||
return; | ||
} | ||
|
||
return lighthouse(address, flags, config) | ||
.then((results: Results) => { |
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.
sorry, will have to rebase after #1141
}); | ||
|
||
return Runner.run({}, {url, config}).then(results => { | ||
assert.ok(results.artifacts !== undefined); |
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.
maybe check that results.artifacts.HTTPS
is still on there? Do we want to be stricter with this check in any other way?
@@ -295,4 +295,56 @@ describe('Runner', () => { | |||
assert.strictEqual(AuditClass.meta.name, auditExpectedName); | |||
}); | |||
}); | |||
|
|||
it('returns artifacts when audits are given', () => { |
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.
maybe something like 'results include artifacts when audits are given'
?
}); | ||
}); | ||
|
||
it('returns artifacts when auditResult is given', () => { |
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.
same thing, something like 'results include artifacts when auditResult is given'
?
return Runner.run(null, {url, config, driverMock}).then(results => { | ||
assert.ok(results.artifacts !== undefined); | ||
}); | ||
}); |
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.
should probably have one more with passes
and audits
in the config file. driverMock
will provide a trace, so could do a pass that records a trace and then an audit that purely requires a trace (maybe user-timings
?). Something like
const config = new Config({
passes: [{
recordTrace: true,
gatherers: []
}],
audits: [
'user-timings'
]
});
and then the result should have a results.artifacts.traces
array with length > 0
That's kind of venturing into integration testing (relying on gather-runner
internally, for one), but good to catch this case because most lighthouse runs are with configs of the passes/audits format. What do you think?
} else if (config.auditResults) { | ||
// If there are existing audit results, surface those here. | ||
run = run.then(_ => config.auditResults); | ||
// Instantiate and return artifacts for consistency. | ||
// Although we can move this upwards to reduce code repetition, this process might be quite |
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.
the // Although...
comment lines probably aren't necessary here (though good for the code reviewer :).
I do think you're right we want to have the same object shape for artifacts
regardless of path through here, so adding the computed artifacts is the right move
4890cac
to
4489d1b
Compare
return Printer.write(results, 'html', filePath).then(() => { | ||
opn(`http://localhost:${port}/reports/${filename}`); | ||
}); | ||
const filename = `${assetSaver.getFilenamePrefix({url})}.report.html`; |
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.
Although this line can be combined with line 284, we need a variable outside of the 'then-chain' to do that. Also, since we no longer use symlinks, the filenames do not need to be the same. And in the future, we may save JSON instead of html.
continue; | ||
} | ||
assert.ok(results.artifacts.hasOwnProperty(method)); | ||
} |
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.
assetSaver needs computedArtifacts to save screenshots.
if (flags.output === Printer.OutputMode[Printer.OutputMode.pretty]) { | ||
Printer.write(results, 'html', filename); | ||
const filename = `${assetSaver.getFilenamePrefix({url})}.report.html`; | ||
return Printer.write(results, 'html', filename); | ||
} |
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.
Fixed a concurrency error here. Previously, promise will resolve to next 'then' before Printer finishes writing. No sure if that matters.
db32b77
to
23a5483
Compare
Thanks for the advice. Ready for review again! @brendankenny PTAL. |
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.
Yup this works well for me.
Only one nit, otherwise I think this is good to go.
Will merge after the nit is addressed. :)
return results; | ||
}) | ||
.then((results: Results) => { | ||
// remove artifacts so users won't get unnecessary artifacts in the report. |
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.
let's just do this last bit in the previous .then()
, since its all related
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.
Edit: I will come back to this after I rebased this branch
Edit: Fixed. Thanks for the advice
Runner now returns artifacts.\nAdded --exclude-aritifacts, default to be true, will delete artifacts after runner returns it\n
… assigning an empty object to it
Now artifacts are always deleted/undeined before passing to Printer.\nNo longer use .then() after assetSaver.saveArtifacts since it's sync.\nChanged audit run in Runner.run to use for .. of loop instead of reduce to increase readability.
also made runner always return instantiated artifacts for consistency
23a5483
to
55764fe
Compare
Ready for review again. PTAL @paulirish. |
config: Object): Promise<undefined> { | ||
|
||
let chromeLauncher: ChromeLauncher; | ||
return initPort(flags) | ||
.then(() => getDebuggableChrome(flags)) | ||
.then(chrome => chromeLauncher = chrome) | ||
.then(() => lighthouse(url, flags, config)) | ||
.then((results: Results) => { | ||
// delte artifacts from result so reports won't include artifacts. |
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.
delte => delete
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.
fixed.
return results; | ||
}) | ||
.then((results: Results) => { | ||
return results; |
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.
i believe you can nuke this entire .then()
now..
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.
Oops. Sorry, wasn't careful enough when checking after rebase. Didn't notice that. Already fixed.
config: Object): Promise<undefined> { | ||
|
||
let chromeLauncher: ChromeLauncher; | ||
return initPort(flags) | ||
.then(() => getDebuggableChrome(flags)) | ||
.then(chrome => chromeLauncher = chrome) | ||
.then(() => lighthouse(url, flags, config)) | ||
.then((results: Results) => { | ||
// delte artifacts from result so reports won't include artifacts. | ||
const artifacts = results.artifacts; |
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.
👍
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.
:)
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.
some small last things. I like this refactor
}); | ||
|
||
return Runner.run({}, {url, config}).then(results => { | ||
if (!results.artifacts instanceof Object) { |
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.
not sure if these instanceof
checks are really necessary. If not an object, it will throw on the property accesses below anyway
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.
Thanks for the advice. Fixed.
assert.strictEqual(results.artifacts.HTTPS.value, true); | ||
|
||
for (const method in computedArtifacts) { | ||
if (!computedArtifacts.hasOwnProperty(method)) { |
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.
could you just do a loop over Object.keys(computedArtifacts)
here to avoid hasOwnProperty
, etc?
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.
Thanks for the advice. Fixed.
}); | ||
|
||
return Runner.run(null, {url, config, driverMock}).then(results => { | ||
if (!results.artifacts instanceof Object) { |
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.
same as above
assert.ok(false); | ||
return; | ||
} | ||
|
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.
no results.artifacts.HTTPS
here?
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.
No sure what you mean here. Do you mean I need to check whether results.artifacts.HTTPS
exists?
If that's the case, do we need to check results.artifacts
as well? And also results.audits
and so on in other test cases?
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.
I mean in the other two tests you check assert.strictEqual(results.artifacts.HTTPS.value, true);
. I'm not sure what the value of results.artifacts.HTTPS.value
will be here, but I believe runner will attempt to run the HTTPS
gatherer with the mock driver supplied, so it seems like it's worth keeping the check in there since this is checking the artifacts coming through
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.
Got it. And added assert.ok(results.artifacts.HTTPS)
to check whether non-computed
artifacts attributes are returned.
return; | ||
} | ||
|
||
for (const method in computedArtifacts) { |
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.
same as above
}); | ||
|
||
return Runner.run(null, {url, config, driverMock}).then(results => { | ||
if (!results.artifacts instanceof Object) { |
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.
and same here and the for..in loop below too :)
.then((results: Results) => { | ||
// delete artifacts from result so reports won't include artifacts. | ||
const artifacts = results.artifacts; | ||
results.artifacts = undefined; |
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.
style nit: maybe add a line break after this line to separate the two actions?
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.
Thanks. Fixed.
return Runner.run({}, {url, config}).then(results => { | ||
assert.strictEqual(results.artifacts.HTTPS.value, true); | ||
|
||
for (const method in Object.keys(computedArtifacts)) { |
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.
I think you want for...of here
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.
What a dumb mistake : (
Fixed.
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.
Really like how this change turned out!
thanks for all your patience :) |
Moving assets/artifacts saving to cli will allow us to host trace files in the future #616 (with #1130). Because the server needs to know where those trace files are but it's unnatural to include those file paths in the results returned by runner.
This will also make it possible to compare timeline data in Project Performance Experiment #1143.
Taking over from #1115