Skip to content

Commit

Permalink
core(runner): support multiple output modes
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce committed May 7, 2018
1 parent be71709 commit d88112b
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ last-run-results.html
*.report.html
*.report.dom.html
*.report.json
*.report.csv
*.report.pretty
*.artifacts.log

Expand Down
3 changes: 2 additions & 1 deletion lighthouse-cli/cli-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ function getFlags(manualArgv) {
.array('onlyAudits')
.array('onlyCategories')
.array('skipAudits')
.array('output')
.string('extraHeaders')

// default values
.default('chrome-flags', '')
.default('output', 'html')
.default('output', ['html'])
.default('port', 0)
.default('hostname', 'localhost')
.check(/** @param {!LH.Flags} argv */ (argv) => {
Expand Down
25 changes: 8 additions & 17 deletions lighthouse-cli/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,17 @@ function writeFile(filePath, output, outputMode) {
}

/**
* Writes the results.
* @param {LH.RunnerResult} results
* Writes the output.
* @param {string} output
* @param {string} mode
* @param {string} path
* @return {Promise<LH.RunnerResult>}
* @return {Promise<void>}
*/
function write(results, mode, path) {
return new Promise((resolve, reject) => {
const outputPath = checkOutputPath(path);
const output = results.report;

if (outputPath === 'stdout') {
return writeToStdout(output).then(_ => resolve(results));
}
return writeFile(outputPath, output, mode)
.then(_ => {
resolve(results);
})
.catch(err => reject(err));
});
async function write(output, mode, path) {
const outputPath = checkOutputPath(path);
return outputPath === 'stdout' ?
writeToStdout(output) :
writeFile(outputPath, output, mode);
}

/**
Expand Down
49 changes: 20 additions & 29 deletions lighthouse-cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,12 @@ function handleError(err) {
* @param {!LH.Flags} flags
* @return {Promise<void>}
*/
function saveResults(runnerResult, flags) {
async function saveResults(runnerResult, flags) {
const cwd = process.cwd();
let promise = Promise.resolve();

const shouldSaveResults = flags.auditMode || (flags.gatherMode === flags.auditMode);
if (!shouldSaveResults) return promise;
const {lhr, artifacts} = runnerResult;
if (!shouldSaveResults) return;
const {lhr, artifacts, report} = runnerResult;

// 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.
Expand All @@ -115,34 +114,26 @@ function saveResults(runnerResult, flags) {
const resolvedPath = path.resolve(cwd, configuredPath);

if (flags.saveAssets) {
promise = promise.then(_ => assetSaver.saveAssets(artifacts, lhr.audits, resolvedPath));
await assetSaver.saveAssets(artifacts, lhr.audits, resolvedPath);
}

return promise.then(_ => {
if (Array.isArray(flags.output)) {
return flags.output.reduce((innerPromise, outputType) => {
const extension = outputType;
const outputPath = `${resolvedPath}.report.${extension}`;
return innerPromise.then(() => Printer.write(runnerResult, outputType, outputPath));
}, Promise.resolve());
} else {
const extension = flags.output;
const outputPath =
flags.outputPath || `${resolvedPath}.report.${extension}`;
return Printer.write(runnerResult, flags.output, outputPath).then(_ => {
if (flags.output === Printer.OutputMode[Printer.OutputMode.html]) {
if (flags.view) {
opn(outputPath, {wait: false});
} else {
log.log(
'CLI',
// eslint-disable-next-line max-len
'Protip: Run lighthouse with `--view` to immediately open the HTML report in your browser');
}
}
});
for (const outputType of flags.output) {
const extension = outputType;
const output = report[flags.output.indexOf(outputType)];
let outputPath = `${resolvedPath}.report.${extension}`;
// If there was only a single output and the user specified an outputPath, force usage of it.
if (flags.outputPath && flags.output.length === 1) outputPath = flags.outputPath;
await Printer.write(output, outputType, outputPath);

if (outputType === Printer.OutputMode[Printer.OutputMode.html]) {
if (flags.view) {
opn(outputPath, {wait: false});
} else {
// eslint-disable-next-line max-len
log.log('CLI', 'Protip: Run lighthouse with `--view` to immediately open the HTML report in your browser');
}
}
});
}
}

/**
Expand Down
11 changes: 7 additions & 4 deletions lighthouse-core/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,15 @@ function cleanFlagsForSettings(flags = {}) {
return settings;
}

function merge(base, extension) {
// TODO(phulce): disentangle this merge function
function merge(base, extension, overrwriteArrays = false) {
// If the default value doesn't exist or is explicitly null, defer to the extending value
if (typeof base === 'undefined' || base === null) {
return extension;
} else if (typeof extension === 'undefined') {
return base;
} else if (Array.isArray(extension)) {
if (overrwriteArrays) return extension;
if (!Array.isArray(base)) throw new TypeError(`Expected array but got ${typeof base}`);
const merged = base.slice();
extension.forEach(item => {
Expand All @@ -171,7 +173,8 @@ function merge(base, extension) {
} else if (typeof extension === 'object') {
if (typeof base !== 'object') throw new TypeError(`Expected object but got ${typeof base}`);
Object.keys(extension).forEach(key => {
base[key] = merge(base[key], extension[key]);
if (key === 'settings' && typeof base[key] === 'object') overrwriteArrays = true;
base[key] = merge(base[key], extension[key], overrwriteArrays);
});
return base;
}
Expand Down Expand Up @@ -241,7 +244,7 @@ class Config {
configJSON.passes = Config.expandGathererShorthandAndMergeOptions(configJSON.passes);

// Override any applicable settings with CLI flags
configJSON.settings = merge(configJSON.settings || {}, cleanFlagsForSettings(flags));
configJSON.settings = merge(configJSON.settings || {}, cleanFlagsForSettings(flags), true);

// Generate a limited config if specified
if (Array.isArray(configJSON.settings.onlyCategories) ||
Expand Down Expand Up @@ -301,7 +304,7 @@ class Config {
*/
static augmentWithDefaults(config) {
const {defaultSettings, defaultPassConfig} = constants;
config.settings = merge(deepClone(defaultSettings), config.settings);
config.settings = merge(deepClone(defaultSettings), config.settings, true);
if (config.passes) {
config.passes = config.passes.map(pass => merge(deepClone(defaultPassConfig), pass));
}
Expand Down
39 changes: 23 additions & 16 deletions lighthouse-core/report/report-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,31 @@ class ReportGenerator {
/**
* Creates the results output in a format based on the `mode`.
* @param {LH.Result} lhr
* @param {'json'|'html'|'csv'} outputMode
* @return {string}
* @param {LH.Config.Settings['output']} outputModes
* @return {string|string[]}
*/
static generateReport(lhr, outputMode) {
// HTML report.
if (outputMode === 'html') {
return ReportGenerator.generateReportHtml(lhr);
}
// CSV report.
if (outputMode === 'csv') {
return ReportGenerator.generateReportCSV(lhr);
}
// JSON report.
if (outputMode === 'json') {
return JSON.stringify(lhr, null, 2);
}
static generateReport(lhr, outputModes) {
const outputAsArray = Array.isArray(outputModes);
if (typeof outputModes === 'string') outputModes = [outputModes];

const output = outputModes.map(outputMode => {
// HTML report.
if (outputMode === 'html') {
return ReportGenerator.generateReportHtml(lhr);
}
// CSV report.
if (outputMode === 'csv') {
return ReportGenerator.generateReportCSV(lhr);
}
// JSON report.
if (outputMode === 'json') {
return JSON.stringify(lhr, null, 2);
}

throw new Error('Invalid output mode: ' + outputMode);
});

throw new Error('Invalid output mode: ' + outputMode);
return outputAsArray ? output : output[0];
}
}

Expand Down
5 changes: 5 additions & 0 deletions lighthouse-core/test/config/config-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,11 @@ describe('Config', () => {
assert.ok(config.settings.nonsense === undefined, 'did not cleanup settings');
});

it('allows overriding of array-typed settings', () => {
const config = new Config({extends: true}, {output: ['html']});
assert.deepStrictEqual(config.settings.output, ['html']);
});

it('extends the full config', () => {
class CustomAudit extends Audit {
static get meta() {
Expand Down
7 changes: 7 additions & 0 deletions lighthouse-core/test/report/report-generator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,11 @@ describe('ReportGenerator', () => {
assert.ok(outputCheck.test(htmlOutput));
});
});

it('handles array of outputs', () => {
const [json, html] = ReportGenerator.generateReport(sampleResults, ['json', 'html']);
assert.doesNotThrow(_ => JSON.parse(json));
assert.ok(/<!doctype/gim.test(html));
assert.ok(/<html lang="en"/gim.test(html));
});
});
17 changes: 17 additions & 0 deletions lighthouse-core/test/runner-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,21 @@ describe('Runner', () => {
]);
});
});

it('can handle array of outputs', async () => {
const url = 'https://example.com';
const config = new Config({
extends: 'lighthouse:default',
settings: {
onlyCategories: ['performance'],
output: ['json', 'html'],
},
});

const results = await Runner.run(null, {url, config, driverMock});
assert.ok(Array.isArray(results.report) && results.report.length === 2,
'did not return multiple reports');
assert.ok(JSON.parse(results.report[0]), 'did not return json output');
assert.ok(/<!doctype/.test(results.report[1]), 'did not return html output');
});
});
6 changes: 4 additions & 2 deletions typings/externs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ declare global {
cpuSlowdownMultiplier?: number
}

export type OutputMode = 'json' | 'html' | 'csv';

interface SharedFlagsSettings {
output?: 'json' | 'html' | 'csv';
output?: OutputMode|OutputMode[];
maxWaitForLoad?: number;
blockedUrlPatterns?: string[] | null;
additionalTraceCategories?: string | null;
Expand Down Expand Up @@ -88,7 +90,7 @@ declare global {

export interface RunnerResult {
lhr: Result;
report: string;
report: string|string[];
artifacts: Artifacts;
}

Expand Down

0 comments on commit d88112b

Please sign in to comment.