lighthouse 9.4.0-dev.20220227 → 9.4.0-dev.20220302

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.
@@ -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';
@@ -92,6 +93,7 @@ const smokeTests = [
92
93
  offlineSwBroken,
93
94
  offlineSwSlow,
94
95
  oopifRequests,
96
+ oopifScripts,
95
97
  perfBudgets,
96
98
  perfDebug,
97
99
  perfDiagnosticsAnimations,
@@ -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;
@@ -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,9 +5,11 @@
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');
8
+ const {snapshotGather} = require('./gather/snapshot-runner.js');
9
+ const {startTimespanGather} = require('./gather/timespan-runner.js');
10
+ const {navigationGather} = require('./gather/navigation-runner.js');
11
+ const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
12
+ const Runner = require('../runner.js');
11
13
  const UserFlow = require('./user-flow.js');
12
14
 
13
15
  /**
@@ -18,9 +20,48 @@ 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
+
21
61
  module.exports = {
22
62
  snapshot,
23
63
  startTimespan,
24
64
  navigation,
25
65
  startFlow,
66
+ generateFlowReport,
26
67
  };
@@ -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,
@@ -290,9 +290,9 @@ async function _cleanup({requestedUrl, driver, config}) {
290
290
  /**
291
291
  * @param {LH.NavigationRequestor} requestor
292
292
  * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
293
- * @return {Promise<LH.RunnerResult|undefined>}
293
+ * @return {Promise<LH.Gatherer.FRGatherResult>}
294
294
  */
295
- async function navigation(requestor, options) {
295
+ async function navigationGather(requestor, options) {
296
296
  const {page, configContext = {}} = options;
297
297
  log.setLevel(configContext.logLevel || 'error');
298
298
 
@@ -325,11 +325,11 @@ async function navigation(requestor, options) {
325
325
  },
326
326
  runnerOptions
327
327
  );
328
- return Runner.audit(artifacts, runnerOptions);
328
+ return {artifacts, runnerOptions};
329
329
  }
330
330
 
331
331
  module.exports = {
332
- navigation,
332
+ navigationGather,
333
333
  _setup,
334
334
  _setupNavigation,
335
335
  _navigate,
@@ -16,8 +16,11 @@ const {
16
16
  const {initializeConfig} = require('../config/config.js');
17
17
  const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
18
18
 
19
- /** @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options */
20
- 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) {
21
24
  const {configContext = {}} = options;
22
25
  log.setLevel(configContext.logLevel || 'error');
23
26
 
@@ -57,9 +60,9 @@ async function snapshot(options) {
57
60
  },
58
61
  runnerOptions
59
62
  );
60
- return Runner.audit(artifacts, runnerOptions);
63
+ return {artifacts, runnerOptions};
61
64
  }
62
65
 
63
66
  module.exports = {
64
- snapshot,
67
+ snapshotGather,
65
68
  };
@@ -19,9 +19,9 @@ const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
19
19
 
20
20
  /**
21
21
  * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
22
- * @return {Promise<{endTimespan(): Promise<LH.RunnerResult|undefined>}>}
22
+ * @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
23
23
  */
24
- async function startTimespan(options) {
24
+ async function startTimespanGather(options) {
25
25
  const {configContext = {}} = options;
26
26
  log.setLevel(configContext.logLevel || 'error');
27
27
 
@@ -52,7 +52,7 @@ async function startTimespan(options) {
52
52
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
53
53
 
54
54
  return {
55
- async endTimespan() {
55
+ async endTimespanGather() {
56
56
  const finalUrl = await driver.url();
57
57
  phaseOptions.url = finalUrl;
58
58
 
@@ -72,11 +72,11 @@ async function startTimespan(options) {
72
72
  },
73
73
  runnerOptions
74
74
  );
75
- return Runner.audit(artifacts, runnerOptions);
75
+ return {artifacts, runnerOptions};
76
76
  },
77
77
  };
78
78
  }
79
79
 
80
80
  module.exports = {
81
- startTimespan,
81
+ startTimespanGather,
82
82
  };
@@ -6,13 +6,15 @@
6
6
  'use strict';
7
7
 
8
8
  const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
9
- const {snapshot} = require('./gather/snapshot-runner.js');
10
- const {startTimespan} = require('./gather/timespan-runner.js');
11
- const {navigation} = require('./gather/navigation-runner.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 Runner = require('../runner.js');
12
13
 
13
- /** @typedef {Parameters<snapshot>[0]} FrOptions */
14
+ /** @typedef {Parameters<snapshotGather>[0]} FrOptions */
14
15
  /** @typedef {Omit<FrOptions, 'page'> & {name?: string}} UserFlowOptions */
15
16
  /** @typedef {Omit<FrOptions, 'page'> & {stepName?: string}} StepOptions */
17
+ /** @typedef {{gatherResult: LH.Gatherer.FRGatherResult, name: string}} StepArtifact */
16
18
 
17
19
  class UserFlow {
18
20
  /**
@@ -24,8 +26,8 @@ class UserFlow {
24
26
  this.options = {page, ...options};
25
27
  /** @type {string|undefined} */
26
28
  this.name = options?.name;
27
- /** @type {LH.FlowResult.Step[]} */
28
- this.steps = [];
29
+ /** @type {StepArtifact[]} */
30
+ this.stepArtifacts = [];
29
31
  }
30
32
 
31
33
  /**
@@ -38,12 +40,12 @@ class UserFlow {
38
40
  }
39
41
 
40
42
  /**
41
- * @param {LH.Result} lhr
43
+ * @param {LH.Artifacts} artifacts
42
44
  * @return {string}
43
45
  */
44
- _getDefaultStepName(lhr) {
45
- const shortUrl = this._shortenUrl(lhr.finalUrl);
46
- switch (lhr.gatherMode) {
46
+ _getDefaultStepName(artifacts) {
47
+ const shortUrl = this._shortenUrl(artifacts.URL.finalUrl);
48
+ switch (artifacts.GatherContext.gatherMode) {
47
49
  case 'navigation':
48
50
  return `Navigation report (${shortUrl})`;
49
51
  case 'timespan':
@@ -66,7 +68,8 @@ class UserFlow {
66
68
  }
67
69
 
68
70
  // On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
69
- const isSubsequentNavigation = this.steps.some(step => step.lhr.gatherMode === 'navigation');
71
+ const isSubsequentNavigation = this.stepArtifacts
72
+ .some(step => step.gatherResult.artifacts.GatherContext.gatherMode === 'navigation');
70
73
  if (isSubsequentNavigation) {
71
74
  if (settingsOverrides.disableStorageReset === undefined) {
72
75
  settingsOverrides.disableStorageReset = true;
@@ -84,85 +87,93 @@ class UserFlow {
84
87
  * @param {StepOptions=} stepOptions
85
88
  */
86
89
  async navigate(requestor, stepOptions) {
87
- if (this.currentTimespan) throw Error('Timespan already in progress');
90
+ if (this.currentTimespan) throw new Error('Timespan already in progress');
88
91
 
89
- const result = await navigation(
92
+ const gatherResult = await navigationGather(
90
93
  requestor,
91
94
  this._getNextNavigationOptions(stepOptions)
92
95
  );
93
- if (!result) throw Error('Navigation returned undefined');
94
96
 
95
97
  const providedName = stepOptions?.stepName;
96
- this.steps.push({
97
- lhr: result.lhr,
98
- name: providedName || this._getDefaultStepName(result.lhr),
98
+ this.stepArtifacts.push({
99
+ gatherResult,
100
+ name: providedName || this._getDefaultStepName(gatherResult.artifacts),
99
101
  });
100
102
 
101
- return result;
103
+ return gatherResult;
102
104
  }
103
105
 
104
106
  /**
105
107
  * @param {StepOptions=} stepOptions
106
108
  */
107
109
  async startTimespan(stepOptions) {
108
- if (this.currentTimespan) throw Error('Timespan already in progress');
110
+ if (this.currentTimespan) throw new Error('Timespan already in progress');
109
111
 
110
112
  const options = {...this.options, ...stepOptions};
111
- const timespan = await startTimespan(options);
113
+ const timespan = await startTimespanGather(options);
112
114
  this.currentTimespan = {timespan, options};
113
115
  }
114
116
 
115
117
  async endTimespan() {
116
- if (!this.currentTimespan) throw Error('No timespan in progress');
118
+ if (!this.currentTimespan) throw new Error('No timespan in progress');
117
119
 
118
120
  const {timespan, options} = this.currentTimespan;
119
- const result = await timespan.endTimespan();
121
+ const gatherResult = await timespan.endTimespanGather();
120
122
  this.currentTimespan = undefined;
121
- if (!result) throw Error('Timespan returned undefined');
122
123
 
123
124
  const providedName = options?.stepName;
124
- this.steps.push({
125
- lhr: result.lhr,
126
- name: providedName || this._getDefaultStepName(result.lhr),
125
+ this.stepArtifacts.push({
126
+ gatherResult,
127
+ name: providedName || this._getDefaultStepName(gatherResult.artifacts),
127
128
  });
128
129
 
129
- return result;
130
+ return gatherResult;
130
131
  }
131
132
 
132
133
  /**
133
134
  * @param {StepOptions=} stepOptions
134
135
  */
135
136
  async snapshot(stepOptions) {
136
- if (this.currentTimespan) throw Error('Timespan already in progress');
137
+ if (this.currentTimespan) throw new Error('Timespan already in progress');
137
138
 
138
139
  const options = {...this.options, ...stepOptions};
139
- const result = await snapshot(options);
140
- if (!result) throw Error('Snapshot returned undefined');
140
+ const gatherResult = await snapshotGather(options);
141
141
 
142
142
  const providedName = stepOptions?.stepName;
143
- this.steps.push({
144
- lhr: result.lhr,
145
- name: providedName || this._getDefaultStepName(result.lhr),
143
+ this.stepArtifacts.push({
144
+ gatherResult,
145
+ name: providedName || this._getDefaultStepName(gatherResult.artifacts),
146
146
  });
147
147
 
148
- return result;
148
+ return gatherResult;
149
149
  }
150
150
 
151
151
  /**
152
- * @return {LH.FlowResult}
152
+ * @returns {Promise<LH.FlowResult>}
153
153
  */
154
- getFlowResult() {
155
- if (!this.steps.length) throw Error('Need at least one step before getting the flow result');
156
- const url = new URL(this.steps[0].lhr.finalUrl);
157
- const name = this.name || `User flow (${url.hostname})`;
158
- return {steps: this.steps, name};
154
+ async createFlowResult() {
155
+ if (!this.stepArtifacts.length) {
156
+ throw new Error('Need at least one step before getting the result');
157
+ }
158
+ const url = new URL(this.stepArtifacts[0].gatherResult.artifacts.URL.finalUrl);
159
+ const flowName = this.name || `User flow (${url.hostname})`;
160
+
161
+ /** @type {LH.FlowResult['steps']} */
162
+ const steps = [];
163
+ for (const {gatherResult, name} of this.stepArtifacts) {
164
+ const result = await Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
165
+ if (!result) throw new Error(`Step "${name}" did not return a result`);
166
+ steps.push({lhr: result.lhr, name});
167
+ }
168
+
169
+ return {steps, name: flowName};
159
170
  }
160
171
 
161
172
  /**
162
- * @return {string}
173
+ * @return {Promise<string>}
163
174
  */
164
- generateReport() {
165
- const flowResult = this.getFlowResult();
175
+ async generateReport() {
176
+ const flowResult = await this.createFlowResult();
166
177
  return generateFlowReportHtml(flowResult);
167
178
  }
168
179
  }
@@ -73,7 +73,7 @@ class InspectorIssues extends FRGatherer {
73
73
  mixedContentIssue: [],
74
74
  navigatorUserAgentIssue: [],
75
75
  quirksModeIssue: [],
76
- sameSiteCookieIssue: [],
76
+ cookieIssue: [],
77
77
  sharedArrayBufferIssue: [],
78
78
  twaQualityEnforcement: [],
79
79
  };
@@ -5,7 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const isEqual = require('lodash.isequal');
8
+ const {isEqual} = require('lodash');
9
9
 
10
10
  /**
11
11
  * @fileoverview This class is designed to allow maps with arbitrary equality functions.
@@ -5,7 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const isDeepEqual = require('lodash.isequal');
8
+ const {isEqual: isDeepEqual} = require('lodash');
9
9
  const Driver = require('./gather/driver.js');
10
10
  const GatherRunner = require('./gather/gather-runner.js');
11
11
  const ReportScoring = require('./scoring.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.4.0-dev.20220227",
3
+ "version": "9.4.0-dev.20220302",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -115,10 +115,7 @@
115
115
  "@types/jest": "^27.0.3",
116
116
  "@types/jpeg-js": "^0.3.7",
117
117
  "@types/jsdom": "^16.2.13",
118
- "@types/lodash.clonedeep": "^4.5.6",
119
- "@types/lodash.get": "^4.4.6",
120
- "@types/lodash.isequal": "^4.5.2",
121
- "@types/lodash.set": "^4.3.6",
118
+ "@types/lodash": "^4.14.178",
122
119
  "@types/node": "*",
123
120
  "@types/pako": "^1.0.1",
124
121
  "@types/resize-observer-browser": "^0.1.1",
@@ -140,7 +137,7 @@
140
137
  "cpy": "^8.1.2",
141
138
  "cross-env": "^7.0.2",
142
139
  "csv-validator": "^0.0.3",
143
- "devtools-protocol": "0.0.964215",
140
+ "devtools-protocol": "0.0.975298",
144
141
  "es-main": "^1.0.2",
145
142
  "eslint": "^8.4.1",
146
143
  "eslint-config-google": "^0.14.0",
@@ -195,10 +192,7 @@
195
192
  "js-library-detector": "^6.4.0",
196
193
  "lighthouse-logger": "^1.3.0",
197
194
  "lighthouse-stack-packs": "^1.7.0",
198
- "lodash.clonedeep": "^4.5.0",
199
- "lodash.get": "^4.4.2",
200
- "lodash.isequal": "^4.5.0",
201
- "lodash.set": "^4.3.2",
195
+ "lodash": "^4.17.21",
202
196
  "lookup-closest-locale": "6.2.0",
203
197
  "metaviewport-parser": "0.2.0",
204
198
  "open": "^8.4.0",
@@ -213,7 +207,7 @@
213
207
  "yargs-parser": "^21.0.0"
214
208
  },
215
209
  "resolutions": {
216
- "puppeteer/**/devtools-protocol": "0.0.964215"
210
+ "puppeteer/**/devtools-protocol": "0.0.975298"
217
211
  },
218
212
  "repository": "GoogleChrome/lighthouse",
219
213
  "keywords": [
@@ -5,8 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const _set = require('lodash.set');
9
- const _get = require('lodash.get');
8
+ const {set: _set, get: _get} = require('lodash');
10
9
 
11
10
  const format = require('./format.js');
12
11
 
@@ -38,6 +38,7 @@ Array [
38
38
  "blockedByResponseIssueDetails",
39
39
  "clientHintIssueDetails",
40
40
  "contentSecurityPolicyIssueDetails",
41
+ "cookieIssueDetails",
41
42
  "corsIssueDetails",
42
43
  "deprecationIssueDetails",
43
44
  "federatedAuthRequestIssueDetails",
@@ -47,7 +48,6 @@ Array [
47
48
  "mixedContentIssueDetails",
48
49
  "navigatorUserAgentIssueDetails",
49
50
  "quirksModeIssueDetails",
50
- "sameSiteCookieIssueDetails",
51
51
  "sharedArrayBufferIssueDetails",
52
52
  "twaQualityEnforcementDetails",
53
53
  ]
@@ -45,7 +45,7 @@
45
45
  {"id":"npm:bootstrap:20120510","severity":"medium","semver":{"vulnerable":["<2.1.0"]}}
46
46
  ],
47
47
  "dojo":[
48
- {"id":"SNYK-JS-DOJO-1535223","severity":"high","semver":{"vulnerable":["<=1.16.4"]}},
48
+ {"id":"SNYK-JS-DOJO-1535223","severity":"high","semver":{"vulnerable":["<1.17.0"]}},
49
49
  {"id":"SNYK-JS-DOJO-559224","severity":"medium","semver":{"vulnerable":["<1.11.10",">=1.12.1 <1.12.8",">=1.13.0 <1.13.7",">=1.14.0 <1.14.6",">=1.15.0 <1.15.3",">=1.16.0 <1.16.2"]}},
50
50
  {"id":"SNYK-JS-DOJO-174934","severity":"medium","semver":{"vulnerable":[">=1.0.0 <1.0.3",">=1.1.0 <1.1.2",">=1.2.0 <1.2.4",">=1.3.0 <1.3.3",">=1.4.0 <1.4.2"]}},
51
51
  {"id":"SNYK-JS-DOJO-174933","severity":"medium","semver":{"vulnerable":["<1.2.0"]}},
@@ -70,7 +70,7 @@
70
70
  {"id":"SNYK-JS-HANDLEBARS-1279029","severity":"medium","semver":{"vulnerable":["<4.7.7"]}},
71
71
  {"id":"SNYK-JS-HANDLEBARS-1056767","severity":"high","semver":{"vulnerable":["<4.7.7"]}},
72
72
  {"id":"SNYK-JS-HANDLEBARS-567742","severity":"medium","semver":{"vulnerable":["<4.6.0"]}},
73
- {"id":"SNYK-JS-HANDLEBARS-534988","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.5.3","<3.0.8"]}},
73
+ {"id":"SNYK-JS-HANDLEBARS-534988","severity":"critical","semver":{"vulnerable":[">=4.0.0 <4.5.3","<3.0.8"]}},
74
74
  {"id":"SNYK-JS-HANDLEBARS-534478","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.5.3","<3.0.8"]}},
75
75
  {"id":"SNYK-JS-HANDLEBARS-480388","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.4.5"]}},
76
76
  {"id":"SNYK-JS-HANDLEBARS-469063","severity":"high","semver":{"vulnerable":[">=4.0.0 <4.3.0","<3.8.0"]}},
@@ -116,7 +116,7 @@
116
116
  {"id":"SNYK-JS-LODASH-1040724","severity":"high","semver":{"vulnerable":["<4.17.21"]}},
117
117
  {"id":"SNYK-JS-LODASH-1018905","severity":"medium","semver":{"vulnerable":["<4.17.21"]}},
118
118
  {"id":"SNYK-JS-LODASH-608086","severity":"high","semver":{"vulnerable":["<4.17.17"]}},
119
- {"id":"SNYK-JS-LODASH-590103","severity":"high","semver":{"vulnerable":["<4.17.20"]}},
119
+ {"id":"SNYK-JS-LODASH-590103","severity":"critical","semver":{"vulnerable":["<4.17.20"]}},
120
120
  {"id":"SNYK-JS-LODASH-567746","severity":"medium","semver":{"vulnerable":["<4.17.16"]}},
121
121
  {"id":"SNYK-JS-LODASH-450202","severity":"high","semver":{"vulnerable":["<4.17.12"]}},
122
122
  {"id":"SNYK-JS-LODASH-73639","severity":"medium","semver":{"vulnerable":["<4.17.11"]}},
@@ -566,7 +566,7 @@ declare module Artifacts {
566
566
  mixedContentIssue: LH.Crdp.Audits.MixedContentIssueDetails[];
567
567
  navigatorUserAgentIssue: LH.Crdp.Audits.NavigatorUserAgentIssueDetails[];
568
568
  quirksModeIssue: LH.Crdp.Audits.QuirksModeIssueDetails[];
569
- sameSiteCookieIssue: LH.Crdp.Audits.SameSiteCookieIssueDetails[];
569
+ cookieIssue: LH.Crdp.Audits.CookieIssueDetails[];
570
570
  sharedArrayBufferIssue: LH.Crdp.Audits.SharedArrayBufferIssueDetails[];
571
571
  twaQualityEnforcement: LH.Crdp.Audits.TrustedWebActivityIssueDetails[];
572
572
  }
@@ -62,6 +62,14 @@ declare module Gatherer {
62
62
  settings: Config.Settings;
63
63
  }
64
64
 
65
+ interface FRGatherResult {
66
+ artifacts: Artifacts;
67
+ runnerOptions: {
68
+ config: Config.FRConfig;
69
+ computedCache: Map<string, ArbitraryEqualityMap>
70
+ }
71
+ }
72
+
65
73
  interface PassContext {
66
74
  gatherMode: 'navigation';
67
75
  /** The url of the currently loaded page. If the main document redirects, this will be updated to keep track. */
@@ -52,7 +52,7 @@ declare global {
52
52
  {expectations: Smokehouse.ExpectedRunnerResult | Array<Smokehouse.ExpectedRunnerResult>}
53
53
 
54
54
  export type LighthouseRunner =
55
- (url: string, configJson?: Config.Json, runnerOptions?: {isDebug?: boolean; useFraggleRock?: boolean}) => Promise<{lhr: LHResult, artifacts: Artifacts, log: string}>;
55
+ {runnerName?: string} & ((url: string, configJson?: Config.Json, runnerOptions?: {isDebug?: boolean; useFraggleRock?: boolean}) => Promise<{lhr: LHResult, artifacts: Artifacts, log: string}>);
56
56
 
57
57
  export interface SmokehouseOptions {
58
58
  /** If true, performs extra logging from the test runs. */