fix: save artifacts and assets in output-path (#1601)

This commit is contained in:
Patrick Hulce 2017-02-06 17:28:42 -08:00 коммит произвёл Brendan Kenny
Родитель 6d4c05bd87
Коммит 76fb69e4b6
5 изменённых файлов: 79 добавлений и 57 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -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');

Просмотреть файл

@ -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.