lighthouse 8.4.0 → 8.5.0-dev.20210921

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/changelog.md +79 -0
  2. package/flow-report/assets/styles.css +85 -61
  3. package/flow-report/src/app.tsx +6 -2
  4. package/flow-report/src/common.tsx +0 -41
  5. package/flow-report/src/icons.tsx +11 -0
  6. package/flow-report/src/summary/summary.tsx +32 -25
  7. package/flow-report/src/topbar.tsx +60 -0
  8. package/flow-report/src/util.ts +0 -1
  9. package/flow-report/src/wrappers/{gauge.tsx → category-score.tsx} +11 -5
  10. package/flow-report/test/summary/summary-test.tsx +3 -3
  11. package/flow-report/tsconfig.json +6 -22
  12. package/{lighthouse-core/gather/gatherers/gather-context.js → lighthouse-cli/.eslintrc.cjs} +20 -18
  13. package/lighthouse-cli/bin.js +31 -21
  14. package/lighthouse-cli/cli-flags.js +12 -7
  15. package/lighthouse-cli/commands/commands.js +2 -7
  16. package/lighthouse-cli/commands/list-audits.js +2 -2
  17. package/lighthouse-cli/commands/list-trace-categories.js +2 -2
  18. package/lighthouse-cli/index.js +3 -1
  19. package/lighthouse-cli/package.json +4 -0
  20. package/lighthouse-cli/printer.js +4 -3
  21. package/lighthouse-cli/run.js +14 -15
  22. package/lighthouse-cli/sentry-prompt.js +5 -6
  23. package/lighthouse-cli/test/smokehouse/frontends/back-compat-util.js +1 -1
  24. package/lighthouse-cli/test/smokehouse/frontends/lib.js +5 -4
  25. package/lighthouse-cli/test/smokehouse/frontends/node.js +1 -1
  26. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +93 -14
  27. package/lighthouse-cli/test/smokehouse/lib/child-process-error.js +1 -1
  28. package/lighthouse-cli/test/smokehouse/lib/concurrent-mapper.js +1 -1
  29. package/lighthouse-cli/test/smokehouse/lib/local-console.js +1 -1
  30. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +20 -16
  31. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +15 -12
  32. package/lighthouse-cli/test/smokehouse/report-assert.js +23 -13
  33. package/lighthouse-cli/test/smokehouse/smokehouse.js +32 -11
  34. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +0 -2
  35. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +6 -1
  36. package/lighthouse-core/audits/dobetterweb/js-libraries.js +5 -0
  37. package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -0
  38. package/lighthouse-core/audits/lcp-lazy-loaded.js +1 -0
  39. package/lighthouse-core/audits/oopif-iframe-test-audit.js +30 -0
  40. package/lighthouse-core/computed/metrics/metric.js +2 -1
  41. package/lighthouse-core/config/config-helpers.js +47 -3
  42. package/lighthouse-core/config/config.js +3 -46
  43. package/lighthouse-core/config/default-config.js +0 -1
  44. package/lighthouse-core/fraggle-rock/config/config.js +22 -4
  45. package/lighthouse-core/fraggle-rock/config/default-config.js +0 -9
  46. package/lighthouse-core/fraggle-rock/config/filters.js +3 -0
  47. package/lighthouse-core/fraggle-rock/config/validation.js +19 -1
  48. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +8 -3
  49. package/lighthouse-core/fraggle-rock/gather/driver.js +10 -0
  50. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +40 -19
  51. package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +3 -0
  52. package/lighthouse-core/fraggle-rock/gather/session.js +44 -1
  53. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +4 -1
  54. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +5 -1
  55. package/lighthouse-core/gather/devtools-log.js +9 -3
  56. package/lighthouse-core/gather/driver/network-monitor.js +57 -13
  57. package/lighthouse-core/gather/driver/prepare.js +43 -17
  58. package/lighthouse-core/gather/driver/target-manager.js +125 -0
  59. package/lighthouse-core/gather/driver.js +20 -0
  60. package/lighthouse-core/gather/fetcher.js +2 -2
  61. package/lighthouse-core/gather/gather-runner.js +1 -1
  62. package/lighthouse-core/gather/gatherers/css-usage.js +0 -2
  63. package/lighthouse-core/gather/gatherers/devtools-log.js +11 -9
  64. package/lighthouse-core/gather/gatherers/js-usage.js +0 -1
  65. package/lighthouse-core/gather/gatherers/script-elements.js +4 -5
  66. package/lighthouse-core/gather/gatherers/seo/robots-txt.js +3 -3
  67. package/lighthouse-core/gather/gatherers/trace.js +4 -5
  68. package/lighthouse-core/index.js +4 -0
  69. package/lighthouse-core/lib/emulation.js +0 -1
  70. package/lighthouse-core/lib/i18n/locales/en-US.json +6 -0
  71. package/lighthouse-core/lib/i18n/locales/en-XL.json +6 -0
  72. package/lighthouse-core/lib/network-recorder.js +8 -0
  73. package/lighthouse-core/lib/proto-preprocessor.js +5 -10
  74. package/lighthouse-core/runner.js +1 -3
  75. package/package.json +12 -9
  76. package/readme.md +2 -0
  77. package/report/assets/styles.css +120 -12
  78. package/report/assets/templates.html +13 -0
  79. package/report/clients/psi.js +5 -1
  80. package/report/generator/tsconfig.json +5 -16
  81. package/report/renderer/category-renderer.js +58 -5
  82. package/report/renderer/components.js +25 -5
  83. package/report/renderer/performance-category-renderer.js +5 -5
  84. package/report/renderer/pwa-category-renderer.js +10 -0
  85. package/report/renderer/report-renderer.js +12 -3
  86. package/report/renderer/report-ui-features.js +1 -4
  87. package/report/test/renderer/category-renderer-test.js +19 -0
  88. package/report/tsconfig.json +6 -21
  89. package/report/types/report-result.d.ts +1 -1
  90. package/root.js +3 -1
  91. package/third-party/snyk/snapshot.json +2 -2
  92. package/tsconfig-all.json +15 -0
  93. package/tsconfig-base.json +24 -0
  94. package/tsconfig.json +8 -18
  95. package/types/artifacts.d.ts +7 -7
  96. package/types/config.d.ts +1 -1
  97. package/types/gatherer.d.ts +7 -1
  98. package/types/lhr/flow.d.ts +0 -1
  99. package/types/lhr/lhr.d.ts +5 -1
  100. package/types/lhr/tsconfig.json +4 -14
  101. package/types/protocol.d.ts +2 -0
  102. package/types/smokehouse.d.ts +2 -1
  103. package/dist/report/flow.js +0 -149
  104. package/dist/report/standalone.js +0 -228
  105. package/flow-report/test/common-test.tsx +0 -131
  106. package/lighthouse-core/gather/gatherers/host-form-factor.js +0 -31
  107. package/lighthouse-core/gather/gatherers/host-user-agent.js +0 -29
@@ -11,8 +11,9 @@
11
11
  * Currently uses `lighthouse-dt-bundle.js`.
12
12
  */
13
13
 
14
- const ChromeLauncher = require('chrome-launcher');
15
- const ChromeProtocol = require('../../../../lighthouse-core/gather/connections/cri.js');
14
+ import ChromeLauncher from 'chrome-launcher';
15
+
16
+ import ChromeProtocol from '../../../../lighthouse-core/gather/connections/cri.js';
16
17
 
17
18
  const originalRequire = global.require;
18
19
  if (typeof globalThis === 'undefined') {
@@ -22,7 +23,7 @@ if (typeof globalThis === 'undefined') {
22
23
 
23
24
  // Load bundle, which creates a `global.runBundledLighthouse`.
24
25
  // @ts-ignore - file exists if `yarn build-all` is run, but not used for types anyways.
25
- require('../../../../dist/lighthouse-dt-bundle.js'); // eslint-disable-line
26
+ import '../../../../dist/lighthouse-dt-bundle.js'; // eslint-disable-line
26
27
 
27
28
  global.require = originalRequire;
28
29
 
@@ -43,20 +44,23 @@ async function runLighthouse(url, configJson, testRunnerOptions = {}) {
43
44
  const port = launchedChrome.port;
44
45
  const connection = new ChromeProtocol(port);
45
46
 
46
- // Run Lighthouse.
47
- const logLevel = testRunnerOptions.isDebug ? 'info' : undefined;
48
- const runnerResult = await lighthouse(url, {port, logLevel}, configJson, connection);
49
- if (!runnerResult) throw new Error('No runnerResult');
50
-
51
- // Clean up and return results.
52
- await launchedChrome.kill();
53
- return {
54
- lhr: runnerResult.lhr,
55
- artifacts: runnerResult.artifacts,
56
- log: '', // TODO: if want to run in parallel, need to capture lighthouse-logger output.
57
- };
47
+ try {
48
+ // Run Lighthouse.
49
+ const logLevel = testRunnerOptions.isDebug ? 'info' : undefined;
50
+ const runnerResult = await lighthouse(url, {port, logLevel}, configJson, connection);
51
+ if (!runnerResult) throw new Error('No runnerResult');
52
+
53
+ return {
54
+ lhr: runnerResult.lhr,
55
+ artifacts: runnerResult.artifacts,
56
+ log: '', // TODO: if want to run in parallel, need to capture lighthouse-logger output.
57
+ };
58
+ } finally {
59
+ // Clean up and return results.
60
+ await launchedChrome.kill();
61
+ }
58
62
  }
59
63
 
60
- module.exports = {
64
+ export {
61
65
  runLighthouse,
62
66
  };
@@ -12,16 +12,18 @@
12
12
  * them.
13
13
  */
14
14
 
15
- const fs = require('fs').promises;
16
- const os = require('os');
17
- const {promisify} = require('util');
18
- const execFileAsync = promisify(require('child_process').execFile);
15
+ import {promises as fs} from 'fs';
16
+ import {promisify} from 'util';
17
+ import {execFile} from 'child_process';
19
18
 
20
- const log = require('lighthouse-logger');
19
+ import log from 'lighthouse-logger';
21
20
 
22
- const assetSaver = require('../../../../lighthouse-core/lib/asset-saver.js');
23
- const LocalConsole = require('../lib/local-console.js');
24
- const ChildProcessError = require('../lib/child-process-error.js');
21
+ import assetSaver from '../../../../lighthouse-core/lib/asset-saver.js';
22
+ import {LocalConsole} from '../lib/local-console.js';
23
+ import {ChildProcessError} from '../lib/child-process-error.js';
24
+ import {LH_ROOT} from '../../../../root.js';
25
+
26
+ const execFileAsync = promisify(execFile);
25
27
 
26
28
  /**
27
29
  * Launch Chrome and do a full Lighthouse run via the Lighthouse CLI.
@@ -31,9 +33,10 @@ const ChildProcessError = require('../lib/child-process-error.js');
31
33
  * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
32
34
  */
33
35
  async function runLighthouse(url, configJson, testRunnerOptions = {}) {
34
- const tmpPath = await fs.mkdtemp(`${os.tmpdir()}/smokehouse-`);
35
-
36
36
  const {isDebug} = testRunnerOptions;
37
+ const tmpDir = `${LH_ROOT}/.tmp/smokehouse`;
38
+ await fs.mkdir(tmpDir, {recursive: true});
39
+ const tmpPath = await fs.mkdtemp(`${tmpDir}/smokehouse-`);
37
40
  return internalRun(url, tmpPath, configJson, testRunnerOptions)
38
41
  // Wait for internalRun() before removing scratch directory.
39
42
  .finally(() => !isDebug && fs.rmdir(tmpPath, {recursive: true}));
@@ -69,7 +72,7 @@ async function internalRun(url, tmpPath, configJson, options) {
69
72
  const artifactsDirectory = `${tmpPath}/artifacts/`;
70
73
 
71
74
  const args = [
72
- `${__dirname}/../../../index.js`, // 'lighthouse-cli/index.js'
75
+ `${LH_ROOT}/lighthouse-cli/index.js`,
73
76
  `${url}`,
74
77
  `--output-path=${outputPath}`,
75
78
  '--output=json',
@@ -142,6 +145,6 @@ async function internalRun(url, tmpPath, configJson, options) {
142
145
  };
143
146
  }
144
147
 
145
- module.exports = {
148
+ export {
146
149
  runLighthouse,
147
150
  };
@@ -10,15 +10,10 @@
10
10
  * against the results actually collected from Lighthouse.
11
11
  */
12
12
 
13
- const cloneDeep = require('lodash.clonedeep');
14
- const log = require('lighthouse-logger');
15
- const LocalConsole = require('./lib/local-console.js');
13
+ import cloneDeep from 'lodash.clonedeep';
14
+ import log from 'lighthouse-logger';
16
15
 
17
- const NUMBER_REGEXP = /(?:\d|\.)+/.source;
18
- const OPS_REGEXP = /<=?|>=?|\+\/-|±/.source;
19
- // An optional number, optional whitespace, an operator, optional whitespace, a number.
20
- const NUMERICAL_EXPECTATION_REGEXP =
21
- new RegExp(`^(${NUMBER_REGEXP})?\\s*(${OPS_REGEXP})\\s*(${NUMBER_REGEXP})$`);
16
+ import {LocalConsole} from './lib/local-console.js';
22
17
 
23
18
  /**
24
19
  * @typedef Difference
@@ -36,6 +31,12 @@ const NUMERICAL_EXPECTATION_REGEXP =
36
31
  * @property {Difference|null} [diff]
37
32
  */
38
33
 
34
+ const NUMBER_REGEXP = /(?:\d|\.)+/.source;
35
+ const OPS_REGEXP = /<=?|>=?|\+\/-|±/.source;
36
+ // An optional number, optional whitespace, an operator, optional whitespace, a number.
37
+ const NUMERICAL_EXPECTATION_REGEXP =
38
+ new RegExp(`^(${NUMBER_REGEXP})?\\s*(${OPS_REGEXP})\\s*(${NUMBER_REGEXP})$`);
39
+
39
40
  /**
40
41
  * Checks if the actual value matches the expectation. Does not recursively search. This supports
41
42
  * - Greater than/less than operators, e.g. "<100", ">90"
@@ -157,9 +158,11 @@ function makeComparison(name, actualResult, expectedResult) {
157
158
  * @param {LocalConsole} localConsole
158
159
  * @param {LH.Result} lhr
159
160
  * @param {Smokehouse.ExpectedRunnerResult} expected
161
+ * @param {{isBundled?: boolean}=} reportOptions
160
162
  */
161
- function pruneExpectations(localConsole, lhr, expected) {
163
+ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
162
164
  const isFraggleRock = lhr.configSettings.channel === 'fraggle-rock-cli';
165
+ const isBundled = reportOptions && reportOptions.isBundled;
163
166
 
164
167
  /**
165
168
  * Lazily compute the Chrome version because some reports are explicitly asserting error conditions.
@@ -211,6 +214,12 @@ function pruneExpectations(localConsole, lhr, expected) {
211
214
  `Actual channel: ${lhr.configSettings.channel}`,
212
215
  ].join(' '));
213
216
  delete obj[key];
217
+ } else if (value._skipInBundled && !isBundled) {
218
+ localConsole.log([
219
+ `[${key}] marked as skip in bundled and runner is bundled, pruning expectation:`,
220
+ JSON.stringify(value, null, 2),
221
+ ].join(' '));
222
+ delete obj[key];
214
223
  } else {
215
224
  pruneRecursively(value);
216
225
  }
@@ -218,6 +227,7 @@ function pruneExpectations(localConsole, lhr, expected) {
218
227
 
219
228
  delete obj._legacyOnly;
220
229
  delete obj._fraggleRockOnly;
230
+ delete obj._skipInBundled;
221
231
  delete obj._minChromiumMilestone;
222
232
  delete obj._maxChromiumMilestone;
223
233
  }
@@ -358,13 +368,13 @@ function reportAssertion(localConsole, assertion) {
358
368
  * summary. Returns count of passed and failed tests.
359
369
  * @param {{lhr: LH.Result, artifacts: LH.Artifacts, networkRequests?: string[]}} actual
360
370
  * @param {Smokehouse.ExpectedRunnerResult} expected
361
- * @param {{isDebug?: boolean}=} reportOptions
371
+ * @param {{isDebug?: boolean, isBundled?: boolean}=} reportOptions
362
372
  * @return {{passed: number, failed: number, log: string}}
363
373
  */
364
- function report(actual, expected, reportOptions = {}) {
374
+ function getAssertionReport(actual, expected, reportOptions = {}) {
365
375
  const localConsole = new LocalConsole();
366
376
 
367
- expected = pruneExpectations(localConsole, actual.lhr, expected);
377
+ expected = pruneExpectations(localConsole, actual.lhr, expected, reportOptions);
368
378
  const comparisons = collateResults(localConsole, actual, expected);
369
379
 
370
380
  let correctCount = 0;
@@ -389,4 +399,4 @@ function report(actual, expected, reportOptions = {}) {
389
399
  };
390
400
  }
391
401
 
392
- module.exports = report;
402
+ export {getAssertionReport};
@@ -11,27 +11,38 @@
11
11
  * smoke tests passed.
12
12
  */
13
13
 
14
- const log = require('lighthouse-logger');
15
- const cliLighthouseRunner = require('./lighthouse-runners/cli.js').runLighthouse;
16
- const getAssertionReport = require('./report-assert.js');
17
- const LocalConsole = require('./lib/local-console.js');
18
- const ConcurrentMapper = require('./lib/concurrent-mapper.js');
19
-
20
14
  /* eslint-disable no-console */
21
15
 
22
- /** @typedef {import('./lib/child-process-error.js')} ChildProcessError */
16
+ /** @typedef {import('./lib/child-process-error.js').ChildProcessError} ChildProcessError */
23
17
 
24
- // The number of concurrent (`!runSerially`) tests to run if `jobs` isn't set.
25
- const DEFAULT_CONCURRENT_RUNS = 5;
26
- const DEFAULT_RETRIES = 0;
18
+ /**
19
+ * @typedef Run
20
+ * @property {string[] | undefined} networkRequests
21
+ * @property {LH.Result} lhr
22
+ * @property {LH.Artifacts} artifacts
23
+ * @property {string} lighthouseLog
24
+ * @property {string} assertionLog
25
+ */
27
26
 
28
27
  /**
29
28
  * @typedef SmokehouseResult
30
29
  * @property {string} id
31
30
  * @property {number} passed
32
31
  * @property {number} failed
32
+ * @property {Run[]} runs
33
33
  */
34
34
 
35
+ import log from 'lighthouse-logger';
36
+
37
+ import {runLighthouse as cliLighthouseRunner} from './lighthouse-runners/cli.js';
38
+ import {getAssertionReport} from './report-assert.js';
39
+ import {LocalConsole} from './lib/local-console.js';
40
+ import {ConcurrentMapper} from './lib/concurrent-mapper.js';
41
+
42
+ // The number of concurrent (`!runSerially`) tests to run if `jobs` isn't set.
43
+ const DEFAULT_CONCURRENT_RUNS = 5;
44
+ const DEFAULT_RETRIES = 0;
45
+
35
46
  /**
36
47
  * Runs the selected smoke tests. Returns whether all assertions pass.
37
48
  * @param {Array<Smokehouse.TestDfn>} smokeTestDefns
@@ -120,6 +131,8 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
120
131
  console.log(`${purpleify(id)} smoketest starting…`);
121
132
 
122
133
  // Rerun test until there's a passing result or retries are exhausted to prevent flakes.
134
+ /** @type {Run[]} */
135
+ const runs = [];
123
136
  let result;
124
137
  let report;
125
138
  const bufferedConsole = new LocalConsole();
@@ -145,6 +158,13 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
145
158
 
146
159
  // Assert result.
147
160
  report = getAssertionReport(result, expectations, {isDebug});
161
+
162
+ runs.push({
163
+ ...result,
164
+ lighthouseLog: result.log,
165
+ assertionLog: report.log,
166
+ });
167
+
148
168
  if (report.failed) {
149
169
  bufferedConsole.log(` ${getAssertionLog(report.failed)} failed.`);
150
170
  continue; // Retry, if possible.
@@ -180,6 +200,7 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
180
200
  id,
181
201
  passed,
182
202
  failed,
203
+ runs,
183
204
  };
184
205
  }
185
206
 
@@ -206,6 +227,6 @@ function getAssertionLog(count) {
206
227
  return `${count} assertion${plural}`;
207
228
  }
208
229
 
209
- module.exports = {
230
+ export {
210
231
  runSmokehouse,
211
232
  };
@@ -129,7 +129,6 @@ class UnusedBytes extends Audit {
129
129
  const [result, graph, simulator] = await Promise.all([
130
130
  this.audit_(artifacts, networkRecords, context),
131
131
  // Page dependency graph is only used in navigation mode.
132
- // TODO(FR-COMPAT): Use dependency graph in timespan mode.
133
132
  gatherContext.gatherMode === 'navigation' ?
134
133
  PageDependencyGraph.request({trace, devtoolsLog}, context) :
135
134
  null,
@@ -201,7 +200,6 @@ class UnusedBytes extends Audit {
201
200
  }
202
201
 
203
202
  /**
204
- * TODO(FR-COMPAT): Rework opportunities to remove emphasis on `wastedMs`
205
203
  * @param {number} wastedBytes
206
204
  * @param {Simulator} simulator
207
205
  */
@@ -17,6 +17,10 @@ const URL = require('../../lib/url-shim.js');
17
17
  const i18n = require('../../lib/i18n/i18n.js');
18
18
 
19
19
  const UIStrings = {
20
+ /** Descriptive title of a Lighthouse audit that checks if images match their displayed dimensions. This is displayed when the audit is passing. */
21
+ title: 'Images were appropriate for their displayed size',
22
+ /** Descriptive title of a Lighthouse audit that checks if images match their displayed dimensions. This is displayed when the audit is failing. */
23
+ failureTitle: 'Images were larger than their displayed size',
20
24
  /** Label for a column in a data table; entries will be the dimensions of an image as it appears on the page. */
21
25
  columnDisplayedDimensions: 'Displayed dimensions',
22
26
  /** Label for a column in a data table; entries will be the dimensions of an image from it's source file. */
@@ -35,7 +39,8 @@ class UsesResponsiveImagesSnapshot extends Audit {
35
39
  static get meta() {
36
40
  return {
37
41
  id: 'uses-responsive-images-snapshot',
38
- title: UsesResponsiveImages.str_(UsesResponsiveImages.UIStrings.title),
42
+ title: str_(UIStrings.title),
43
+ failureTitle: str_(UIStrings.failureTitle),
39
44
  description: UsesResponsiveImages.str_(UsesResponsiveImages.UIStrings.description),
40
45
  supportedModes: ['snapshot'],
41
46
  requiredArtifacts: ['ImageElements', 'ViewportDimensions'],
@@ -32,6 +32,7 @@ class JsLibrariesAudit extends Audit {
32
32
  return {
33
33
  id: 'js-libraries',
34
34
  title: str_(UIStrings.title),
35
+ scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
35
36
  description: str_(UIStrings.description),
36
37
  requiredArtifacts: ['Stacks'],
37
38
  };
@@ -69,6 +70,10 @@ class JsLibrariesAudit extends Audit {
69
70
  }),
70
71
  };
71
72
 
73
+ if (!libDetails.length) {
74
+ return {score: null, notApplicable: true};
75
+ }
76
+
72
77
  return {
73
78
  score: 1, // Always pass for now.
74
79
  details: {
@@ -60,6 +60,7 @@ class UsesHTTP2Audit extends Audit {
60
60
  id: 'uses-http2',
61
61
  title: str_(UIStrings.title),
62
62
  description: str_(UIStrings.description),
63
+ scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
63
64
  supportedModes: ['navigation'],
64
65
  requiredArtifacts: ['URL', 'devtoolsLogs', 'traces'],
65
66
  };
@@ -27,6 +27,7 @@ class LargestContentfulPaintLazyLoaded extends Audit {
27
27
  return {
28
28
  id: 'lcp-lazy-loaded',
29
29
  title: str_(UIStrings.title),
30
+ failureTitle: str_(UIStrings.failureTitle),
30
31
  description: str_(UIStrings.description),
31
32
  supportedModes: ['navigation'],
32
33
  requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements'],
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+ 'use strict';
7
+
8
+ /**
9
+ * @fileoverview This is a fake audit used exclusively in smoke tests to force inclusion of IFrameElements artifact.
10
+ * It is included here for complex reasons in the way the bundled smoketests work.
11
+ *
12
+ * The smokehouse configs are evaluated first in the node CLI side (which requires an absolute path using LH_ROOT).
13
+ * The smokehouse configs are then *re-evaluated* in the bundled context for execution by Lighthouse (which *cannot* use an absolute path using LH_ROOT).
14
+ *
15
+ * This mismatch in environment demands that the audit path in the config must be context-aware,
16
+ * yet the require-graph for the config is included before even the CLI knows that it will be using
17
+ * a bundled runner. Rather than force a massive smoketest architecture change, we include a harmless,
18
+ * test-only audit in our core list instead.
19
+ */
20
+
21
+ module.exports = {
22
+ meta: {
23
+ id: 'oopif-iframe-test-audit',
24
+ title: 'IFrame Elements',
25
+ failureTitle: 'IFrame Elements',
26
+ description: 'Audit to force the inclusion of IFrameElements artifact',
27
+ requiredArtifacts: ['IFrameElements'],
28
+ },
29
+ audit: () => ({score: 1}),
30
+ };
@@ -69,7 +69,8 @@ class Metric {
69
69
  }
70
70
 
71
71
  const processedTrace = await ProcessedTrace.request(trace, context);
72
- const processedNavigation = await ProcessedNavigation.request(processedTrace, context);
72
+ const processedNavigation = gatherContext.gatherMode === 'timespan' ?
73
+ undefined : await ProcessedNavigation.request(processedTrace, context);
73
74
 
74
75
  const augmentedData = Object.assign({
75
76
  networkRecords: await NetworkRecords.request(devtoolsLog, context),
@@ -9,6 +9,7 @@ const path = require('path');
9
9
  const isDeepEqual = require('lodash.isequal');
10
10
  const constants = require('./constants.js');
11
11
  const Budget = require('./budget.js');
12
+ const ConfigPlugin = require('./config-plugin.js');
12
13
  const Runner = require('../runner.js');
13
14
  const i18n = require('../lib/i18n/i18n.js');
14
15
  const validation = require('../fraggle-rock/config/validation.js');
@@ -17,6 +18,21 @@ const validation = require('../fraggle-rock/config/validation.js');
17
18
  /** @typedef {typeof import('../audits/audit.js')} Audit */
18
19
  /** @typedef {InstanceType<GathererConstructor>} Gatherer */
19
20
 
21
+ function isBundledEnvironment() {
22
+ // If we're in DevTools or LightRider, we are definitely bundled.
23
+ // TODO: refactor and delete `global.isDevtools`.
24
+ if (global.isDevtools || global.isLightrider) return true;
25
+
26
+ try {
27
+ // Not foolproof, but `lighthouse-logger` is a dependency of lighthouse that should always be resolvable.
28
+ // `require.resolve` will only throw in atypical/bundled environments.
29
+ require.resolve('lighthouse-logger');
30
+ return false;
31
+ } catch (err) {
32
+ return true;
33
+ }
34
+ }
35
+
20
36
  /**
21
37
  * If any items with identical `path` properties are found in the input array,
22
38
  * merge their `options` properties into the first instance and then discard any
@@ -224,9 +240,8 @@ function requireAudit(auditPath, coreAuditList, configDir) {
224
240
  const coreAudit = coreAuditList.find(a => a === auditPathJs);
225
241
  let requirePath = `../audits/${auditPath}`;
226
242
  if (!coreAudit) {
227
- // TODO: refactor and delete `global.isDevtools`.
228
- if (global.isDevtools || global.isLightrider) {
229
- // This is for pubads bundling.
243
+ if (isBundledEnvironment()) {
244
+ // This is for pubads bundling.
230
245
  requirePath = auditPath;
231
246
  } else {
232
247
  // Otherwise, attempt to find it elsewhere. This throws if not found.
@@ -298,6 +313,34 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
298
313
  }
299
314
 
300
315
 
316
+ /**
317
+ * @param {LH.Config.Json} configJSON
318
+ * @param {string | undefined} configDir
319
+ * @param {{plugins?: string[]} | undefined} flags
320
+ * @return {LH.Config.Json}
321
+ */
322
+ function mergePlugins(configJSON, configDir, flags) {
323
+ const configPlugins = configJSON.plugins || [];
324
+ const flagPlugins = (flags && flags.plugins) || [];
325
+ const pluginNames = new Set([...configPlugins, ...flagPlugins]);
326
+
327
+ for (const pluginName of pluginNames) {
328
+ validation.assertValidPluginName(configJSON, pluginName);
329
+
330
+ // In bundled contexts, `resolveModulePath` will fail, so use the raw pluginName directly.
331
+ const pluginPath = isBundledEnvironment() ?
332
+ pluginName :
333
+ resolveModulePath(pluginName, configDir, 'plugin');
334
+ const rawPluginJson = require(pluginPath);
335
+ const pluginJson = ConfigPlugin.parsePlugin(rawPluginJson, pluginName);
336
+
337
+ configJSON = mergeConfigFragment(configJSON, pluginJson);
338
+ }
339
+
340
+ return configJSON;
341
+ }
342
+
343
+
301
344
  /**
302
345
  * Turns a GathererJson into a GathererDefn which involves a few main steps:
303
346
  * - Expanding the JSON shorthand the full definition format.
@@ -528,6 +571,7 @@ module.exports = {
528
571
  mergeOptionsOfItems,
529
572
  mergeConfigFragment,
530
573
  mergeConfigFragmentArrayByKey,
574
+ mergePlugins,
531
575
  resolveSettings,
532
576
  resolveGathererToDefn,
533
577
  resolveAuditsToDefns,
@@ -14,13 +14,12 @@ const validation = require('./../fraggle-rock/config/validation.js');
14
14
  const log = require('lighthouse-logger');
15
15
  const path = require('path');
16
16
  const Runner = require('../runner.js');
17
- const ConfigPlugin = require('./config-plugin.js');
18
17
  const {
18
+ mergePlugins,
19
19
  mergeConfigFragment,
20
20
  resolveSettings,
21
21
  resolveAuditsToDefns,
22
22
  resolveGathererToDefn,
23
- resolveModulePath,
24
23
  deepClone,
25
24
  deepCloneConfigJson,
26
25
  } = require('./config-helpers.js');
@@ -40,6 +39,7 @@ const BASE_ARTIFACT_BLANKS = {
40
39
  NetworkUserAgent: '',
41
40
  BenchmarkIndex: '',
42
41
  WebAppManifest: '',
42
+ GatherContext: '',
43
43
  InstallabilityErrors: '',
44
44
  Stacks: '',
45
45
  traces: '',
@@ -148,22 +148,6 @@ function assertValidFlags(flags) {
148
148
  }
149
149
  }
150
150
 
151
- /**
152
- * Throws if pluginName is invalid or (somehow) collides with a category in the
153
- * configJSON being added to.
154
- * @param {LH.Config.Json} configJSON
155
- * @param {string} pluginName
156
- */
157
- function assertValidPluginName(configJSON, pluginName) {
158
- if (!pluginName.startsWith('lighthouse-plugin-')) {
159
- throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
160
- }
161
-
162
- if (configJSON.categories && configJSON.categories[pluginName]) {
163
- throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
164
- }
165
- }
166
-
167
151
 
168
152
  /**
169
153
  * @implements {LH.Config.Config}
@@ -203,7 +187,7 @@ class Config {
203
187
  const configDir = configPath ? path.dirname(configPath) : undefined;
204
188
 
205
189
  // Validate and merge in plugins (if any).
206
- configJSON = Config.mergePlugins(configJSON, flags, configDir);
190
+ configJSON = mergePlugins(configJSON, configDir, flags);
207
191
 
208
192
  if (flags) {
209
193
  assertValidFlags(flags);
@@ -294,33 +278,6 @@ class Config {
294
278
  return mergeConfigFragment(baseJSON, extendJSON);
295
279
  }
296
280
 
297
- /**
298
- * @param {LH.Config.Json} configJSON
299
- * @param {LH.Flags=} flags
300
- * @param {string=} configDir
301
- * @return {LH.Config.Json}
302
- */
303
- static mergePlugins(configJSON, flags, configDir) {
304
- const configPlugins = configJSON.plugins || [];
305
- const flagPlugins = (flags && flags.plugins) || [];
306
- const pluginNames = new Set([...configPlugins, ...flagPlugins]);
307
-
308
- for (const pluginName of pluginNames) {
309
- assertValidPluginName(configJSON, pluginName);
310
-
311
- // TODO: refactor and delete `global.isDevtools`.
312
- const pluginPath = global.isDevtools || global.isLightrider ?
313
- pluginName :
314
- resolveModulePath(pluginName, configDir, 'plugin');
315
- const rawPluginJson = require(pluginPath);
316
- const pluginJson = ConfigPlugin.parsePlugin(rawPluginJson, pluginName);
317
-
318
- configJSON = Config.extendConfigJSON(configJSON, pluginJson);
319
- }
320
-
321
- return configJSON;
322
- }
323
-
324
281
  /**
325
282
  * @param {LH.Config.Json['passes']} passes
326
283
  * @return {?Array<Required<LH.Config.PassJson>>}
@@ -146,7 +146,6 @@ const defaultConfig = {
146
146
  'iframe-elements',
147
147
  'form-elements',
148
148
  'main-document-content',
149
- 'gather-context',
150
149
  'global-listeners',
151
150
  'dobetterweb/appcache',
152
151
  'dobetterweb/doctype',
@@ -24,11 +24,14 @@ const {
24
24
  resolveSettings,
25
25
  resolveAuditsToDefns,
26
26
  resolveGathererToDefn,
27
+ mergePlugins,
27
28
  mergeConfigFragment,
28
29
  mergeConfigFragmentArrayByKey,
29
30
  } = require('../../config/config-helpers.js');
30
31
  const defaultConfigPath = path.join(__dirname, './default-config.js');
31
32
 
33
+ /** @typedef {Omit<LH.Config.FRContext, 'gatherMode'> & {gatherMode: LH.Gatherer.GatherMode}} ConfigContext */
34
+
32
35
  /**
33
36
  * @param {LH.Config.Json|undefined} configJSON
34
37
  * @param {{configPath?: string}} context
@@ -132,7 +135,7 @@ function resolveArtifactsToDefns(artifacts, configDir) {
132
135
  const coreGathererList = Runner.getGathererList();
133
136
  const artifactDefns = artifacts.map(artifactJson => {
134
137
  /** @type {LH.Config.GathererJson} */
135
- // @ts-expect-error FR-COMPAT - eventually move the config-helpers to support new types
138
+ // @ts-expect-error - remove when legacy runner path is removed.
136
139
  const gathererJson = artifactJson.gatherer;
137
140
 
138
141
  const gatherer = resolveGathererToDefn(gathererJson, coreGathererList, configDir);
@@ -158,6 +161,20 @@ function resolveArtifactsToDefns(artifacts, configDir) {
158
161
  return artifactDefns;
159
162
  }
160
163
 
164
+ /**
165
+ * Overrides the settings that may not apply to the chosen gather mode.
166
+ *
167
+ * @param {LH.Config.Settings} settings
168
+ * @param {ConfigContext} context
169
+ */
170
+ function overrideSettingsForGatherMode(settings, context) {
171
+ if (context.gatherMode === 'timespan') {
172
+ if (settings.throttlingMethod === 'simulate') {
173
+ settings.throttlingMethod = 'devtools';
174
+ }
175
+ }
176
+ }
177
+
161
178
  /**
162
179
  * Overrides the quiet windows when throttlingMethod requires observation.
163
180
  *
@@ -224,7 +241,7 @@ function resolveNavigationsToDefns(navigations, artifactDefns, settings) {
224
241
 
225
242
  /**
226
243
  * @param {LH.Config.Json|undefined} configJSON
227
- * @param {Omit<LH.Config.FRContext, 'gatherMode'> & {gatherMode: LH.Gatherer.GatherMode}} context
244
+ * @param {ConfigContext} context
228
245
  * @return {{config: LH.Config.FRConfig, warnings: string[]}}
229
246
  */
230
247
  function initializeConfig(configJSON, context) {
@@ -234,10 +251,11 @@ function initializeConfig(configJSON, context) {
234
251
  let {configWorkingCopy, configDir} = resolveWorkingCopy(configJSON, context); // eslint-disable-line prefer-const
235
252
 
236
253
  configWorkingCopy = resolveExtensions(configWorkingCopy);
237
-
238
- // TODO(FR-COMPAT): handle config plugins
254
+ configWorkingCopy = mergePlugins(configWorkingCopy, configDir, context.settingsOverrides);
239
255
 
240
256
  const settings = resolveSettings(configWorkingCopy.settings || {}, context.settingsOverrides);
257
+ overrideSettingsForGatherMode(settings, context);
258
+
241
259
  const artifacts = resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir);
242
260
  const navigations = resolveNavigationsToDefns(configWorkingCopy.navigations, artifacts, settings);
243
261