From 4697d8553968970a8f6476ce4714ab66d40988ac Mon Sep 17 00:00:00 2001 From: Patrick Hulce Date: Thu, 5 Apr 2018 16:14:24 -0700 Subject: [PATCH] core: add default audit options for scores --- lighthouse-core/audits/audit.js | 7 ++++ lighthouse-core/audits/bootup-time.js | 24 +++++++++----- .../byte-efficiency/total-byte-weight.js | 28 +++++++++------- .../byte-efficiency/uses-long-cache-ttl.js | 28 +++++++++------- .../audits/consistently-interactive.js | 23 ++++++++----- .../audits/dobetterweb/dom-size.js | 26 ++++++++------- .../audits/estimated-input-latency.js | 33 ++++++++++--------- lighthouse-core/audits/first-interactive.js | 23 ++++++++----- .../audits/first-meaningful-paint.js | 32 +++++++++--------- .../audits/mainthread-work-breakdown.js | 23 ++++++++----- lighthouse-core/audits/speed-index-metric.js | 29 ++++++++-------- lighthouse-core/config/config.js | 10 ++++-- lighthouse-core/runner.js | 4 ++- .../test/audits/bootup-time-test.js | 4 +-- .../byte-efficiency/total-byte-weight-test.js | 7 ++-- .../uses-long-cache-ttl-test.js | 15 +++++---- .../audits/consistently-interactive-test.js | 5 +-- .../test/audits/dobetterweb/dom-size-test.js | 7 ++-- .../audits/estimated-input-latency-test.js | 3 +- .../audits/first-meaningful-paint-test.js | 15 +++++---- .../audits/mainthread-work-breakdown-test.js | 9 ++--- .../test/audits/speed-index-metric-test.js | 9 ++--- lighthouse-core/test/config/config-test.js | 4 +-- typings/audit.d.ts | 5 +++ 24 files changed, 224 insertions(+), 149 deletions(-) diff --git a/lighthouse-core/audits/audit.js b/lighthouse-core/audits/audit.js index 227868c99793..78c918f667cd 100644 --- a/lighthouse-core/audits/audit.js +++ b/lighthouse-core/audits/audit.js @@ -42,6 +42,13 @@ class Audit { throw new Error('Audit meta information must be overridden.'); } + /** + * @return {Object} + */ + static get defaultOptions() { + return {}; + } + /** * Computes a clamped score between 0 and 1 based on the measured value. Score is determined by * considering a log-normal distribution governed by the two control points, point of diminishing diff --git a/lighthouse-core/audits/bootup-time.js b/lighthouse-core/audits/bootup-time.js index 682a51cb4632..f3e20c08c7be 100644 --- a/lighthouse-core/audits/bootup-time.js +++ b/lighthouse-core/audits/bootup-time.js @@ -11,11 +11,6 @@ const Util = require('../report/v2/renderer/util'); const {groupIdToName, taskToGroup} = require('../lib/task-groups'); const THRESHOLD_IN_MS = 10; -// Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/rkphawothk -// <500ms ~= 100, >2s is yellow, >3.5s is red -const SCORING_POINT_OF_DIMINISHING_RETURNS = 600; -const SCORING_MEDIAN = 3500; - class BootupTime extends Audit { /** * @return {!AuditMeta} @@ -34,6 +29,18 @@ class BootupTime extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/rkphawothk + // <500ms ~= 100, >2s is yellow, >3.5s is red + scorePODR: 600, + scoreMedian: 3500, + }; + } + /** * @param {DevtoolsTimelineModel} timelineModel * @return {!Map} @@ -65,9 +72,10 @@ class BootupTime extends Audit { /** * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!AuditResult} */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[BootupTime.DEFAULT_PASS]; return artifacts.requestDevtoolsTimelineModel(trace).then(devtoolsTimelineModel => { const executionTimings = BootupTime.getExecutionTimingsByURL(devtoolsTimelineModel); @@ -106,8 +114,8 @@ class BootupTime extends Audit { const score = Audit.computeLogNormalScore( totalBootupTime, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); return { diff --git a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js index b7680894fb7e..b962a70a6cb6 100644 --- a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js +++ b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js @@ -8,11 +8,6 @@ const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const Util = require('../../report/v2/renderer/util'); -// Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/gpmjeykbwr -// ~75th and ~90th percentiles http://httparchive.org/interesting.php?a=All&l=Feb%201%202017&s=All#bytesTotal -const SCORING_POINT_OF_DIMINISHING_RETURNS = 2500 * 1024; -const SCORING_MEDIAN = 4000 * 1024; - class TotalByteWeight extends ByteEfficiencyAudit { /** * @return {!AuditMeta} @@ -31,11 +26,24 @@ class TotalByteWeight extends ByteEfficiencyAudit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/gpmjeykbwr + // ~75th and ~90th percentiles http://httparchive.org/interesting.php?a=All&l=Feb%201%202017&s=All#bytesTotal + scorePODR: 2500 * 1024, + scoreMedian: 4000 * 1024, + }; + } + /** * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!Promise} */ - static audit(artifacts) { + static audit(artifacts, context) { const devtoolsLogs = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS]; return Promise.all([ artifacts.requestNetworkRecords(devtoolsLogs), @@ -60,14 +68,10 @@ class TotalByteWeight extends ByteEfficiencyAudit { const totalCompletedRequests = results.length; results = results.sort((itemA, itemB) => itemB.totalBytes - itemA.totalBytes).slice(0, 10); - // Use the CDF of a log-normal distribution for scoring. - // <= 1600KB: score≈1 - // 4000KB: score=0.50 - // >= 9000KB: score≈0 const score = ByteEfficiencyAudit.computeLogNormalScore( totalBytes, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); const headings = [ diff --git a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js index d7c401b5fd2c..f657f7a436a6 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js +++ b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js @@ -14,10 +14,6 @@ const URL = require('../../lib/url-shim'); // Ignore assets that have very high likelihood of cache hit const IGNORE_THRESHOLD_IN_PERCENT = 0.925; -// Scoring curve: https://www.desmos.com/calculator/zokzso8umm -const SCORING_POINT_OF_DIMINISHING_RETURNS = 4; // 4 KB -const SCORING_MEDIAN = 768; // 768 KB - class CacheHeaders extends Audit { /** * @return {!AuditMeta} @@ -36,6 +32,17 @@ class CacheHeaders extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/zokzso8umm + scorePODR: 4 * 1024, + scoreMedian: 768 * 1024, + }; + } + /** * Interpolates the y value at a point x on the line defined by (x0, y0) and (x1, y1) * @param {number} x0 @@ -154,9 +161,10 @@ class CacheHeaders extends Audit { /** * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!AuditResult} */ - static audit(artifacts) { + static audit(artifacts, context) { const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; return artifacts.requestNetworkRecords(devtoolsLogs).then(records => { const results = []; @@ -205,14 +213,10 @@ class CacheHeaders extends Audit { (a, b) => a.cacheLifetimeInSeconds - b.cacheLifetimeInSeconds || b.totalBytes - a.totalBytes ); - // Use the CDF of a log-normal distribution for scoring. - // <= 4KB: score≈1 - // 768KB: score=0.5 - // >= 4600KB: score≈0.05 const score = Audit.computeLogNormalScore( - totalWastedBytes / 1024, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + totalWastedBytes, + context.options.scorePODR, + context.options.scoreMedian ); const headings = [ diff --git a/lighthouse-core/audits/consistently-interactive.js b/lighthouse-core/audits/consistently-interactive.js index 0189adc5092f..dbb228d37290 100644 --- a/lighthouse-core/audits/consistently-interactive.js +++ b/lighthouse-core/audits/consistently-interactive.js @@ -11,11 +11,6 @@ const NetworkRecorder = require('../lib/network-recorder'); const TracingProcessor = require('../lib/traces/tracing-processor'); const LHError = require('../lib/errors'); -// Parameters (in ms) for log-normal CDF scoring. To see the curve: -// https://www.desmos.com/calculator/uti67afozh -const SCORING_POINT_OF_DIMINISHING_RETURNS = 1700; -const SCORING_MEDIAN = 10000; - const REQUIRED_QUIET_WINDOW = 5000; const ALLOWED_CONCURRENT_REQUESTS = 2; @@ -41,6 +36,17 @@ class ConsistentlyInteractiveMetric extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/uti67afozh + scorePODR: 1700, + scoreMedian: 10000, + }; + } + /** * Finds all time periods where the number of inflight requests is less than or equal to the * number of allowed concurrent requests (2). @@ -155,9 +161,10 @@ class ConsistentlyInteractiveMetric extends Audit { /** * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!Promise} */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[Audit.DEFAULT_PASS]; const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; const computedArtifacts = [ @@ -192,8 +199,8 @@ class ConsistentlyInteractiveMetric extends Audit { return { score: Audit.computeLogNormalScore( timeInMs, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ), rawValue: timeInMs, displayValue: Util.formatMilliseconds(timeInMs), diff --git a/lighthouse-core/audits/dobetterweb/dom-size.js b/lighthouse-core/audits/dobetterweb/dom-size.js index 6499ee118c43..f5d99790491a 100644 --- a/lighthouse-core/audits/dobetterweb/dom-size.js +++ b/lighthouse-core/audits/dobetterweb/dom-size.js @@ -19,10 +19,6 @@ const MAX_DOM_NODES = 1500; const MAX_DOM_TREE_WIDTH = 60; const MAX_DOM_TREE_DEPTH = 32; -// Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/9cyxpm5qgp. -const SCORING_POINT_OF_DIMINISHING_RETURNS = 2400; -const SCORING_MEDIAN = 3000; - class DOMSize extends Audit { static get MAX_DOM_NODES() { return MAX_DOM_NODES; @@ -47,22 +43,30 @@ class DOMSize extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/9cyxpm5qgp + scorePODR: 2400, + scoreMedian: 3000, + }; + } + /** * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!AuditResult} */ - static audit(artifacts) { + static audit(artifacts, context) { const stats = artifacts.DOMStats; - // Use the CDF of a log-normal distribution for scoring. - // <= 1500: score≈1 - // 3000: score=0.5 - // >= 5970: score≈0 const score = Audit.computeLogNormalScore( stats.totalDOMNodes, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); const headings = [ diff --git a/lighthouse-core/audits/estimated-input-latency.js b/lighthouse-core/audits/estimated-input-latency.js index a7d254ded163..29dbef90d342 100644 --- a/lighthouse-core/audits/estimated-input-latency.js +++ b/lighthouse-core/audits/estimated-input-latency.js @@ -10,11 +10,6 @@ const Util = require('../report/v2/renderer/util'); const TracingProcessor = require('../lib/traces/tracing-processor'); const LHError = require('../lib/errors'); -// Parameters (in ms) for log-normal CDF scoring. To see the curve: -// https://www.desmos.com/calculator/srv0hqhf7d -const SCORING_POINT_OF_DIMINISHING_RETURNS = 50; -const SCORING_MEDIAN = 100; - class EstimatedInputLatency extends Audit { /** * @return {!AuditMeta} @@ -33,7 +28,18 @@ class EstimatedInputLatency extends Audit { }; } - static calculate(tabTrace) { + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/srv0hqhf7d + scorePODR: 50, + scoreMedian: 100, + }; + } + + static calculate(tabTrace, context) { const startTime = tabTrace.timings.firstMeaningfulPaint; if (!startTime) { throw new LHError(LHError.errors.NO_FMP); @@ -43,16 +49,10 @@ class EstimatedInputLatency extends Audit { const ninetieth = latencyPercentiles.find(result => result.percentile === 0.9); const rawValue = parseFloat(ninetieth.time.toFixed(1)); - // Use the CDF of a log-normal distribution for scoring. - // 10th Percentile ≈ 58ms - // 25th Percentile ≈ 75ms - // Median = 100ms - // 75th Percentile ≈ 133ms - // 95th Percentile ≈ 199ms const score = Audit.computeLogNormalScore( ninetieth.time, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); return { @@ -69,13 +69,14 @@ class EstimatedInputLatency extends Audit { * Audits the page to estimate input latency. * @see https://github.com/GoogleChrome/lighthouse/issues/28 * @param {!Artifacts} artifacts The artifacts from the gather phase. + * @param {LH.Audit.Context} context * @return {!Promise} The score from the audit, ranging from 0-100. */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[this.DEFAULT_PASS]; return artifacts.requestTraceOfTab(trace) - .then(EstimatedInputLatency.calculate); + .then(traceOfTab => EstimatedInputLatency.calculate(traceOfTab, context)); } } diff --git a/lighthouse-core/audits/first-interactive.js b/lighthouse-core/audits/first-interactive.js index 0b76c789cae4..49d356ed16bd 100644 --- a/lighthouse-core/audits/first-interactive.js +++ b/lighthouse-core/audits/first-interactive.js @@ -8,11 +8,6 @@ const Audit = require('./audit'); const Util = require('../report/v2/renderer/util.js'); -// Parameters (in ms) for log-normal CDF scoring. To see the curve: -// https://www.desmos.com/calculator/rjp0lbit8y -const SCORING_POINT_OF_DIMINISHING_RETURNS = 1700; -const SCORING_MEDIAN = 10000; - class FirstInteractiveMetric extends Audit { /** * @return {!AuditMeta} @@ -29,22 +24,34 @@ class FirstInteractiveMetric extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/uti67afozh + scorePODR: 1700, + scoreMedian: 10000, + }; + } + /** * Identify the time the page is "first interactive" * @see https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/edit# * * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!Promise} */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[Audit.DEFAULT_PASS]; return artifacts.requestFirstInteractive(trace) .then(firstInteractive => { return { score: Audit.computeLogNormalScore( firstInteractive.timeInMs, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ), rawValue: firstInteractive.timeInMs, displayValue: Util.formatMilliseconds(firstInteractive.timeInMs), diff --git a/lighthouse-core/audits/first-meaningful-paint.js b/lighthouse-core/audits/first-meaningful-paint.js index d894d8d84c5b..8c7c526cd263 100644 --- a/lighthouse-core/audits/first-meaningful-paint.js +++ b/lighthouse-core/audits/first-meaningful-paint.js @@ -9,12 +9,6 @@ const Audit = require('./audit'); const Util = require('../report/v2/renderer/util'); const LHError = require('../lib/errors'); -// Parameters (in ms) for log-normal CDF scoring. To see the curve: -// https://www.desmos.com/calculator/joz3pqttdq -const SCORING_POINT_OF_DIMINISHING_RETURNS = 1600; -const SCORING_MEDIAN = 4000; - - class FirstMeaningfulPaint extends Audit { /** * @return {!AuditMeta} @@ -30,14 +24,26 @@ class FirstMeaningfulPaint extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/joz3pqttdq + scorePODR: 1600, + scoreMedian: 4000, + }; + } + /** * Audits the page to give a score for First Meaningful Paint. * @see https://github.com/GoogleChrome/lighthouse/issues/26 * @see https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/view * @param {!Artifacts} artifacts The artifacts from the gather phase. + * @param {LH.Audit.Context} context * @return {!Promise} */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[this.DEFAULT_PASS]; return artifacts.requestTraceOfTab(trace).then(tabTrace => { if (!tabTrace.firstMeaningfulPaintEvt) { @@ -50,7 +56,7 @@ class FirstMeaningfulPaint extends Audit { throw new LHError(LHError.errors.NO_NAVSTART); } - const result = this.calculateScore(tabTrace); + const result = this.calculateScore(tabTrace, context); return { score: result.score, @@ -64,7 +70,7 @@ class FirstMeaningfulPaint extends Audit { }); } - static calculateScore(traceOfTab) { + static calculateScore(traceOfTab, context) { // Expose the raw, unchanged monotonic timestamps from the trace, along with timing durations const extendedInfo = { timestamps: { @@ -94,15 +100,11 @@ class FirstMeaningfulPaint extends Audit { } }); - // Use the CDF of a log-normal distribution for scoring. - // < 1100ms: score≈1 - // 4000ms: score=0.5 - // >= 14000ms: score≈0 const firstMeaningfulPaint = traceOfTab.timings.firstMeaningfulPaint; const score = Audit.computeLogNormalScore( firstMeaningfulPaint, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); return { diff --git a/lighthouse-core/audits/mainthread-work-breakdown.js b/lighthouse-core/audits/mainthread-work-breakdown.js index 6b5b5302f7d9..74062a3528ea 100644 --- a/lighthouse-core/audits/mainthread-work-breakdown.js +++ b/lighthouse-core/audits/mainthread-work-breakdown.js @@ -15,11 +15,6 @@ const Util = require('../report/v2/renderer/util'); // We group all trace events into groups to show a highlevel breakdown of the page const {taskToGroup} = require('../lib/task-groups'); -// Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/s2eqcifkum -// <1s ~= 100, >3s is yellow, >4s is red -const SCORING_POINT_OF_DIMINISHING_RETURNS = 1500; -const SCORING_MEDIAN = 4000; - class MainThreadWorkBreakdown extends Audit { /** * @return {!AuditMeta} @@ -37,6 +32,17 @@ class MainThreadWorkBreakdown extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/s2eqcifkum + scorePODR: 1500, + scoreMedian: 4000, + }; + } + /** * @param {!DevtoolsTimelineModel} timelineModel * @return {!Map} @@ -53,9 +59,10 @@ class MainThreadWorkBreakdown extends Audit { /** * @param {!Artifacts} artifacts + * @param {LH.Audit.Context} context * @return {!AuditResult} */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[MainThreadWorkBreakdown.DEFAULT_PASS]; return artifacts.requestDevtoolsTimelineModel(trace) @@ -92,8 +99,8 @@ class MainThreadWorkBreakdown extends Audit { const score = Audit.computeLogNormalScore( totalExecutionTime, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); return { diff --git a/lighthouse-core/audits/speed-index-metric.js b/lighthouse-core/audits/speed-index-metric.js index 97e2ddb81fdb..bbd7b8963883 100644 --- a/lighthouse-core/audits/speed-index-metric.js +++ b/lighthouse-core/audits/speed-index-metric.js @@ -9,11 +9,6 @@ const Audit = require('./audit'); const Util = require('../report/v2/renderer/util'); const LHError = require('../lib/errors'); -// Parameters (in ms) for log-normal CDF scoring. To see the curve: -// https://www.desmos.com/calculator/mdgjzchijg -const SCORING_POINT_OF_DIMINISHING_RETURNS = 1250; -const SCORING_MEDIAN = 5500; - class SpeedIndexMetric extends Audit { /** * @return {!AuditMeta} @@ -29,13 +24,25 @@ class SpeedIndexMetric extends Audit { }; } + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // see https://www.desmos.com/calculator/mdgjzchijg + scorePODR: 1250, + scoreMedian: 5500, + }; + } + /** * Audits the page to give a score for the Speed Index. * @see https://github.com/GoogleChrome/lighthouse/issues/197 * @param {!Artifacts} artifacts The artifacts from the gather phase. + * @param {LH.Audit.Context} context * @return {!Promise} */ - static audit(artifacts) { + static audit(artifacts, context) { const trace = artifacts.traces[this.DEFAULT_PASS]; // run speedline @@ -55,16 +62,10 @@ class SpeedIndexMetric extends Audit { } }); - // Use the CDF of a log-normal distribution for scoring. - // 10th Percentile = 2,240 - // 25th Percentile = 3,430 - // Median = 5,500 - // 75th Percentile = 8,820 - // 95th Percentile = 17,400 const score = Audit.computeLogNormalScore( speedline.perceptualSpeedIndex, - SCORING_POINT_OF_DIMINISHING_RETURNS, - SCORING_MEDIAN + context.options.scorePODR, + context.options.scoreMedian ); const extendedInfo = { diff --git a/lighthouse-core/config/config.js b/lighthouse-core/config/config.js index 03d9d2734ef7..897d9d0a76b1 100644 --- a/lighthouse-core/config/config.js +++ b/lighthouse-core/config/config.js @@ -268,6 +268,12 @@ function merge(base, extension) { return extension; } +function cloneArrayWithPluginSafety(array) { + return array.map(item => { + return typeof item === 'object' ? Object.assign({}, item) : item; + }); +} + function deepClone(json) { const cloned = JSON.parse(JSON.stringify(json)); @@ -275,12 +281,12 @@ function deepClone(json) { // injection of plugins. if (Array.isArray(json.passes)) { cloned.passes.forEach((pass, i) => { - pass.gatherers = Array.from(json.passes[i].gatherers); + pass.gatherers = cloneArrayWithPluginSafety(json.passes[i].gatherers); }); } if (Array.isArray(json.audits)) { - cloned.audits = Array.from(json.audits); + cloned.audits = cloneArrayWithPluginSafety(json.audits); } return cloned; diff --git a/lighthouse-core/runner.js b/lighthouse-core/runner.js index 65cdfb76c82e..e3eedab5a093 100644 --- a/lighthouse-core/runner.js +++ b/lighthouse-core/runner.js @@ -260,8 +260,10 @@ class Runner { throw error; } } + + const auditOptions = Object.assign({}, audit.defaultOptions, auditDefn.options); // all required artifacts are in good shape, so we proceed - return audit.audit(artifacts, {options: auditDefn.options || {}, settings: opts.settings}); + return audit.audit(artifacts, {options: auditOptions, settings: opts.settings}); // Fill remaining audit result fields. }).then(auditResult => Audit.generateAuditResult(audit, auditResult)) .catch(err => { diff --git a/lighthouse-core/test/audits/bootup-time-test.js b/lighthouse-core/test/audits/bootup-time-test.js index aa879118d655..60e27a46485c 100644 --- a/lighthouse-core/test/audits/bootup-time-test.js +++ b/lighthouse-core/test/audits/bootup-time-test.js @@ -22,7 +22,7 @@ describe('Performance: bootup-time audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return BootupTime.audit(artifacts).then(output => { + return BootupTime.audit(artifacts, {options: BootupTime.defaultOptions}).then(output => { assert.equal(output.details.items.length, 4); assert.equal(output.score, 1); assert.equal(Math.round(output.rawValue), 176); @@ -51,7 +51,7 @@ describe('Performance: bootup-time audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return BootupTime.audit(artifacts) + return BootupTime.audit(artifacts, {options: BootupTime.defaultOptions}) .then(output => { assert.equal(output.details.items.length, 0); assert.equal(output.score, 1); diff --git a/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js b/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js index e1f209313754..5e5ee5c191f3 100644 --- a/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js @@ -7,6 +7,7 @@ const TotalByteWeight = require('../../../audits/byte-efficiency/total-byte-weight.js'); const assert = require('assert'); +const options = TotalByteWeight.defaultOptions; /* eslint-env mocha */ @@ -39,7 +40,7 @@ describe('Total byte weight audit', () => { ['file.jpg', 70], ]); - return TotalByteWeight.audit(artifacts).then(result => { + return TotalByteWeight.audit(artifacts, {options}).then(result => { assert.strictEqual(result.rawValue, 150 * 1024); assert.strictEqual(result.score, 1); const results = result.details.items; @@ -64,7 +65,7 @@ describe('Total byte weight audit', () => { ['small6.js', 5], ]); - return TotalByteWeight.audit(artifacts).then(result => { + return TotalByteWeight.audit(artifacts, {options}).then(result => { assert.ok(0.40 < result.score && result.score < 0.6, 'score is around 0.5'); assert.strictEqual(result.rawValue, 4180 * 1024); const results = result.details.items; @@ -80,7 +81,7 @@ describe('Total byte weight audit', () => { ['file.jpg', 7000], ]); - return TotalByteWeight.audit(artifacts).then(result => { + return TotalByteWeight.audit(artifacts, {options}).then(result => { assert.strictEqual(result.rawValue, 15000 * 1024); assert.strictEqual(result.score, 0); }); diff --git a/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js b/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js index 98ca872ef80b..8373a80bf71c 100644 --- a/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js @@ -8,6 +8,7 @@ const CacheHeadersAudit = require('../../../audits/byte-efficiency/uses-long-cache-ttl.js'); const assert = require('assert'); const WebInspector = require('../../../lib/web-inspector'); +const options = CacheHeadersAudit.defaultOptions; /* eslint-env mocha */ @@ -56,7 +57,7 @@ describe('Cache headers audit', () => { it('detects missing cache headers', () => { networkRecords = [networkRecord()]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { const items = result.extendedInfo.value.results; assert.equal(items.length, 1); assert.equal(items[0].cacheLifetimeInSeconds, 0); @@ -73,7 +74,7 @@ describe('Cache headers audit', () => { networkRecord({headers: {'cache-control': 'max-age=31536000'}}), // a year ]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { const items = result.details.items; assert.equal(items.length, 3); assert.equal(items[0].cacheLifetimeInSeconds, 3600); @@ -98,7 +99,7 @@ describe('Cache headers audit', () => { networkRecord({headers: {expires: expiresIn(3600)}}), // an hour ]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { const items = result.extendedInfo.value.results; assert.equal(items.length, 3); closeEnough(items[0].cacheLifetimeInSeconds, 3600); @@ -124,7 +125,7 @@ describe('Cache headers audit', () => { }}), ]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { const items = result.extendedInfo.value.results; assert.equal(items.length, 2); assert.ok(Math.abs(items[0].cacheLifetimeInSeconds - 3600) <= 1, 'invalid expires parsing'); @@ -140,7 +141,7 @@ describe('Cache headers audit', () => { networkRecord({headers: {'etag': 'md5hashhere', 'cache-control': 'max-age=60'}}), ]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { const items = result.extendedInfo.value.results; assert.equal(items.length, 2); }); @@ -155,7 +156,7 @@ describe('Cache headers audit', () => { networkRecord({headers: {pragma: 'no-cache'}}), ]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { const items = result.extendedInfo.value.results; assert.equal(result.score, 1); assert.equal(items.length, 0); @@ -170,7 +171,7 @@ describe('Cache headers audit', () => { networkRecord({resourceType: WebInspector.resourceTypes.XHR}), ]; - return CacheHeadersAudit.audit(artifacts).then(result => { + return CacheHeadersAudit.audit(artifacts, {options}).then(result => { assert.equal(result.score, 1); const items = result.extendedInfo.value.results; assert.equal(items.length, 1); diff --git a/lighthouse-core/test/audits/consistently-interactive-test.js b/lighthouse-core/test/audits/consistently-interactive-test.js index 70ba4885cd95..fc07f065e3b9 100644 --- a/lighthouse-core/test/audits/consistently-interactive-test.js +++ b/lighthouse-core/test/audits/consistently-interactive-test.js @@ -8,6 +8,7 @@ const ConsistentlyInteractive = require('../../audits/consistently-interactive.js'); const Runner = require('../../runner.js'); const assert = require('assert'); +const options = ConsistentlyInteractive.defaultOptions; const acceptableTrace = require('../fixtures/traces/progressive-app-m60.json'); const acceptableDevToolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json'); @@ -38,7 +39,7 @@ describe('Performance: consistently-interactive audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return ConsistentlyInteractive.audit(artifacts).then(output => { + return ConsistentlyInteractive.audit(artifacts, {options}).then(output => { assert.equal(output.score, 0.99); assert.equal(Math.round(output.rawValue), 1582); assert.equal(output.displayValue, '1,580\xa0ms'); @@ -55,7 +56,7 @@ describe('Performance: consistently-interactive audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return ConsistentlyInteractive.audit(artifacts).then(output => { + return ConsistentlyInteractive.audit(artifacts, {options}).then(output => { assert.equal(output.score, 0.95); assert.equal(Math.round(output.rawValue), 2712); assert.equal(output.displayValue, '2,710\xa0ms'); diff --git a/lighthouse-core/test/audits/dobetterweb/dom-size-test.js b/lighthouse-core/test/audits/dobetterweb/dom-size-test.js index a0eb9572c889..e203ec123410 100644 --- a/lighthouse-core/test/audits/dobetterweb/dom-size-test.js +++ b/lighthouse-core/test/audits/dobetterweb/dom-size-test.js @@ -7,6 +7,7 @@ const DOMSize = require('../../../audits/dobetterweb/dom-size.js'); const assert = require('assert'); +const options = DOMSize.defaultOptions; /* eslint-env mocha */ @@ -21,7 +22,7 @@ describe('Num DOM nodes audit', () => { }; it('calculates score hitting top of distribution', () => { - const auditResult = DOMSize.audit(artifact); + const auditResult = DOMSize.audit(artifact, {options}); assert.equal(auditResult.score, 1); assert.equal(auditResult.rawValue, numNodes); assert.equal(auditResult.displayValue, `${numNodes.toLocaleString()} nodes`); @@ -32,11 +33,11 @@ describe('Num DOM nodes audit', () => { it('calculates score hitting mid distribution', () => { artifact.DOMStats.totalDOMNodes = 3100; - assert.equal(DOMSize.audit(artifact).score, 0.43); + assert.equal(DOMSize.audit(artifact, {options}).score, 0.43); }); it('calculates score hitting bottom of distribution', () => { artifact.DOMStats.totalDOMNodes = 5970; - assert.equal(DOMSize.audit(artifact).score, 0); + assert.equal(DOMSize.audit(artifact, {options}).score, 0); }); }); diff --git a/lighthouse-core/test/audits/estimated-input-latency-test.js b/lighthouse-core/test/audits/estimated-input-latency-test.js index ac33f0395a27..95a3f39c5f86 100644 --- a/lighthouse-core/test/audits/estimated-input-latency-test.js +++ b/lighthouse-core/test/audits/estimated-input-latency-test.js @@ -8,6 +8,7 @@ const Audit = require('../../audits/estimated-input-latency.js'); const Runner = require('../../runner.js'); const assert = require('assert'); +const options = Audit.defaultOptions; const pwaTrace = require('../fixtures/traces/progressive-app.json'); @@ -25,7 +26,7 @@ function generateArtifactsWithTrace(trace) { describe('Performance: estimated-input-latency audit', () => { it('evaluates valid input correctly', () => { const artifacts = generateArtifactsWithTrace({traceEvents: pwaTrace}); - return Audit.audit(artifacts).then(output => { + return Audit.audit(artifacts, {options}).then(output => { assert.equal(output.debugString, undefined); assert.equal(output.rawValue, 16.2); assert.equal(output.displayValue, '16\xa0ms'); diff --git a/lighthouse-core/test/audits/first-meaningful-paint-test.js b/lighthouse-core/test/audits/first-meaningful-paint-test.js index dcbbfa3ebaca..d2ac0fd6c8b6 100644 --- a/lighthouse-core/test/audits/first-meaningful-paint-test.js +++ b/lighthouse-core/test/audits/first-meaningful-paint-test.js @@ -8,6 +8,7 @@ const FMPAudit = require('../../audits/first-meaningful-paint.js'); const Audit = require('../../audits/audit.js'); const assert = require('assert'); +const options = FMPAudit.defaultOptions; const traceEvents = require('../fixtures/traces/progressive-app.json'); const badNavStartTrace = require('../fixtures/traces/bad-nav-start-ts.json'); const lateTracingStartedTrace = require('../fixtures/traces/tracingstarted-after-navstart.json'); @@ -32,7 +33,7 @@ describe('Performance: first-meaningful-paint audit', () => { let fmpResult; it('processes a valid trace file', () => { - return FMPAudit.audit(generateArtifactsWithTrace(traceEvents)).then(result => { + return FMPAudit.audit(generateArtifactsWithTrace(traceEvents), {options}).then(result => { fmpResult = result; }).catch(_ => { assert.ok(false); @@ -67,7 +68,8 @@ describe('Performance: first-meaningful-paint audit', () => { describe('finds correct FMP', () => { it('if there was a tracingStartedInPage after the frame\'s navStart', () => { - return FMPAudit.audit(generateArtifactsWithTrace(lateTracingStartedTrace)).then(result => { + const artifacts = generateArtifactsWithTrace(lateTracingStartedTrace); + return FMPAudit.audit(artifacts, {options}).then(result => { assert.equal(result.displayValue, '530\xa0ms'); assert.equal(result.rawValue, 529.9); assert.equal(result.extendedInfo.value.timestamps.navStart, 29343540951); @@ -78,7 +80,8 @@ describe('Performance: first-meaningful-paint audit', () => { }); it('if there was a tracingStartedInPage after the frame\'s navStart #2', () => { - return FMPAudit.audit(generateArtifactsWithTrace(badNavStartTrace)).then(result => { + const artifacts = generateArtifactsWithTrace(badNavStartTrace); + return FMPAudit.audit(artifacts, {options}).then(result => { assert.equal(result.displayValue, '630\xa0ms'); assert.equal(result.rawValue, 632.4); assert.equal(result.extendedInfo.value.timestamps.navStart, 8885424467); @@ -89,7 +92,7 @@ describe('Performance: first-meaningful-paint audit', () => { }); it('if it appears slightly before the fCP', () => { - return FMPAudit.audit(generateArtifactsWithTrace(preactTrace)).then(result => { + return FMPAudit.audit(generateArtifactsWithTrace(preactTrace), {options}).then(result => { assert.equal(result.displayValue, '880\xa0ms'); assert.equal(result.rawValue, 878.4); assert.equal(result.extendedInfo.value.timestamps.navStart, 1805796384607); @@ -100,7 +103,7 @@ describe('Performance: first-meaningful-paint audit', () => { }); it('from candidates if no defined FMP exists', () => { - return FMPAudit.audit(generateArtifactsWithTrace(noFMPtrace)).then(result => { + return FMPAudit.audit(generateArtifactsWithTrace(noFMPtrace), {options}).then(result => { assert.equal(result.displayValue, '4,460\xa0ms'); assert.equal(result.rawValue, 4460.9); assert.equal(result.extendedInfo.value.timings.fCP, 1494.73); @@ -111,7 +114,7 @@ describe('Performance: first-meaningful-paint audit', () => { }); it('handles traces missing an FCP', () => { - return FMPAudit.audit(generateArtifactsWithTrace(noFCPtrace)).then(result => { + return FMPAudit.audit(generateArtifactsWithTrace(noFCPtrace), {options}).then(result => { assert.strictEqual(result.debugString, undefined); assert.strictEqual(result.displayValue, '480\xa0ms'); assert.strictEqual(result.rawValue, 482.3); diff --git a/lighthouse-core/test/audits/mainthread-work-breakdown-test.js b/lighthouse-core/test/audits/mainthread-work-breakdown-test.js index 2b16bbe93a83..e751feb3c26c 100644 --- a/lighthouse-core/test/audits/mainthread-work-breakdown-test.js +++ b/lighthouse-core/test/audits/mainthread-work-breakdown-test.js @@ -9,6 +9,7 @@ const PageExecutionTimings = require('../../audits/mainthread-work-breakdown.js'); const Runner = require('../../runner.js'); const assert = require('assert'); +const options = PageExecutionTimings.defaultOptions; const acceptableTrace = require('../fixtures/traces/progressive-app-m60.json'); const siteWithRedirectTrace = require('../fixtures/traces/site-with-redirect.json'); @@ -67,7 +68,7 @@ describe('Performance: page execution timings audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return PageExecutionTimings.audit(artifacts).then(output => { + return PageExecutionTimings.audit(artifacts, {options}).then(output => { const valueOf = name => Math.round(output.extendedInfo.value[name]); assert.equal(output.details.items.length, 12); @@ -89,7 +90,7 @@ describe('Performance: page execution timings audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return PageExecutionTimings.audit(artifacts).then(output => { + return PageExecutionTimings.audit(artifacts, {options}).then(output => { const valueOf = name => Math.round(output.extendedInfo.value[name]); assert.equal(output.details.items.length, 13); assert.equal(output.score, 1); @@ -110,7 +111,7 @@ describe('Performance: page execution timings audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return PageExecutionTimings.audit(artifacts).then(output => { + return PageExecutionTimings.audit(artifacts, {options}).then(output => { const valueOf = name => Math.round(output.extendedInfo.value[name]); assert.equal(output.details.items.length, 12); assert.equal(output.score, 1); @@ -131,7 +132,7 @@ describe('Performance: page execution timings audit', () => { }, }, Runner.instantiateComputedArtifacts()); - return PageExecutionTimings.audit(artifacts).then(output => { + return PageExecutionTimings.audit(artifacts, {options}).then(output => { assert.equal(output.details.items.length, 0); assert.equal(output.score, 1); assert.equal(Math.round(output.rawValue), 0); diff --git a/lighthouse-core/test/audits/speed-index-metric-test.js b/lighthouse-core/test/audits/speed-index-metric-test.js index 285f4e745d9b..69106f93dc76 100644 --- a/lighthouse-core/test/audits/speed-index-metric-test.js +++ b/lighthouse-core/test/audits/speed-index-metric-test.js @@ -11,6 +11,7 @@ const Audit = require('../../audits/speed-index-metric.js'); const assert = require('assert'); const pwaTrace = require('../fixtures/traces/progressive-app.json'); const Runner = require('../../runner.js'); +const options = Audit.defaultOptions; const emptyTraceStub = { traces: { @@ -41,7 +42,7 @@ describe('Performance: speed-index-metric audit', () => { traces: {defaultPass: {traceEvents: pwaTrace}}, }); - return Audit.audit(artifacts).then(result => { + return Audit.audit(artifacts, {options}).then(result => { assert.equal(result.score, 1); assert.equal(result.rawValue, 609); assert.equal(Math.round(result.extendedInfo.value.timings.firstVisualChange), 475); @@ -53,7 +54,7 @@ describe('Performance: speed-index-metric audit', () => { it('throws an error if no frames', () => { const artifacts = mockArtifactsWithSpeedlineResult({frames: []}); - return Audit.audit(artifacts).then( + return Audit.audit(artifacts, {options}).then( _ => assert.ok(false), _ => assert.ok(true)); }); @@ -65,7 +66,7 @@ describe('Performance: speed-index-metric audit', () => { }; const artifacts = mockArtifactsWithSpeedlineResult(SpeedlineResult); - return Audit.audit(artifacts).then( + return Audit.audit(artifacts, {options}).then( _ => assert.ok(false), _ => assert.ok(true)); }); @@ -79,7 +80,7 @@ describe('Performance: speed-index-metric audit', () => { }; const artifacts = mockArtifactsWithSpeedlineResult(SpeedlineResult); - return Audit.audit(artifacts).then(response => { + return Audit.audit(artifacts, {options}).then(response => { assert.equal(response.rawValue, 845); assert.equal(response.extendedInfo.value.timings.firstVisualChange, 630); assert.equal(response.extendedInfo.value.timings.visuallyComplete, 930); diff --git a/lighthouse-core/test/config/config-test.js b/lighthouse-core/test/config/config-test.js index da71776164db..3d3c5c0261a0 100644 --- a/lighthouse-core/test/config/config-test.js +++ b/lighthouse-core/test/config/config-test.js @@ -60,8 +60,8 @@ describe('Config', () => { it('uses the default config when no config is provided', () => { const config = new Config(); - assert.deepStrictEqual(origConfig.categories, config.categories); - assert.equal(origConfig.audits.length, config.audits.length); + assert.deepStrictEqual(config.categories, origConfig.categories); + assert.equal(config.audits.length, origConfig.audits.length); }); it('throws when a passName is used twice', () => { diff --git a/typings/audit.d.ts b/typings/audit.d.ts index 5483f02275e4..1e5612eeb9b4 100644 --- a/typings/audit.d.ts +++ b/typings/audit.d.ts @@ -11,6 +11,11 @@ declare global { settings: ConfigSettings; } + export interface ScoreOptions { + scorePODR: number; + scoreMedian: number; + } + export interface ScoringModes { NUMERIC: 'numeric'; BINARY: 'binary';