fix: save artifacts and assets in output-path (#1601)
This commit is contained in:
Родитель
6d4c05bd87
Коммит
76fb69e4b6
|
@ -16,6 +16,7 @@ last-run-results.html
|
|||
*.trace.json
|
||||
*.screenshots.html
|
||||
*.report.html
|
||||
*.artifacts.log
|
||||
|
||||
closure-error.log
|
||||
|
||||
|
|
|
@ -249,6 +249,33 @@ function handleError(err: LighthouseError) {
|
|||
}
|
||||
}
|
||||
|
||||
function saveResults(results: Results,
|
||||
artifacts: Object,
|
||||
flags: {output: any, outputPath: string, saveArtifacts: boolean, saveAssets: boolean}) {
|
||||
let promise = Promise.resolve(results);
|
||||
const cwd = process.cwd();
|
||||
// Use the output path as the prefix for all generated files.
|
||||
// If no output path is set, generate a file prefix using the URL and date.
|
||||
const configuredPath = !flags.outputPath || flags.outputPath === 'stdout' ?
|
||||
assetSaver.getFilenamePrefix(results) :
|
||||
flags.outputPath.replace(/\.\w{2,4}$/, '');
|
||||
const resolvedPath = path.resolve(cwd, configuredPath);
|
||||
|
||||
if (flags.saveArtifacts) {
|
||||
assetSaver.saveArtifacts(artifacts, resolvedPath);
|
||||
}
|
||||
|
||||
if (flags.saveAssets) {
|
||||
promise = promise.then(_ => assetSaver.saveAssets(artifacts, results.audits, resolvedPath));
|
||||
}
|
||||
|
||||
if (flags.output === Printer.OutputMode[Printer.OutputMode.pretty]) {
|
||||
promise = promise.then(_ => Printer.write(results, 'html', `${resolvedPath}.report.html`));
|
||||
}
|
||||
|
||||
return promise.then(_ => Printer.write(results, flags.output, flags.outputPath));
|
||||
}
|
||||
|
||||
function runLighthouse(url: string,
|
||||
flags: {port: number, skipAutolaunch: boolean, selectChrome: boolean, output: any,
|
||||
outputPath: string, interactive: boolean, saveArtifacts: boolean, saveAssets: boolean},
|
||||
|
@ -260,30 +287,16 @@ function runLighthouse(url: string,
|
|||
.then(chrome => chromeLauncher = chrome)
|
||||
.then(() => lighthouse(url, flags, config))
|
||||
.then((results: Results) => {
|
||||
// delete artifacts from result so reports won't include artifacts.
|
||||
// remove artifacts from result so reports won't include artifacts.
|
||||
const artifacts = results.artifacts;
|
||||
results.artifacts = undefined;
|
||||
|
||||
if (flags.saveArtifacts) {
|
||||
assetSaver.saveArtifacts(artifacts);
|
||||
}
|
||||
if (flags.saveAssets) {
|
||||
return assetSaver.saveAssets(artifacts, results).then(() => results);
|
||||
}
|
||||
return results;
|
||||
})
|
||||
.then((results: Results) => Printer.write(results, flags.output, flags.outputPath))
|
||||
.then((results: Results) => {
|
||||
if (flags.output === Printer.OutputMode[Printer.OutputMode.pretty]) {
|
||||
const filename = `${assetSaver.getFilenamePrefix(results)}.report.html`;
|
||||
return Printer.write(results, 'html', filename);
|
||||
}
|
||||
return results;
|
||||
})
|
||||
.then((results: Results) => {
|
||||
let promise = saveResults(results, artifacts, flags);
|
||||
if (flags.interactive) {
|
||||
return performanceXServer.hostExperiment({url, flags, config}, results);
|
||||
promise = promise.then(() => performanceXServer.hostExperiment({url, flags, config}, results));
|
||||
}
|
||||
|
||||
return promise;
|
||||
})
|
||||
.then(() => chromeLauncher.kill())
|
||||
.catch(err => {
|
||||
|
|
|
@ -48,13 +48,12 @@ function getFilenamePrefix(results) {
|
|||
/**
|
||||
* Generate basic HTML page of screenshot filmstrip
|
||||
* @param {!Array<{timestamp: number, datauri: string}>} screenshots
|
||||
* @param {!Results} results
|
||||
* @return {!string}
|
||||
*/
|
||||
function screenshotDump(screenshots, results) {
|
||||
function screenshotDump(screenshots) {
|
||||
return `
|
||||
<!doctype html>
|
||||
<title>screenshots ${getFilenamePrefix(results)}</title>
|
||||
<title>screenshots</title>
|
||||
<style>
|
||||
html {
|
||||
overflow-x: scroll;
|
||||
|
@ -89,26 +88,27 @@ img {
|
|||
}
|
||||
|
||||
/**
|
||||
* Save entire artifacts object to a single stringified file
|
||||
* Save entire artifacts object to a single stringified file located at
|
||||
* pathWithBasename + .artifacts.log
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {!string} artifactsFilename
|
||||
* @param {string} pathWithBasename
|
||||
*/
|
||||
// Set to ignore because testing it would imply testing fs, which isn't strictly necessary.
|
||||
/* istanbul ignore next */
|
||||
function saveArtifacts(artifacts, artifactsFilename) {
|
||||
artifactsFilename = artifactsFilename || 'artifacts.log';
|
||||
function saveArtifacts(artifacts, pathWithBasename) {
|
||||
const fullPath = `${pathWithBasename}.artifacts.log`;
|
||||
// The networkRecords artifacts have circular references
|
||||
fs.writeFileSync(artifactsFilename, stringifySafe(artifacts));
|
||||
log.log('artifacts file saved to disk', artifactsFilename);
|
||||
fs.writeFileSync(fullPath, stringifySafe(artifacts));
|
||||
log.log('artifacts file saved to disk', fullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter traces and extract screenshots to prepare for saving.
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {!Results} results
|
||||
* @param {!Audits} audits
|
||||
* @return {!Promise<!Array<{traceData: !Object, html: string}>>}
|
||||
*/
|
||||
function prepareAssets(artifacts, results) {
|
||||
function prepareAssets(artifacts, audits) {
|
||||
const passNames = Object.keys(artifacts.traces);
|
||||
const assets = [];
|
||||
|
||||
|
@ -118,10 +118,10 @@ function prepareAssets(artifacts, results) {
|
|||
return chain.then(_ => artifacts.requestScreenshots(trace))
|
||||
.then(screenshots => {
|
||||
const traceData = Object.assign({}, trace);
|
||||
const html = screenshotDump(screenshots, results);
|
||||
const html = screenshotDump(screenshots);
|
||||
|
||||
if (results && results.audits) {
|
||||
const evts = new Metrics(traceData.traceEvents, results.audits).generateFakeEvents();
|
||||
if (audits) {
|
||||
const evts = new Metrics(traceData.traceEvents, audits).generateFakeEvents();
|
||||
traceData.traceEvents.push(...evts);
|
||||
}
|
||||
assets.push({
|
||||
|
@ -136,19 +136,21 @@ function prepareAssets(artifacts, results) {
|
|||
/**
|
||||
* Writes trace(s) and associated screenshot(s) to disk.
|
||||
* @param {!Artifacts} artifacts
|
||||
* @param {!Results} results
|
||||
* @param {!Audits} audits
|
||||
* @param {string} pathWithBasename
|
||||
* @return {!Promise}
|
||||
*/
|
||||
function saveAssets(artifacts, results) {
|
||||
return prepareAssets(artifacts, results).then(assets => {
|
||||
function saveAssets(artifacts, audits, pathWithBasename) {
|
||||
return prepareAssets(artifacts, audits).then(assets => {
|
||||
assets.forEach((data, index) => {
|
||||
const filenamePrefix = getFilenamePrefix(results);
|
||||
const traceData = data.traceData;
|
||||
fs.writeFileSync(`${filenamePrefix}-${index}.trace.json`, JSON.stringify(traceData, null, 2));
|
||||
log.log('trace file saved to disk', filenamePrefix);
|
||||
const traceFilename = `${pathWithBasename}-${index}.trace.json`;
|
||||
fs.writeFileSync(traceFilename, JSON.stringify(traceData, null, 2));
|
||||
log.log('trace file saved to disk', traceFilename);
|
||||
|
||||
fs.writeFileSync(`${filenamePrefix}-${index}.screenshots.html`, data.html);
|
||||
log.log('screenshots saved to disk', filenamePrefix);
|
||||
const screenshotsFilename = `${pathWithBasename}-${index}.screenshots.html`;
|
||||
fs.writeFileSync(screenshotsFilename, data.html);
|
||||
log.log('screenshots saved to disk', screenshotsFilename);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -46,10 +46,6 @@ describe('asset-saver helper', () => {
|
|||
});
|
||||
|
||||
it('generates HTML', () => {
|
||||
const options = {
|
||||
url: 'https://testexample.com',
|
||||
generatedTime: '2016-05-31T23:34:30.547Z'
|
||||
};
|
||||
const artifacts = {
|
||||
traces: {
|
||||
[Audit.DEFAULT_PASS]: {
|
||||
|
@ -58,19 +54,12 @@ describe('asset-saver helper', () => {
|
|||
},
|
||||
requestScreenshots: () => Promise.resolve([]),
|
||||
};
|
||||
return assetSaver.prepareAssets(artifacts, options).then(assets => {
|
||||
return assetSaver.prepareAssets(artifacts).then(assets => {
|
||||
assert.ok(/<!doctype/gim.test(assets[0].html));
|
||||
});
|
||||
});
|
||||
|
||||
describe('saves files', function() {
|
||||
const options = {
|
||||
url: 'https://testexample.com/',
|
||||
generatedTime: '2016-05-31T23:34:30.547Z',
|
||||
flags: {
|
||||
saveAssets: true
|
||||
}
|
||||
};
|
||||
const artifacts = {
|
||||
traces: {
|
||||
[Audit.DEFAULT_PASS]: {
|
||||
|
@ -80,17 +69,17 @@ describe('asset-saver helper', () => {
|
|||
requestScreenshots: () => Promise.resolve(screenshotFilmstrip)
|
||||
};
|
||||
|
||||
assetSaver.saveAssets(artifacts, options);
|
||||
assetSaver.saveAssets(artifacts, dbwResults.audits, process.cwd() + '/the_file');
|
||||
|
||||
it('trace file saved to disk with data', () => {
|
||||
const traceFilename = assetSaver.getFilenamePrefix(options) + '-0.trace.json';
|
||||
const traceFilename = 'the_file-0.trace.json';
|
||||
const traceFileContents = fs.readFileSync(traceFilename, 'utf8');
|
||||
assert.ok(traceFileContents.length > 3000000);
|
||||
fs.unlinkSync(traceFilename);
|
||||
});
|
||||
|
||||
it('screenshots file saved to disk with data', () => {
|
||||
const ssFilename = assetSaver.getFilenamePrefix(options) + '-0.screenshots.html';
|
||||
const ssFilename = 'the_file-0.screenshots.html';
|
||||
const ssFileContents = fs.readFileSync(ssFilename, 'utf8');
|
||||
assert.ok(/<!doctype/gim.test(ssFileContents));
|
||||
assert.ok(ssFileContents.includes('{"timestamp":674089419.919'));
|
||||
|
@ -108,7 +97,7 @@ describe('asset-saver helper', () => {
|
|||
requestScreenshots: () => Promise.resolve([]),
|
||||
};
|
||||
const beforeCount = countEvents(dbwTrace);
|
||||
return assetSaver.prepareAssets(mockArtifacts, dbwResults).then(preparedAssets => {
|
||||
return assetSaver.prepareAssets(mockArtifacts, dbwResults.audits).then(preparedAssets => {
|
||||
const afterCount = countEvents(preparedAssets[0].traceData);
|
||||
const metricsSansNavStart = Metrics.metricsDefinitions.length - 1;
|
||||
assert.equal(afterCount, beforeCount + (2 * metricsSansNavStart), 'unexpected event count');
|
||||
|
|
17
readme.md
17
readme.md
|
@ -164,6 +164,23 @@ Options:
|
|||
installations are found [boolean]
|
||||
```
|
||||
|
||||
### Output Path Examples
|
||||
|
||||
`--output-path=~/mydir/foo.out --save-assets` generates
|
||||
* `~/mydir/foo.out`
|
||||
* `~/mydir/foo.report.html`
|
||||
* `~/mydir/foo-0.trace.json`
|
||||
* `~/mydir/foo-0.screenshots.html`
|
||||
|
||||
`--output-path=./report.json --output json --save-artifacts` generates
|
||||
* `./report.json`
|
||||
* `./report.artifacts.log`
|
||||
|
||||
`--save-artifacts` prints a pretty report to `stdout` **and** generates
|
||||
* `./<HOST>_<DATE>.report.html`
|
||||
* `./<HOST>_<DATE>.artifacts.log`
|
||||
|
||||
|
||||
## Lighthouse w/ mobile devices
|
||||
|
||||
Lighthouse can run against a real mobile device. You can follow the [Remote Debugging on Android (Legacy Workflow)](https://developer.chrome.com/devtools/docs/remote-debugging-legacy) up through step 3.3, but the TL;DR is install & run adb, enable USB debugging, then port forward 9222 from the device to the machine with Lighthouse.
|
||||
|
|
Загрузка…
Ссылка в новой задаче