lighthouse 9.4.0 → 9.5.0

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 (42) hide show
  1. package/changelog.md +5902 -0
  2. package/dist/report/bundle.esm.js +3 -2
  3. package/dist/report/flow.js +2 -2
  4. package/dist/report/standalone.js +2 -2
  5. package/lighthouse-cli/run.js +5 -1
  6. package/lighthouse-cli/test/smokehouse/core-tests.js +4 -0
  7. package/lighthouse-cli/test/smokehouse/frontends/lib.js +3 -1
  8. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -1
  9. package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +4 -1
  10. package/lighthouse-cli/test/smokehouse/readme.md +14 -0
  11. package/lighthouse-cli/test/smokehouse/report-assert.js +64 -28
  12. package/lighthouse-cli/test/smokehouse/smokehouse.js +5 -2
  13. package/lighthouse-core/audits/dobetterweb/inspector-issues.js +6 -6
  14. package/lighthouse-core/audits/script-treemap-data.js +1 -1
  15. package/lighthouse-core/config/config-helpers.js +1 -1
  16. package/lighthouse-core/fraggle-rock/api.js +56 -5
  17. package/lighthouse-core/fraggle-rock/config/config.js +1 -1
  18. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +1 -1
  19. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +8 -4
  20. package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +3 -1
  21. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +12 -5
  22. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +13 -7
  23. package/lighthouse-core/fraggle-rock/user-flow.js +114 -55
  24. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +33 -25
  25. package/lighthouse-core/gather/gatherers/inspector-issues.js +1 -1
  26. package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
  27. package/lighthouse-core/lib/asset-saver.js +15 -0
  28. package/lighthouse-core/runner.js +1 -1
  29. package/lighthouse-core/scripts/package.json +4 -0
  30. package/package.json +7 -12
  31. package/report/assets/templates.html +2 -1
  32. package/report/renderer/components.js +1 -1
  33. package/report/renderer/topbar-features.js +2 -1
  34. package/shared/localization/swap-locale.js +1 -2
  35. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
  36. package/third-party/snyk/snapshot.json +4 -3
  37. package/types/artifacts.d.ts +1 -1
  38. package/types/config.d.ts +1 -1
  39. package/types/gatherer.d.ts +8 -0
  40. package/types/global-lh.d.ts +3 -0
  41. package/types/smokehouse.d.ts +1 -1
  42. package/types/user-flow.d.ts +19 -0
@@ -207,7 +207,11 @@ async function runLighthouseWithFraggleRock(url, flags, config, launchedChrome)
207
207
  const browser = await puppeteer.connect({browserURL: `http://localhost:${launchedChrome.port}`});
208
208
  const page = await browser.newPage();
209
209
  flags.channel = 'fraggle-rock-cli';
210
- const configContext = {configPath: flags.configPath, settingsOverrides: flags};
210
+ const configContext = {
211
+ configPath: flags.configPath,
212
+ settingsOverrides: flags,
213
+ logLevel: flags.logLevel,
214
+ };
211
215
  return fraggleRock.navigation(url, {page, config, configContext});
212
216
  }
213
217
 
@@ -33,6 +33,7 @@ import offlineReady from './test-definitions/offline-ready.js';
33
33
  import offlineSwBroken from './test-definitions/offline-sw-broken.js';
34
34
  import offlineSwSlow from './test-definitions/offline-sw-slow.js';
35
35
  import oopifRequests from './test-definitions/oopif-requests.js';
36
+ import oopifScripts from './test-definitions/oopif-scripts.js';
36
37
  import perfBudgets from './test-definitions/perf-budgets.js';
37
38
  import perfDebug from './test-definitions/perf-debug.js';
38
39
  import perfDiagnosticsAnimations from './test-definitions/perf-diagnostics-animations.js';
@@ -53,6 +54,7 @@ import redirectsHistoryPushState from './test-definitions/redirects-history-push
53
54
  import redirectsMultipleServer from './test-definitions/redirects-multiple-server.js';
54
55
  import redirectsSingleClient from './test-definitions/redirects-single-client.js';
55
56
  import redirectsSingleServer from './test-definitions/redirects-single-server.js';
57
+ import redirectsSelf from './test-definitions/redirects-self.js';
56
58
  import screenshot from './test-definitions/screenshot.js';
57
59
  import seoFailing from './test-definitions/seo-failing.js';
58
60
  import seoPassing from './test-definitions/seo-passing.js';
@@ -91,6 +93,7 @@ const smokeTests = [
91
93
  offlineSwBroken,
92
94
  offlineSwSlow,
93
95
  oopifRequests,
96
+ oopifScripts,
94
97
  perfBudgets,
95
98
  perfDebug,
96
99
  perfDiagnosticsAnimations,
@@ -111,6 +114,7 @@ const smokeTests = [
111
114
  redirectsMultipleServer,
112
115
  redirectsSingleClient,
113
116
  redirectsSingleServer,
117
+ redirectsSelf,
114
118
  screenshot,
115
119
  seoFailing,
116
120
  seoPassing,
@@ -13,11 +13,13 @@
13
13
 
14
14
  /* eslint-disable no-console */
15
15
 
16
- import cloneDeep from 'lodash.clonedeep';
16
+ import _ from 'lodash';
17
17
 
18
18
  import smokeTests from '../core-tests.js';
19
19
  import {runSmokehouse, getShardedDefinitions} from '../smokehouse.js';
20
20
 
21
+ const {cloneDeep} = _;
22
+
21
23
  /**
22
24
  * @param {Smokehouse.SmokehouseLibOptions} options
23
25
  */
@@ -17,7 +17,7 @@ import path from 'path';
17
17
  import fs from 'fs';
18
18
  import url from 'url';
19
19
 
20
- import cloneDeep from 'lodash.clonedeep';
20
+ import _ from 'lodash';
21
21
  import yargs from 'yargs';
22
22
  import * as yargsHelpers from 'yargs/helpers';
23
23
  import log from 'lighthouse-logger';
@@ -26,6 +26,8 @@ import {runSmokehouse, getShardedDefinitions} from '../smokehouse.js';
26
26
  import {updateTestDefnFormat} from './back-compat-util.js';
27
27
  import {LH_ROOT} from '../../../../root.js';
28
28
 
29
+ const {cloneDeep} = _;
30
+
29
31
  const coreTestDefnsPath =
30
32
  path.join(LH_ROOT, 'lighthouse-cli/test/smokehouse/core-tests.js');
31
33
 
@@ -186,6 +188,7 @@ async function begin() {
186
188
  console.log('\n✨ Be sure to have recently run this: yarn build-all');
187
189
  }
188
190
  const {runLighthouse} = await import(runnerPath);
191
+ runLighthouse.runnerName = argv.runner;
189
192
 
190
193
  // Find test definition file and filter by requestedTestIds.
191
194
  let testDefnPath = argv.testsPath || coreTestDefnsPath;
@@ -75,10 +75,13 @@ async function runLighthouse(url, configJson, testRunnerOptions = {}) {
75
75
  await buildDevtoolsPromise;
76
76
 
77
77
  const outputDir = fs.mkdtempSync(os.tmpdir() + '/lh-smoke-cdt-runner-');
78
+ const chromeFlags = [
79
+ `--custom-devtools-frontend=file://${devtoolsDir}/out/Default/gen/front_end`,
80
+ ];
78
81
  const args = [
79
82
  'run-devtools',
80
83
  url,
81
- `--custom-devtools-frontend=file://${devtoolsDir}/out/Default/gen/front_end`,
84
+ `--chrome-flags=${chromeFlags.join(' ')}`,
82
85
  '--output-dir', outputDir,
83
86
  ];
84
87
  if (configJson) {
@@ -66,6 +66,8 @@ However, if an array literal is used as the expectation, an extra condition is e
66
66
 
67
67
  Arrays can be checked against a subset of elements using the special `_includes` property. The value of `_includes` _must_ be an array. Each assertion in `_includes` will remove the matching item from consideration for the rest.
68
68
 
69
+ Arrays can be asserted to not match any elements using the special `_excludes` property. The value of `_excludes` _must_ be an array. If an `_includes` check is defined before an `_excludes` check, only the element not matched under the previous will be considered.
70
+
69
71
  **Examples**:
70
72
  | Actual | Expected | Result |
71
73
  | -- | -- | -- |
@@ -73,6 +75,9 @@ Arrays can be checked against a subset of elements using the special `_includes`
73
75
  | `[{timeInMs: 5}, {timeInMs: 15}]` | `{length: 2}` | ✅ PASS |
74
76
  | `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}]}` | ✅ PASS |
75
77
  | `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}, {timeInMs: 5}]}` | ❌ FAIL |
78
+ | `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}], _excludes: [{timeInMs: 5}]}` | ✅ PASS |
79
+ | `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}], _excludes: [{timeInMs: 15}]}` | ❌ FAIL |
80
+ | `[{timeInMs: 5}, {timeInMs: 15}]` | `{_includes: [{timeInMs: 5}], _excludes: [{}]}` | ❌ FAIL |
76
81
  | `[{timeInMs: 5}, {timeInMs: 15}]` | `[{timeInMs: 5}]` | ❌ FAIL |
77
82
 
78
83
  ### Special environment checks
@@ -104,6 +109,15 @@ If an expectation requires a minimum version of Chromium, use `_minChromiumMiles
104
109
  },
105
110
  ```
106
111
 
112
+ All pruning checks:
113
+
114
+ - `_minChromiumMilestone`
115
+ - `_maxChromiumMilestone`
116
+ - `_legacyOnly`
117
+ - `_fraggleRockOnly`
118
+ - `_skipInBundled`
119
+ - `_runner` (set to same value provided to CLI --runner flag, ex: `'devtools'`)
120
+
107
121
  ## Pipeline
108
122
 
109
123
  The different frontends launch smokehouse with a set of tests to run. Smokehouse then coordinates the tests using a particular method of running Lighthouse (CLI, as a bundle, etc).
@@ -10,11 +10,13 @@
10
10
  * against the results actually collected from Lighthouse.
11
11
  */
12
12
 
13
- import cloneDeep from 'lodash.clonedeep';
13
+ import _ from 'lodash';
14
14
  import log from 'lighthouse-logger';
15
15
 
16
16
  import {LocalConsole} from './lib/local-console.js';
17
17
 
18
+ const {cloneDeep} = _;
19
+
18
20
  /**
19
21
  * @typedef Difference
20
22
  * @property {string} path
@@ -103,6 +105,8 @@ function findDifference(path, actual, expected) {
103
105
  };
104
106
  }
105
107
 
108
+ let inclExclCopy;
109
+
106
110
  // We only care that all expected's own properties are on actual (and not the other way around).
107
111
  // Note an expected `undefined` can match an actual that is either `undefined` or not defined.
108
112
  for (const key of Object.keys(expected)) {
@@ -112,6 +116,8 @@ function findDifference(path, actual, expected) {
112
116
  const expectedValue = expected[key];
113
117
 
114
118
  if (key === '_includes') {
119
+ inclExclCopy = [...actual];
120
+
115
121
  if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
116
122
  if (!Array.isArray(actual)) {
117
123
  return {
@@ -121,12 +127,12 @@ function findDifference(path, actual, expected) {
121
127
  };
122
128
  }
123
129
 
124
- const actualCopy = [...actual];
125
130
  for (const expectedEntry of expectedValue) {
126
131
  const matchingIndex =
127
- actualCopy.findIndex(actualEntry => !findDifference(keyPath, actualEntry, expectedEntry));
132
+ inclExclCopy.findIndex(actualEntry =>
133
+ !findDifference(keyPath, actualEntry, expectedEntry));
128
134
  if (matchingIndex !== -1) {
129
- actualCopy.splice(matchingIndex, 1);
135
+ inclExclCopy.splice(matchingIndex, 1);
130
136
  continue;
131
137
  }
132
138
 
@@ -140,6 +146,33 @@ function findDifference(path, actual, expected) {
140
146
  continue;
141
147
  }
142
148
 
149
+ if (key === '_excludes') {
150
+ // Re-use state from `_includes` check, if there was one.
151
+ /** @type {any[]} */
152
+ const arrToCheckAgainst = inclExclCopy || actual;
153
+
154
+ if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
155
+ if (!Array.isArray(actual)) continue;
156
+
157
+ const expectedExclusions = expectedValue;
158
+ for (const expectedExclusion of expectedExclusions) {
159
+ const matchingIndex = arrToCheckAgainst.findIndex(actualEntry =>
160
+ !findDifference(keyPath, actualEntry, expectedExclusion));
161
+ if (matchingIndex !== -1) {
162
+ return {
163
+ path,
164
+ actual: arrToCheckAgainst[matchingIndex],
165
+ expected: {
166
+ message: 'Expected to not find matching entry via _excludes',
167
+ expectedExclusion,
168
+ },
169
+ };
170
+ }
171
+ }
172
+
173
+ continue;
174
+ }
175
+
143
176
  const actualValue = actual[key];
144
177
  const subDifference = findDifference(keyPath, actualValue, expectedValue);
145
178
 
@@ -187,7 +220,7 @@ function makeComparison(name, actualResult, expectedResult) {
187
220
  * @param {LocalConsole} localConsole
188
221
  * @param {LH.Result} lhr
189
222
  * @param {Smokehouse.ExpectedRunnerResult} expected
190
- * @param {{isBundled?: boolean}=} reportOptions
223
+ * @param {{runner?: string, isBundled?: boolean}=} reportOptions
191
224
  */
192
225
  function pruneExpectations(localConsole, lhr, expected, reportOptions) {
193
226
  const isFraggleRock = lhr.configSettings.channel === 'fraggle-rock-cli';
@@ -217,8 +250,20 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
217
250
  * @param {*} obj
218
251
  */
219
252
  function pruneRecursively(obj) {
220
- for (const key of Object.keys(obj)) {
221
- const value = obj[key];
253
+ /**
254
+ * @param {string} key
255
+ */
256
+ const remove = (key) => {
257
+ if (Array.isArray(obj)) {
258
+ obj.splice(Number(key), 1);
259
+ } else {
260
+ delete obj[key];
261
+ }
262
+ };
263
+
264
+ // Because we may be deleting keys, we should iterate the keys backwards
265
+ // otherwise arrays with multiple pruning checks will skip elements.
266
+ for (const [key, value] of Object.entries(obj).reverse()) {
222
267
  if (!value || typeof value !== 'object') {
223
268
  continue;
224
269
  }
@@ -229,42 +274,32 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
229
274
  JSON.stringify(value, null, 2),
230
275
  `Actual Chromium version: ${getChromeVersion()}`,
231
276
  ].join(' '));
232
- if (Array.isArray(obj)) {
233
- obj.splice(Number(key), 1);
234
- } else {
235
- delete obj[key];
236
- }
277
+ remove(key);
237
278
  } else if (value._legacyOnly && isFraggleRock) {
238
279
  localConsole.log([
239
280
  `[${key}] marked legacy only but run is Fraggle Rock, pruning expectation:`,
240
281
  JSON.stringify(value, null, 2),
241
282
  ].join(' '));
242
- if (Array.isArray(obj)) {
243
- obj.splice(Number(key), 1);
244
- } else {
245
- delete obj[key];
246
- }
283
+ remove(key);
247
284
  } else if (value._fraggleRockOnly && !isFraggleRock) {
248
285
  localConsole.log([
249
286
  `[${key}] marked Fraggle Rock only but run is legacy, pruning expectation:`,
250
287
  JSON.stringify(value, null, 2),
251
288
  `Actual channel: ${lhr.configSettings.channel}`,
252
289
  ].join(' '));
253
- if (Array.isArray(obj)) {
254
- obj.splice(Number(key), 1);
255
- } else {
256
- delete obj[key];
257
- }
290
+ remove(key);
258
291
  } else if (value._skipInBundled && !isBundled) {
259
292
  localConsole.log([
260
293
  `[${key}] marked as skip in bundled and runner is bundled, pruning expectation:`,
261
294
  JSON.stringify(value, null, 2),
262
295
  ].join(' '));
263
- if (Array.isArray(obj)) {
264
- obj.splice(Number(key), 1);
265
- } else {
266
- delete obj[key];
267
- }
296
+ remove(key);
297
+ } else if (value._runner && reportOptions?.runner !== value._runner) {
298
+ localConsole.log([
299
+ `[${key}] is only for runner ${value._runner}, pruning expectation:`,
300
+ JSON.stringify(value, null, 2),
301
+ ].join(' '));
302
+ remove(key);
268
303
  } else {
269
304
  pruneRecursively(value);
270
305
  }
@@ -275,6 +310,7 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
275
310
  delete obj._skipInBundled;
276
311
  delete obj._minChromiumMilestone;
277
312
  delete obj._maxChromiumMilestone;
313
+ delete obj._runner;
278
314
  }
279
315
 
280
316
  const cloned = cloneDeep(expected);
@@ -420,7 +456,7 @@ function reportAssertion(localConsole, assertion) {
420
456
  * summary. Returns count of passed and failed tests.
421
457
  * @param {{lhr: LH.Result, artifacts: LH.Artifacts, networkRequests?: string[]}} actual
422
458
  * @param {Smokehouse.ExpectedRunnerResult} expected
423
- * @param {{isDebug?: boolean, isBundled?: boolean}=} reportOptions
459
+ * @param {{runner?: string, isDebug?: boolean, isBundled?: boolean}=} reportOptions
424
460
  * @return {{passed: number, failed: number, log: string}}
425
461
  */
426
462
  function getAssertionReport(actual, expected, reportOptions = {}) {
@@ -57,7 +57,7 @@ async function runSmokehouse(smokeTestDefns, smokehouseOptions) {
57
57
  useFraggleRock,
58
58
  jobs = DEFAULT_CONCURRENT_RUNS,
59
59
  retries = DEFAULT_RETRIES,
60
- lighthouseRunner = cliLighthouseRunner,
60
+ lighthouseRunner = Object.assign(cliLighthouseRunner, {runnerName: 'cli'}),
61
61
  takeNetworkRequestUrls,
62
62
  } = smokehouseOptions;
63
63
  assertPositiveInteger('jobs', jobs);
@@ -159,7 +159,10 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
159
159
  }
160
160
 
161
161
  // Assert result.
162
- report = getAssertionReport(result, expectations, {isDebug});
162
+ report = getAssertionReport(result, expectations, {
163
+ runner: lighthouseRunner.runnerName,
164
+ isDebug,
165
+ });
163
166
 
164
167
  runs.push({
165
168
  ...result,
@@ -71,19 +71,19 @@ class IssuesPanelEntries extends Audit {
71
71
  }
72
72
 
73
73
  /**
74
- * @param {Array<LH.Crdp.Audits.SameSiteCookieIssueDetails>} sameSiteCookieIssues
74
+ * @param {Array<LH.Crdp.Audits.CookieIssueDetails>} CookieIssues
75
75
  * @return {LH.Audit.Details.TableItem}
76
76
  */
77
- static getSameSiteCookieRow(sameSiteCookieIssues) {
77
+ static getCookieRow(CookieIssues) {
78
78
  const requestUrls = new Set();
79
- for (const issue of sameSiteCookieIssues) {
79
+ for (const issue of CookieIssues) {
80
80
  const requestUrl = (issue.request?.url) || issue.cookieUrl;
81
81
  if (requestUrl) {
82
82
  requestUrls.add(requestUrl);
83
83
  }
84
84
  }
85
85
  return {
86
- issueType: 'SameSite cookie',
86
+ issueType: 'Cookie',
87
87
  subItems: {
88
88
  type: 'subitems',
89
89
  items: Array.from(requestUrls).map(url => {
@@ -164,8 +164,8 @@ class IssuesPanelEntries extends Audit {
164
164
  if (issues.mixedContentIssue.length) {
165
165
  items.push(this.getMixedContentRow(issues.mixedContentIssue));
166
166
  }
167
- if (issues.sameSiteCookieIssue.length) {
168
- items.push(this.getSameSiteCookieRow(issues.sameSiteCookieIssue));
167
+ if (issues.cookieIssue.length) {
168
+ items.push(this.getCookieRow(issues.cookieIssue));
169
169
  }
170
170
  if (issues.blockedByResponseIssue.length) {
171
171
  items.push(this.getBlockedByResponseRow(issues.blockedByResponseIssue));
@@ -197,7 +197,7 @@ class ScriptTreemapDataAudit extends Audit {
197
197
 
198
198
  nodes.push({
199
199
  name,
200
- resourceBytes: scriptElement.src.length,
200
+ resourceBytes: scriptElement.content?.length || 0,
201
201
  });
202
202
  continue;
203
203
  }
@@ -6,7 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const path = require('path');
9
- const isDeepEqual = require('lodash.isequal');
9
+ const {isEqual: isDeepEqual} = require('lodash');
10
10
  const constants = require('./constants.js');
11
11
  const Budget = require('./budget.js');
12
12
  const ConfigPlugin = require('./config-plugin.js');
@@ -5,22 +5,73 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const {snapshot} = require('./gather/snapshot-runner.js');
9
- const {startTimespan} = require('./gather/timespan-runner.js');
10
- const {navigation} = require('./gather/navigation-runner.js');
11
- const UserFlow = require('./user-flow.js');
8
+ const {UserFlow, auditGatherSteps} = require('./user-flow.js');
9
+ const {snapshotGather} = require('./gather/snapshot-runner.js');
10
+ const {startTimespanGather} = require('./gather/timespan-runner.js');
11
+ const {navigationGather} = require('./gather/navigation-runner.js');
12
+ const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
13
+ const Runner = require('../runner.js');
12
14
 
13
15
  /**
14
16
  * @param {import('puppeteer').Page} page
15
- * @param {UserFlow.UserFlowOptions} [options]
17
+ * @param {ConstructorParameters<LH.UserFlow>[1]} [options]
16
18
  */
17
19
  async function startFlow(page, options) {
18
20
  return new UserFlow(page, options);
19
21
  }
20
22
 
23
+ /**
24
+ * @param {Parameters<navigationGather>} params
25
+ * @return {Promise<LH.RunnerResult|undefined>}
26
+ */
27
+ async function navigation(...params) {
28
+ const gatherResult = await navigationGather(...params);
29
+ return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
30
+ }
31
+
32
+ /**
33
+ * @param {Parameters<snapshotGather>} params
34
+ * @return {Promise<LH.RunnerResult|undefined>}
35
+ */
36
+ async function snapshot(...params) {
37
+ const gatherResult = await snapshotGather(...params);
38
+ return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
39
+ }
40
+
41
+ /**
42
+ * @param {Parameters<startTimespanGather>} params
43
+ * @return {Promise<{endTimespan: () => Promise<LH.RunnerResult|undefined>}>}
44
+ */
45
+ async function startTimespan(...params) {
46
+ const {endTimespanGather} = await startTimespanGather(...params);
47
+ const endTimespan = async () => {
48
+ const gatherResult = await endTimespanGather();
49
+ return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
50
+ };
51
+ return {endTimespan};
52
+ }
53
+
54
+ /**
55
+ * @param {LH.FlowResult} flowResult
56
+ */
57
+ async function generateFlowReport(flowResult) {
58
+ return generateFlowReportHtml(flowResult);
59
+ }
60
+
61
+ /**
62
+ * @param {LH.UserFlow.FlowArtifacts} flowArtifacts
63
+ * @param {LH.Config.Json} [config]
64
+ */
65
+ async function auditFlowArtifacts(flowArtifacts, config) {
66
+ const {gatherSteps, name} = flowArtifacts;
67
+ return await auditGatherSteps(gatherSteps, {name, config});
68
+ }
69
+
21
70
  module.exports = {
22
71
  snapshot,
23
72
  startTimespan,
24
73
  navigation,
25
74
  startFlow,
75
+ generateFlowReport,
76
+ auditFlowArtifacts,
26
77
  };
@@ -30,7 +30,7 @@ const {
30
30
  } = require('../../config/config-helpers.js');
31
31
  const defaultConfigPath = path.join(__dirname, './default-config.js');
32
32
 
33
- /** @typedef {Omit<LH.Config.FRContext, 'gatherMode'> & {gatherMode: LH.Gatherer.GatherMode}} ConfigContext */
33
+ /** @typedef {LH.Config.FRContext & {gatherMode: LH.Gatherer.GatherMode}} ConfigContext */
34
34
 
35
35
  /**
36
36
  * @param {LH.Config.Json|undefined} configJSON
@@ -6,7 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const log = require('lighthouse-logger');
9
- const isEqual = require('lodash.isequal');
9
+ const {isEqual} = require('lodash');
10
10
  const {
11
11
  getBrowserVersion,
12
12
  getBenchmarkIndex,
@@ -210,6 +210,7 @@ async function _computeNavigationResult(
210
210
  async function _navigation(navigationContext) {
211
211
  const artifactState = getEmptyArtifactState();
212
212
  const phaseState = {
213
+ url: await navigationContext.driver.url(),
213
214
  gatherMode: /** @type {const} */ ('navigation'),
214
215
  driver: navigationContext.driver,
215
216
  computedCache: navigationContext.computedCache,
@@ -223,6 +224,7 @@ async function _navigation(navigationContext) {
223
224
  await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseState});
224
225
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseState});
225
226
  const navigateResult = await _navigate(navigationContext);
227
+ phaseState.url = navigateResult.finalUrl;
226
228
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseState});
227
229
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseState});
228
230
  await _cleanupNavigation(navigationContext);
@@ -288,10 +290,12 @@ async function _cleanup({requestedUrl, driver, config}) {
288
290
  /**
289
291
  * @param {LH.NavigationRequestor} requestor
290
292
  * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
291
- * @return {Promise<LH.RunnerResult|undefined>}
293
+ * @return {Promise<LH.Gatherer.FRGatherResult>}
292
294
  */
293
- async function navigation(requestor, options) {
295
+ async function navigationGather(requestor, options) {
294
296
  const {page, configContext = {}} = options;
297
+ log.setLevel(configContext.logLevel || 'error');
298
+
295
299
  const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'navigation'});
296
300
  const computedCache = new Map();
297
301
  const internalOptions = {
@@ -321,11 +325,11 @@ async function navigation(requestor, options) {
321
325
  },
322
326
  runnerOptions
323
327
  );
324
- return Runner.audit(artifacts, runnerOptions);
328
+ return {artifacts, runnerOptions};
325
329
  }
326
330
 
327
331
  module.exports = {
328
- navigation,
332
+ navigationGather,
329
333
  _setup,
330
334
  _setupNavigation,
331
335
  _navigate,
@@ -15,6 +15,7 @@
15
15
  * @property {LH.Gatherer.GatherMode} gatherMode
16
16
  * @property {Map<string, LH.ArbitraryEqualityMap>} computedCache
17
17
  * @property {LH.Config.Settings} settings
18
+ * @property {string} url
18
19
  */
19
20
 
20
21
  /** @typedef {Record<string, Promise<any>>} IntermediateArtifacts */
@@ -74,6 +75,7 @@ async function collectPhaseArtifacts(options) {
74
75
  gatherMode,
75
76
  computedCache,
76
77
  settings,
78
+ url,
77
79
  } = options;
78
80
  const priorPhase = phaseToPriorPhase[phase];
79
81
  const priorPhaseArtifacts = (priorPhase && artifactState[priorPhase]) || {};
@@ -90,7 +92,7 @@ async function collectPhaseArtifacts(options) {
90
92
  : /** @type {Dependencies} */ ({});
91
93
 
92
94
  return gatherer[phase]({
93
- url: await driver.url(),
95
+ url,
94
96
  gatherMode,
95
97
  driver,
96
98
  baseArtifacts,
@@ -5,6 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const log = require('lighthouse-logger');
8
9
  const Driver = require('./driver.js');
9
10
  const Runner = require('../../runner.js');
10
11
  const {
@@ -15,16 +16,21 @@ const {
15
16
  const {initializeConfig} = require('../config/config.js');
16
17
  const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
17
18
 
18
- /** @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options */
19
- async function snapshot(options) {
19
+ /**
20
+ * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
21
+ * @return {Promise<LH.Gatherer.FRGatherResult>}
22
+ */
23
+ async function snapshotGather(options) {
20
24
  const {configContext = {}} = options;
25
+ log.setLevel(configContext.logLevel || 'error');
26
+
21
27
  const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'snapshot'});
22
28
  const driver = new Driver(options.page);
23
29
  await driver.connect();
24
30
 
25
31
  /** @type {Map<string, LH.ArbitraryEqualityMap>} */
26
32
  const computedCache = new Map();
27
- const url = await options.page.url();
33
+ const url = await driver.url();
28
34
 
29
35
  const runnerOptions = {config, computedCache};
30
36
  const artifacts = await Runner.gather(
@@ -36,6 +42,7 @@ async function snapshot(options) {
36
42
  const artifactDefinitions = config.artifacts || [];
37
43
  const artifactState = getEmptyArtifactState();
38
44
  await collectPhaseArtifacts({
45
+ url,
39
46
  phase: 'getArtifact',
40
47
  gatherMode: 'snapshot',
41
48
  driver,
@@ -53,9 +60,9 @@ async function snapshot(options) {
53
60
  },
54
61
  runnerOptions
55
62
  );
56
- return Runner.audit(artifacts, runnerOptions);
63
+ return {artifacts, runnerOptions};
57
64
  }
58
65
 
59
66
  module.exports = {
60
- snapshot,
67
+ snapshotGather,
61
68
  };