lighthouse 9.5.0-dev.20220815 → 9.5.0-dev.20220816

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 (101) hide show
  1. package/cli/commands/list-locales.js +1 -1
  2. package/core/config/config-helpers.js +0 -15
  3. package/core/config/config.js +1 -1
  4. package/core/config/constants.js +1 -0
  5. package/core/config/default-config.js +0 -2
  6. package/core/fraggle-rock/api.js +1 -1
  7. package/core/fraggle-rock/config/config.js +12 -13
  8. package/core/fraggle-rock/gather/navigation-runner.js +17 -26
  9. package/core/fraggle-rock/gather/snapshot-runner.js +4 -5
  10. package/core/fraggle-rock/gather/timespan-runner.js +4 -5
  11. package/core/fraggle-rock/replay/stringify-extension.js +105 -0
  12. package/core/fraggle-rock/user-flow.js +10 -12
  13. package/core/gather/driver/execution-context.js +19 -2
  14. package/core/gather/gather-runner.js +1 -1
  15. package/core/index.js +2 -5
  16. package/core/lib/asset-saver.js +2 -2
  17. package/core/lib/page-functions.js +15 -3
  18. package/core/lib/traces/{pwmetrics-events.js → metric-trace-events.js} +3 -4
  19. package/core/runner.js +2 -2
  20. package/dist/report/bundle.esm.js +1 -14
  21. package/dist/report/flow.js +28 -27
  22. package/dist/report/standalone.js +4 -4
  23. package/flow-report/test/flow-report-pptr-test.ts +1 -1
  24. package/package.json +2 -1
  25. package/report/generator/file-namer.js +5 -1
  26. package/report/generator/flow-report-assets.js +10 -6
  27. package/report/generator/report-assets.js +8 -5
  28. package/report/generator/report-generator.js +7 -7
  29. package/report/generator/tsconfig.json +1 -0
  30. package/report/test/generator/file-namer-test.js +2 -2
  31. package/report/test/generator/report-generator-test.js +8 -9
  32. package/report/test/renderer/report-renderer-axe-test.js +2 -2
  33. package/report/test/renderer/report-ui-features-test.js +1 -1
  34. package/shared/localization/format.js +11 -13
  35. package/shared/localization/i18n-module.js +7 -2
  36. package/shared/localization/locales/ar-XB.json +0 -21
  37. package/shared/localization/locales/ar.json +0 -21
  38. package/shared/localization/locales/bg.json +0 -21
  39. package/shared/localization/locales/ca.json +0 -21
  40. package/shared/localization/locales/cs.json +0 -21
  41. package/shared/localization/locales/da.json +0 -21
  42. package/shared/localization/locales/de.json +0 -21
  43. package/shared/localization/locales/el.json +0 -21
  44. package/shared/localization/locales/en-GB.json +0 -21
  45. package/shared/localization/locales/en-US.json +0 -21
  46. package/shared/localization/locales/en-XA.json +0 -21
  47. package/shared/localization/locales/en-XL.json +0 -21
  48. package/shared/localization/locales/es-419.json +0 -21
  49. package/shared/localization/locales/es.json +0 -21
  50. package/shared/localization/locales/fi.json +0 -21
  51. package/shared/localization/locales/fil.json +0 -21
  52. package/shared/localization/locales/fr.json +0 -21
  53. package/shared/localization/locales/he.json +0 -21
  54. package/shared/localization/locales/hi.json +0 -21
  55. package/shared/localization/locales/hr.json +0 -21
  56. package/shared/localization/locales/hu.json +0 -21
  57. package/shared/localization/locales/id.json +0 -21
  58. package/shared/localization/locales/it.json +0 -21
  59. package/shared/localization/locales/ja.json +0 -21
  60. package/shared/localization/locales/ko.json +0 -21
  61. package/shared/localization/locales/lt.json +0 -21
  62. package/shared/localization/locales/lv.json +0 -21
  63. package/shared/localization/locales/nl.json +0 -21
  64. package/shared/localization/locales/no.json +0 -21
  65. package/shared/localization/locales/pl.json +0 -21
  66. package/shared/localization/locales/pt-PT.json +0 -21
  67. package/shared/localization/locales/pt.json +0 -21
  68. package/shared/localization/locales/ro.json +0 -21
  69. package/shared/localization/locales/ru.json +0 -21
  70. package/shared/localization/locales/sk.json +0 -21
  71. package/shared/localization/locales/sl.json +0 -21
  72. package/shared/localization/locales/sr-Latn.json +0 -21
  73. package/shared/localization/locales/sr.json +0 -21
  74. package/shared/localization/locales/sv.json +0 -21
  75. package/shared/localization/locales/ta.json +0 -21
  76. package/shared/localization/locales/te.json +0 -21
  77. package/shared/localization/locales/th.json +0 -21
  78. package/shared/localization/locales/tr.json +0 -21
  79. package/shared/localization/locales/uk.json +0 -21
  80. package/shared/localization/locales/vi.json +0 -21
  81. package/shared/localization/locales/zh-HK.json +0 -21
  82. package/shared/localization/locales/zh-TW.json +0 -21
  83. package/shared/localization/locales/zh.json +0 -21
  84. package/shared/localization/locales.js +53 -50
  85. package/shared/localization/swap-flow-locale.js +6 -2
  86. package/shared/localization/swap-locale.js +4 -4
  87. package/shared/test/localization/format-test.js +17 -20
  88. package/shared/test/localization/locales-test.js +2 -2
  89. package/shared/test/localization/swap-locale-test.js +3 -2
  90. package/shared/tsconfig.json +1 -0
  91. package/shared/type-verifiers.js +1 -1
  92. package/tsconfig.json +1 -5
  93. package/types/config.d.ts +0 -13
  94. package/types/externs.d.ts +1 -1
  95. package/types/lhr/settings.d.ts +2 -0
  96. package/types/user-flow.d.ts +1 -1
  97. package/core/audits/dobetterweb/no-vulnerable-libraries.js +0 -229
  98. package/report/generator/package.json +0 -4
  99. package/report/test/generator/package.json +0 -4
  100. package/shared/package.json +0 -4
  101. package/third-party/snyk/snapshot.json +0 -182
@@ -4,7 +4,7 @@
4
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
5
  */
6
6
 
7
- import locales from '../../shared/localization/locales.js';
7
+ import {locales} from '../../shared/localization/locales.js';
8
8
 
9
9
  function listLocales() {
10
10
  const localesList = Object.keys(locales);
@@ -606,20 +606,6 @@ function deepCloneConfigJson(json) {
606
606
  return cloned;
607
607
  }
608
608
 
609
- /**
610
- * @param {LH.Flags} flags
611
- * @return {LH.Config.FRContext}
612
- */
613
- function flagsToFRContext(flags) {
614
- return {
615
- configPath: flags?.configPath,
616
- settingsOverrides: flags,
617
- logLevel: flags?.logLevel,
618
- hostname: flags?.hostname,
619
- port: flags?.port,
620
- };
621
- }
622
-
623
609
  export {
624
610
  deepClone,
625
611
  deepCloneConfigJson,
@@ -631,5 +617,4 @@ export {
631
617
  resolveGathererToDefn,
632
618
  resolveModulePath,
633
619
  resolveSettings,
634
- flagsToFRContext,
635
620
  };
@@ -7,7 +7,7 @@
7
7
 
8
8
  import legacyDefaultConfig from './legacy-default-config.js';
9
9
  import * as constants from './constants.js';
10
- import format from '../../shared/localization/format.js';
10
+ import * as format from '../../shared/localization/format.js';
11
11
  import * as validation from './../fraggle-rock/config/validation.js';
12
12
  import log from 'lighthouse-logger';
13
13
  import path from 'path';
@@ -119,6 +119,7 @@ const defaultSettings = {
119
119
  onlyAudits: null,
120
120
  onlyCategories: null,
121
121
  skipAudits: null,
122
+ skipAboutBlank: false,
122
123
  };
123
124
 
124
125
  /** @type {LH.Config.Pass} */
@@ -413,7 +413,6 @@ const defaultConfig = {
413
413
  'dobetterweb/geolocation-on-start',
414
414
  'dobetterweb/inspector-issues',
415
415
  'dobetterweb/no-document-write',
416
- 'dobetterweb/no-vulnerable-libraries',
417
416
  'dobetterweb/js-libraries',
418
417
  'dobetterweb/notification-on-start',
419
418
  'dobetterweb/password-inputs-can-be-pasted-into',
@@ -666,7 +665,6 @@ const defaultConfig = {
666
665
  {id: 'is-on-https', weight: 1, group: 'best-practices-trust-safety'},
667
666
  {id: 'geolocation-on-start', weight: 1, group: 'best-practices-trust-safety'},
668
667
  {id: 'notification-on-start', weight: 1, group: 'best-practices-trust-safety'},
669
- {id: 'no-vulnerable-libraries', weight: 1, group: 'best-practices-trust-safety'},
670
668
  {id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
671
669
  // User Experience
672
670
  {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'},
@@ -9,7 +9,7 @@ import {UserFlow, auditGatherSteps} from './user-flow.js';
9
9
  import {snapshotGather} from './gather/snapshot-runner.js';
10
10
  import {startTimespanGather} from './gather/timespan-runner.js';
11
11
  import {navigationGather} from './gather/navigation-runner.js';
12
- import ReportGenerator from '../../report/generator/report-generator.js';
12
+ import {ReportGenerator} from '../../report/generator/report-generator.js';
13
13
  import {Runner} from '../runner.js';
14
14
 
15
15
  /**
@@ -40,8 +40,6 @@ const defaultConfigPath = path.join(
40
40
  '../../config/default-config.js'
41
41
  );
42
42
 
43
- /** @typedef {LH.Config.FRContext & {gatherMode: LH.Gatherer.GatherMode}} ConfigContext */
44
-
45
43
  /**
46
44
  * @param {LH.Config.Json|undefined} configJSON
47
45
  * @param {{configPath?: string}} context
@@ -176,10 +174,10 @@ async function resolveArtifactsToDefns(artifacts, configDir) {
176
174
  * Overrides the settings that may not apply to the chosen gather mode.
177
175
  *
178
176
  * @param {LH.Config.Settings} settings
179
- * @param {ConfigContext} context
177
+ * @param {LH.Gatherer.GatherMode} gatherMode
180
178
  */
181
- function overrideSettingsForGatherMode(settings, context) {
182
- if (context.gatherMode === 'timespan') {
179
+ function overrideSettingsForGatherMode(settings, gatherMode) {
180
+ if (gatherMode === 'timespan') {
183
181
  if (settings.throttlingMethod === 'simulate') {
184
182
  settings.throttlingMethod = 'devtools';
185
183
  }
@@ -251,21 +249,22 @@ function resolveNavigationsToDefns(navigations, artifactDefns, settings) {
251
249
  }
252
250
 
253
251
  /**
254
- * @param {LH.Config.Json|undefined} configJSON
255
- * @param {ConfigContext} context
252
+ * @param {LH.Gatherer.GatherMode} gatherMode
253
+ * @param {LH.Config.Json=} configJSON
254
+ * @param {LH.Flags=} flags
256
255
  * @return {Promise<{config: LH.Config.FRConfig, warnings: string[]}>}
257
256
  */
258
- async function initializeConfig(configJSON, context) {
257
+ async function initializeConfig(gatherMode, configJSON, flags = {}) {
259
258
  const status = {msg: 'Initialize config', id: 'lh:config'};
260
259
  log.time(status, 'verbose');
261
260
 
262
- let {configWorkingCopy, configDir} = resolveWorkingCopy(configJSON, context); // eslint-disable-line prefer-const
261
+ let {configWorkingCopy, configDir} = resolveWorkingCopy(configJSON, flags);
263
262
 
264
263
  configWorkingCopy = resolveExtensions(configWorkingCopy);
265
- configWorkingCopy = await mergePlugins(configWorkingCopy, configDir, context.settingsOverrides);
264
+ configWorkingCopy = await mergePlugins(configWorkingCopy, configDir, flags);
266
265
 
267
- const settings = resolveSettings(configWorkingCopy.settings || {}, context.settingsOverrides);
268
- overrideSettingsForGatherMode(settings, context);
266
+ const settings = resolveSettings(configWorkingCopy.settings || {}, flags);
267
+ overrideSettingsForGatherMode(settings, gatherMode);
269
268
 
270
269
  const artifacts = await resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir);
271
270
  const navigations = resolveNavigationsToDefns(configWorkingCopy.navigations, artifacts, settings);
@@ -282,7 +281,7 @@ async function initializeConfig(configJSON, context) {
282
281
 
283
282
  const {warnings} = assertValidConfig(config);
284
283
 
285
- config = filterConfigByGatherMode(config, context.gatherMode);
284
+ config = filterConfigByGatherMode(config, gatherMode);
286
285
  config = filterConfigByExplicitFilters(config, settings);
287
286
 
288
287
  log.timeEnd(status);
@@ -17,7 +17,7 @@ import * as emulation from '../../lib/emulation.js';
17
17
  import {defaultNavigationConfig} from '../../config/constants.js';
18
18
  import {initializeConfig} from '../config/config.js';
19
19
  import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
20
- import format from '../../../shared/localization/format.js';
20
+ import * as format from '../../../shared/localization/format.js';
21
21
  import {LighthouseError} from '../../lib/lh-error.js';
22
22
  import URL from '../../lib/url-shim.js';
23
23
  import {getPageLoadError} from '../../lib/navigation-error.js';
@@ -25,8 +25,6 @@ import Trace from '../../gather/gatherers/trace.js';
25
25
  import DevtoolsLog from '../../gather/gatherers/devtools-log.js';
26
26
  import NetworkRecords from '../../computed/network-records.js';
27
27
 
28
- /** @typedef {{skipAboutBlank?: boolean}} InternalOptions */
29
-
30
28
  /**
31
29
  * @typedef NavigationContext
32
30
  * @property {Driver} driver
@@ -35,7 +33,6 @@ import NetworkRecords from '../../computed/network-records.js';
35
33
  * @property {LH.NavigationRequestor} requestor
36
34
  * @property {LH.FRBaseArtifacts} baseArtifacts
37
35
  * @property {Map<string, LH.ArbitraryEqualityMap>} computedCache
38
- * @property {InternalOptions} [options]
39
36
  */
40
37
 
41
38
  /** @typedef {Omit<Parameters<typeof collectPhaseArtifacts>[0], 'phase'>} PhaseState */
@@ -44,12 +41,14 @@ const DEFAULT_HOSTNAME = '127.0.0.1';
44
41
  const DEFAULT_PORT = 9222;
45
42
 
46
43
  /**
47
- * @param {{driver: Driver, config: LH.Config.FRConfig, options?: InternalOptions}} args
44
+ * @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor}} args
48
45
  * @return {Promise<{baseArtifacts: LH.FRBaseArtifacts}>}
49
46
  */
50
- async function _setup({driver, config, options}) {
47
+ async function _setup({driver, config, requestor}) {
51
48
  await driver.connect();
52
- if (!options?.skipAboutBlank) {
49
+
50
+ // We can't trigger the navigation through user interaction if we reset the page before starting.
51
+ if (typeof requestor === 'string' && !config.settings.skipAboutBlank) {
53
52
  await gotoURL(driver, defaultNavigationConfig.blankPage, {waitUntil: ['navigated']});
54
53
  }
55
54
 
@@ -64,10 +63,12 @@ async function _setup({driver, config, options}) {
64
63
  * @param {NavigationContext} navigationContext
65
64
  * @return {Promise<{warnings: Array<LH.IcuMessage>}>}
66
65
  */
67
- async function _setupNavigation({requestor, driver, navigation, config, options}) {
68
- if (!options?.skipAboutBlank) {
66
+ async function _setupNavigation({requestor, driver, navigation, config}) {
67
+ // We can't trigger the navigation through user interaction if we reset the page before starting.
68
+ if (typeof requestor === 'string' && !config.settings.skipAboutBlank) {
69
69
  await gotoURL(driver, navigation.blankPage, {...navigation, waitUntil: ['navigated']});
70
70
  }
71
+
71
72
  const {warnings} = await prepare.prepareTargetForIndividualNavigation(
72
73
  driver.defaultSession,
73
74
  config.settings,
@@ -249,10 +250,10 @@ async function _navigation(navigationContext) {
249
250
  }
250
251
 
251
252
  /**
252
- * @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache'], options?: InternalOptions}} args
253
+ * @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache']}} args
253
254
  * @return {Promise<{artifacts: Partial<LH.FRArtifacts & LH.FRBaseArtifacts>}>}
254
255
  */
255
- async function _navigations({driver, config, requestor, baseArtifacts, computedCache, options}) {
256
+ async function _navigations({driver, config, requestor, baseArtifacts, computedCache}) {
256
257
  if (!config.navigations) throw new Error('No navigations configured');
257
258
 
258
259
  /** @type {Partial<LH.FRArtifacts & LH.FRBaseArtifacts>} */
@@ -268,7 +269,6 @@ async function _navigations({driver, config, requestor, baseArtifacts, computedC
268
269
  config,
269
270
  baseArtifacts,
270
271
  computedCache,
271
- options,
272
272
  };
273
273
 
274
274
  let shouldHaltNavigations = false;
@@ -300,25 +300,17 @@ async function _cleanup({requestedUrl, driver, config}) {
300
300
 
301
301
  /**
302
302
  * @param {LH.NavigationRequestor|undefined} requestor
303
- * @param {{page?: LH.Puppeteer.Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
303
+ * @param {{page?: LH.Puppeteer.Page, config?: LH.Config.Json, flags?: LH.Flags}} options
304
304
  * @return {Promise<LH.Gatherer.FRGatherResult>}
305
305
  */
306
306
  async function navigationGather(requestor, options) {
307
- const {configContext = {}} = options;
308
- log.setLevel(configContext.logLevel || 'error');
307
+ const {flags = {}} = options;
308
+ log.setLevel(flags.logLevel || 'error');
309
309
 
310
- const {config} =
311
- await initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
310
+ const {config} = await initializeConfig('navigation', options.config, flags);
312
311
  const computedCache = new Map();
313
- const internalOptions = {
314
- skipAboutBlank: configContext.skipAboutBlank,
315
- };
316
312
 
317
- // We can't trigger the navigation through user interaction if we reset the page before starting.
318
313
  const isCallback = typeof requestor === 'function';
319
- if (isCallback) {
320
- internalOptions.skipAboutBlank = true;
321
- }
322
314
 
323
315
  const runnerOptions = {config, computedCache};
324
316
  const artifacts = await Runner.gather(
@@ -329,7 +321,7 @@ async function navigationGather(requestor, options) {
329
321
  // For navigation mode, we shouldn't connect to a browser in audit mode,
330
322
  // therefore we connect to the browser in the gatherFn callback.
331
323
  if (!page) {
332
- const {hostname = DEFAULT_HOSTNAME, port = DEFAULT_PORT} = configContext;
324
+ const {hostname = DEFAULT_HOSTNAME, port = DEFAULT_PORT} = flags;
333
325
  const browser = await puppeteer.connect({browserURL: `http://${hostname}:${port}`});
334
326
  page = await browser.newPage();
335
327
  }
@@ -339,7 +331,6 @@ async function navigationGather(requestor, options) {
339
331
  driver,
340
332
  config,
341
333
  requestor: normalizedRequestor,
342
- options: internalOptions,
343
334
  };
344
335
  const {baseArtifacts} = await _setup(context);
345
336
  const {artifacts} = await _navigations({...context, baseArtifacts, computedCache});
@@ -13,15 +13,14 @@ import {initializeConfig} from '../config/config.js';
13
13
  import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
14
14
 
15
15
  /**
16
- * @param {{page: LH.Puppeteer.Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
16
+ * @param {{page: LH.Puppeteer.Page, config?: LH.Config.Json, flags?: LH.Flags}} options
17
17
  * @return {Promise<LH.Gatherer.FRGatherResult>}
18
18
  */
19
19
  async function snapshotGather(options) {
20
- const {configContext = {}} = options;
21
- log.setLevel(configContext.logLevel || 'error');
20
+ const {flags = {}} = options;
21
+ log.setLevel(flags.logLevel || 'error');
22
22
 
23
- const {config} =
24
- await initializeConfig(options.config, {...configContext, gatherMode: 'snapshot'});
23
+ const {config} = await initializeConfig('snapshot', options.config, flags);
25
24
  const driver = new Driver(options.page);
26
25
  await driver.connect();
27
26
 
@@ -14,15 +14,14 @@ import {initializeConfig} from '../config/config.js';
14
14
  import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
15
15
 
16
16
  /**
17
- * @param {{page: LH.Puppeteer.Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
17
+ * @param {{page: LH.Puppeteer.Page, config?: LH.Config.Json, flags?: LH.Flags}} options
18
18
  * @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
19
19
  */
20
20
  async function startTimespanGather(options) {
21
- const {configContext = {}} = options;
22
- log.setLevel(configContext.logLevel || 'error');
21
+ const {flags = {}} = options;
22
+ log.setLevel(flags.logLevel || 'error');
23
23
 
24
- const {config} =
25
- await initializeConfig(options.config, {...configContext, gatherMode: 'timespan'});
24
+ const {config} = await initializeConfig('timespan', options.config, flags);
26
25
  const driver = new Driver(options.page);
27
26
  await driver.connect();
28
27
 
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @license Copyright 2022 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
+ import * as PuppeteerReplay from '@puppeteer/replay';
9
+
10
+ /**
11
+ * @param {PuppeteerReplay.Schema.Step} step
12
+ * @return {boolean}
13
+ */
14
+ function isNavigationStep(step) {
15
+ return Boolean(
16
+ step.type === 'navigate' ||
17
+ step.assertedEvents?.some(event => event.type === 'navigation')
18
+ );
19
+ }
20
+
21
+ class LighthouseStringifyExtension extends PuppeteerReplay.PuppeteerStringifyExtension {
22
+ #isProcessingTimespan = false;
23
+
24
+ /**
25
+ * @override
26
+ * @param {PuppeteerReplay.LineWriter} out
27
+ * @param {PuppeteerReplay.Schema.UserFlow} flow
28
+ */
29
+ async beforeAllSteps(out, flow) {
30
+ out.appendLine(`const fs = require('fs');`);
31
+
32
+ let isMobile = true;
33
+ for (const step of flow.steps) {
34
+ if (step.type !== 'setViewport') continue;
35
+ isMobile = step.isMobile;
36
+ }
37
+
38
+ await super.beforeAllSteps(out, flow);
39
+
40
+ const flags = {
41
+ screenEmulation: {
42
+ disabled: true,
43
+ },
44
+ };
45
+ out.appendLine(`const flags = ${JSON.stringify(flags)}`);
46
+ if (isMobile) {
47
+ out.appendLine(`const config = undefined;`);
48
+ } else {
49
+ // eslint-disable-next-line max-len
50
+ out.appendLine(`const config = (await import('lighthouse/core/config/desktop-config.js')).default;`);
51
+ }
52
+
53
+ out.appendLine(`const lhApi = await import('lighthouse/core/fraggle-rock/api.js');`);
54
+ // eslint-disable-next-line max-len
55
+ out.appendLine(`const lhFlow = await lhApi.startFlow(page, {name: ${JSON.stringify(flow.title)}, config, flags});`);
56
+ }
57
+
58
+ /**
59
+ * @override
60
+ * @param {PuppeteerReplay.LineWriter} out
61
+ * @param {PuppeteerReplay.Schema.Step} step
62
+ * @param {PuppeteerReplay.Schema.UserFlow} flow
63
+ */
64
+ async stringifyStep(out, step, flow) {
65
+ if (step.type === 'setViewport') {
66
+ await super.stringifyStep(out, step, flow);
67
+ return;
68
+ }
69
+
70
+ const isNavigation = isNavigationStep(step);
71
+
72
+ if (isNavigation) {
73
+ if (this.#isProcessingTimespan) {
74
+ out.appendLine(`await lhFlow.endTimespan();`);
75
+ this.#isProcessingTimespan = false;
76
+ }
77
+ out.appendLine(`await lhFlow.startNavigation();`);
78
+ } else if (!this.#isProcessingTimespan) {
79
+ out.appendLine(`await lhFlow.startTimespan();`);
80
+ this.#isProcessingTimespan = true;
81
+ }
82
+
83
+ await super.stringifyStep(out, step, flow);
84
+
85
+ if (isNavigation) {
86
+ out.appendLine(`await lhFlow.endNavigation();`);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @override
92
+ * @param {PuppeteerReplay.LineWriter} out
93
+ * @param {PuppeteerReplay.Schema.UserFlow} flow
94
+ */
95
+ async afterAllSteps(out, flow) {
96
+ if (this.#isProcessingTimespan) {
97
+ out.appendLine(`await lhFlow.endTimespan();`);
98
+ }
99
+ out.appendLine(`const lhFlowReport = await lhFlow.generateReport();`);
100
+ out.appendLine(`fs.writeFileSync(__dirname + '/flow.report.html', lhFlowReport)`);
101
+ await super.afterAllSteps(out, flow);
102
+ }
103
+ }
104
+
105
+ export default LighthouseStringifyExtension;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- import ReportGenerator from '../../report/generator/report-generator.js';
8
+ import {ReportGenerator} from '../../report/generator/report-generator.js';
9
9
  import {snapshotGather} from './gather/snapshot-runner.js';
10
10
  import {startTimespanGather} from './gather/timespan-runner.js';
11
11
  import {navigationGather} from './gather/navigation-runner.js';
@@ -63,24 +63,22 @@ class UserFlow {
63
63
  */
64
64
  _getNextNavigationOptions(stepOptions) {
65
65
  const options = {...this.options, ...stepOptions};
66
- const configContext = {...options.configContext};
67
- const settingsOverrides = {...configContext.settingsOverrides};
66
+ const flags = {...options.flags};
68
67
 
69
- if (configContext.skipAboutBlank === undefined) {
70
- configContext.skipAboutBlank = true;
68
+ if (flags.skipAboutBlank === undefined) {
69
+ flags.skipAboutBlank = true;
71
70
  }
72
71
 
73
72
  // On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
74
73
  const isSubsequentNavigation = this._gatherSteps
75
74
  .some(step => step.artifacts.GatherContext.gatherMode === 'navigation');
76
75
  if (isSubsequentNavigation) {
77
- if (settingsOverrides.disableStorageReset === undefined) {
78
- settingsOverrides.disableStorageReset = true;
76
+ if (flags.disableStorageReset === undefined) {
77
+ flags.disableStorageReset = true;
79
78
  }
80
79
  }
81
80
 
82
- configContext.settingsOverrides = settingsOverrides;
83
- options.configContext = configContext;
81
+ options.flags = flags;
84
82
 
85
83
  return options;
86
84
  }
@@ -96,7 +94,7 @@ class UserFlow {
96
94
  artifacts: gatherResult.artifacts,
97
95
  name: providedName || this._getDefaultStepName(gatherResult.artifacts),
98
96
  config: options.config,
99
- configContext: options.configContext,
97
+ flags: options.flags,
100
98
  };
101
99
  this._gatherSteps.push(gatherStep);
102
100
  this._gatherStepRunnerOptions.set(gatherStep, gatherResult.runnerOptions);
@@ -246,7 +244,7 @@ async function auditGatherSteps(gatherSteps, options) {
246
244
  /** @type {LH.FlowResult['steps']} */
247
245
  const steps = [];
248
246
  for (const gatherStep of gatherSteps) {
249
- const {artifacts, name, configContext} = gatherStep;
247
+ const {artifacts, name, flags} = gatherStep;
250
248
 
251
249
  let runnerOptions = options.gatherStepRunnerOptions?.get(gatherStep);
252
250
 
@@ -255,7 +253,7 @@ async function auditGatherSteps(gatherSteps, options) {
255
253
  // Step specific configs take precedence over a config for the entire flow.
256
254
  const configJson = gatherStep.config || options.config;
257
255
  const {gatherMode} = artifacts.GatherContext;
258
- const {config} = await initializeConfig(configJson, {...configContext, gatherMode});
256
+ const {config} = await initializeConfig(gatherMode, configJson, flags);
259
257
  runnerOptions = {
260
258
  config,
261
259
  computedCache: new Map(),
@@ -16,6 +16,13 @@ class ExecutionContext {
16
16
 
17
17
  /** @type {number|undefined} */
18
18
  this._executionContextId = undefined;
19
+ /**
20
+ * Marks how many execution context ids have been created, for purposes of having a unique
21
+ * value (that doesn't expose the actual execution context id) to
22
+ * use for __lighthouseExecutionContextUniqueIdentifier.
23
+ * @type {number}
24
+ */
25
+ this._executionContextIdentifiersCreated = 0;
19
26
 
20
27
  // We use isolated execution contexts for `evaluateAsync` that can be destroyed through navigation
21
28
  // and other page actions. Cleanup our relevant bookkeeping as we see those events.
@@ -64,10 +71,10 @@ class ExecutionContext {
64
71
  });
65
72
 
66
73
  this._executionContextId = isolatedWorldResponse.executionContextId;
74
+ this._executionContextIdentifiersCreated++;
67
75
  return isolatedWorldResponse.executionContextId;
68
76
  }
69
77
 
70
-
71
78
  /**
72
79
  * Evaluate an expression in the given execution context; an undefined contextId implies the main
73
80
  * page without isolation.
@@ -82,15 +89,25 @@ class ExecutionContext {
82
89
  this._session.getNextProtocolTimeout() :
83
90
  60000;
84
91
 
92
+ // `__lighthouseExecutionContextUniqueIdentifier` is only used by the FullPageScreenshot gatherer.
93
+ // See `getNodeDetails` in page-functions.
94
+ const uniqueExecutionContextIdentifier = contextId === undefined ?
95
+ undefined :
96
+ this._executionContextIdentifiersCreated;
97
+
85
98
  const evaluationParams = {
86
99
  // We need to explicitly wrap the raw expression for several purposes:
87
100
  // 1. Ensure that the expression will be a native Promise and not a polyfill/non-Promise.
88
101
  // 2. Ensure that errors in the expression are captured by the Promise.
89
102
  // 3. Ensure that errors captured in the Promise are converted into plain-old JS Objects
90
103
  // so that they can be serialized properly b/c JSON.stringify(new Error('foo')) === '{}'
104
+ //
105
+ // `__lighthouseExecutionContextUniqueIdentifier` is only used by the FullPageScreenshot gatherer.
106
+ // See `getNodeDetails` in page-functions.
91
107
  expression: `(function wrapInNativePromise() {
92
108
  ${ExecutionContext._cachedNativesPreamble};
93
- globalThis.__lighthouseExecutionContextId = ${contextId};
109
+ globalThis.__lighthouseExecutionContextUniqueIdentifier =
110
+ ${uniqueExecutionContextIdentifier};
94
111
  return new Promise(function (resolve) {
95
112
  return Promise.resolve()
96
113
  .then(_ => ${expression})
@@ -10,7 +10,7 @@ import NetworkRecords from '../computed/network-records.js';
10
10
  import {getPageLoadError} from '../lib/navigation-error.js';
11
11
  import * as emulation from '../lib/emulation.js';
12
12
  import * as constants from '../config/constants.js';
13
- import format from '../../shared/localization/format.js';
13
+ import * as format from '../../shared/localization/format.js';
14
14
  import {getBenchmarkIndex, getEnvironmentWarnings} from './driver/environment.js';
15
15
  import * as prepare from './driver/prepare.js';
16
16
  import * as storage from './driver/storage.js';
package/core/index.js CHANGED
@@ -12,7 +12,6 @@ import {Config} from './config/config.js';
12
12
  import URL from './lib/url-shim.js';
13
13
  import * as fraggleRock from './fraggle-rock/api.js';
14
14
  import {Driver} from './gather/driver.js';
15
- import {flagsToFRContext} from './config/config-helpers.js';
16
15
  import {initializeConfig} from './fraggle-rock/config/config.js';
17
16
 
18
17
  /** @typedef {import('./gather/connections/connection.js').Connection} Connection */
@@ -41,8 +40,7 @@ import {initializeConfig} from './fraggle-rock/config/config.js';
41
40
  * @return {Promise<LH.RunnerResult|undefined>}
42
41
  */
43
42
  async function lighthouse(url, flags = {}, configJSON, page) {
44
- const configContext = flagsToFRContext(flags);
45
- return fraggleRock.navigation(url, {page, config: configJSON, configContext});
43
+ return fraggleRock.navigation(url, {page, config: configJSON, flags});
46
44
  }
47
45
 
48
46
  /**
@@ -86,8 +84,7 @@ async function legacyNavigation(url, flags = {}, configJSON, userConnection) {
86
84
  * @return {Promise<LH.Config.FRConfig>}
87
85
  */
88
86
  async function generateConfig(configJson, flags = {}, gatherMode = 'navigation') {
89
- const configContext = flagsToFRContext(flags);
90
- const {config} = await initializeConfig(configJson, {...configContext, gatherMode});
87
+ const {config} = await initializeConfig(gatherMode, configJson, flags);
91
88
  return config;
92
89
  }
93
90
 
@@ -12,7 +12,7 @@ import stream from 'stream';
12
12
  import util from 'util';
13
13
  import {Simulator} from './dependency-graph/simulator/simulator.js';
14
14
  import lanternTraceSaver from './lantern-trace-saver.js';
15
- import {Metrics} from './traces/pwmetrics-events.js';
15
+ import {MetricTraceEvents} from './traces/metric-trace-events.js';
16
16
  import NetworkAnalysisComputed from '../computed/network-analysis.js';
17
17
  import LoadSimulatorComputed from '../computed/load-simulator.js';
18
18
  import {LighthouseError} from '../lib/lh-error.js';
@@ -164,7 +164,7 @@ async function prepareAssets(artifacts, audits) {
164
164
 
165
165
  const traceData = Object.assign({}, trace);
166
166
  if (audits) {
167
- const evts = new Metrics(traceData.traceEvents, audits).generateFakeEvents();
167
+ const evts = new MetricTraceEvents(traceData.traceEvents, audits).generateFakeEvents();
168
168
  traceData.traceEvents = traceData.traceEvents.concat(evts);
169
169
  }
170
170
 
@@ -498,6 +498,18 @@ function getNodeDetails(element) {
498
498
  const selector = getNodeSelector(element);
499
499
 
500
500
  // Create an id that will be unique across all execution contexts.
501
+ //
502
+ // Made up of 3 components:
503
+ // - prefix unique to specific execution context
504
+ // - nth unique node seen by this function for this execution context
505
+ // - node tagName
506
+ //
507
+ // Every page load only has up to two associated contexts - the page context
508
+ // (denoted as `__lighthouseExecutionContextUniqueIdentifier` being undefined)
509
+ // and the isolated context. The id must be unique to distinguish gatherers running
510
+ // on different page loads that identify the same logical element, for purposes
511
+ // of the full page screenshot node lookup; hence the prefix.
512
+ //
501
513
  // The id could be any arbitrary string, the exact value is not important.
502
514
  // For example, tagName is added only because it might be useful for debugging.
503
515
  // But execution id and map size are added to ensure uniqueness.
@@ -507,9 +519,9 @@ function getNodeDetails(element) {
507
519
  let lhId = window.__lighthouseNodesDontTouchOrAllVarianceGoesAway.get(element);
508
520
  if (!lhId) {
509
521
  lhId = [
510
- window.__lighthouseExecutionContextId !== undefined ?
511
- window.__lighthouseExecutionContextId :
512
- 'page',
522
+ window.__lighthouseExecutionContextUniqueIdentifier === undefined ?
523
+ 'page' :
524
+ window.__lighthouseExecutionContextUniqueIdentifier,
513
525
  window.__lighthouseNodesDontTouchOrAllVarianceGoesAway.size,
514
526
  element.tagName,
515
527
  ].join('-');
@@ -19,7 +19,7 @@ function getUberMetrics(auditResults) {
19
19
  return metricsAudit.details.items[0];
20
20
  }
21
21
 
22
- class Metrics {
22
+ class MetricTraceEvents {
23
23
  /**
24
24
  * @param {Array<LH.TraceEvent>} traceEvents
25
25
  * @param {LH.Result['audits']} auditResults
@@ -100,7 +100,7 @@ class Metrics {
100
100
 
101
101
  /** @type {Array<{ts: number, id: string, name: string}>} */
102
102
  const resolvedMetrics = [];
103
- Metrics.metricsDefinitions.forEach(metric => {
103
+ MetricTraceEvents.metricsDefinitions.forEach(metric => {
104
104
  // Skip if auditResults is missing a particular audit result
105
105
  const ts = uberMetrics[metric.tsKey];
106
106
  if (ts === undefined) {
@@ -197,5 +197,4 @@ class Metrics {
197
197
  }
198
198
  }
199
199
 
200
- // TODO(esmodule): rename?
201
- export {Metrics};
200
+ export {MetricTraceEvents};