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
@@ -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 {
@@ -18,10 +19,12 @@ const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
18
19
 
19
20
  /**
20
21
  * @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
21
- * @return {Promise<{endTimespan(): Promise<LH.RunnerResult|undefined>}>}
22
+ * @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
22
23
  */
23
- async function startTimespan(options) {
24
+ async function startTimespanGather(options) {
24
25
  const {configContext = {}} = options;
26
+ log.setLevel(configContext.logLevel || 'error');
27
+
25
28
  const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'timespan'});
26
29
  const driver = new Driver(options.page);
27
30
  await driver.connect();
@@ -29,11 +32,12 @@ async function startTimespan(options) {
29
32
  /** @type {Map<string, LH.ArbitraryEqualityMap>} */
30
33
  const computedCache = new Map();
31
34
  const artifactDefinitions = config.artifacts || [];
32
- const requestedUrl = await options.page.url();
35
+ const requestedUrl = await driver.url();
33
36
  const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
34
37
  const artifactState = getEmptyArtifactState();
35
38
  /** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
36
39
  const phaseOptions = {
40
+ url: requestedUrl,
37
41
  driver,
38
42
  artifactDefinitions,
39
43
  artifactState,
@@ -48,8 +52,10 @@ async function startTimespan(options) {
48
52
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
49
53
 
50
54
  return {
51
- async endTimespan() {
52
- const finalUrl = await options.page.url();
55
+ async endTimespanGather() {
56
+ const finalUrl = await driver.url();
57
+ phaseOptions.url = finalUrl;
58
+
53
59
  const runnerOptions = {config, computedCache};
54
60
  const artifacts = await Runner.gather(
55
61
  async () => {
@@ -66,11 +72,11 @@ async function startTimespan(options) {
66
72
  },
67
73
  runnerOptions
68
74
  );
69
- return Runner.audit(artifacts, runnerOptions);
75
+ return {artifacts, runnerOptions};
70
76
  },
71
77
  };
72
78
  }
73
79
 
74
80
  module.exports = {
75
- startTimespan,
81
+ startTimespanGather,
76
82
  };
@@ -6,13 +6,16 @@
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');
13
+ const {initializeConfig} = require('./config/config.js');
12
14
 
13
- /** @typedef {Parameters<snapshot>[0]} FrOptions */
15
+ /** @typedef {Parameters<snapshotGather>[0]} FrOptions */
14
16
  /** @typedef {Omit<FrOptions, 'page'> & {name?: string}} UserFlowOptions */
15
17
  /** @typedef {Omit<FrOptions, 'page'> & {stepName?: string}} StepOptions */
18
+ /** @typedef {WeakMap<LH.UserFlow.GatherStep, LH.Gatherer.FRGatherResult['runnerOptions']>} GatherStepRunnerOptions */
16
19
 
17
20
  class UserFlow {
18
21
  /**
@@ -24,8 +27,10 @@ class UserFlow {
24
27
  this.options = {page, ...options};
25
28
  /** @type {string|undefined} */
26
29
  this.name = options?.name;
27
- /** @type {LH.FlowResult.Step[]} */
28
- this.steps = [];
30
+ /** @type {LH.UserFlow.GatherStep[]} */
31
+ this._gatherSteps = [];
32
+ /** @type {GatherStepRunnerOptions} */
33
+ this._gatherStepRunnerOptions = new WeakMap();
29
34
  }
30
35
 
31
36
  /**
@@ -38,12 +43,12 @@ class UserFlow {
38
43
  }
39
44
 
40
45
  /**
41
- * @param {LH.Result} lhr
46
+ * @param {LH.Artifacts} artifacts
42
47
  * @return {string}
43
48
  */
44
- _getDefaultStepName(lhr) {
45
- const shortUrl = this._shortenUrl(lhr.finalUrl);
46
- switch (lhr.gatherMode) {
49
+ _getDefaultStepName(artifacts) {
50
+ const shortUrl = this._shortenUrl(artifacts.URL.finalUrl);
51
+ switch (artifacts.GatherContext.gatherMode) {
47
52
  case 'navigation':
48
53
  return `Navigation report (${shortUrl})`;
49
54
  case 'timespan':
@@ -66,7 +71,8 @@ class UserFlow {
66
71
  }
67
72
 
68
73
  // 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');
74
+ const isSubsequentNavigation = this._gatherSteps
75
+ .some(step => step.artifacts.GatherContext.gatherMode === 'navigation');
70
76
  if (isSubsequentNavigation) {
71
77
  if (settingsOverrides.disableStorageReset === undefined) {
72
78
  settingsOverrides.disableStorageReset = true;
@@ -79,92 +85,145 @@ class UserFlow {
79
85
  return options;
80
86
  }
81
87
 
88
+ /**
89
+ *
90
+ * @param {LH.Gatherer.FRGatherResult} gatherResult
91
+ * @param {StepOptions} options
92
+ */
93
+ _addGatherStep(gatherResult, options) {
94
+ const providedName = options?.stepName;
95
+ const gatherStep = {
96
+ artifacts: gatherResult.artifacts,
97
+ name: providedName || this._getDefaultStepName(gatherResult.artifacts),
98
+ config: options.config,
99
+ configContext: options.configContext,
100
+ };
101
+ this._gatherSteps.push(gatherStep);
102
+ this._gatherStepRunnerOptions.set(gatherStep, gatherResult.runnerOptions);
103
+ }
104
+
82
105
  /**
83
106
  * @param {LH.NavigationRequestor} requestor
84
107
  * @param {StepOptions=} stepOptions
85
108
  */
86
109
  async navigate(requestor, stepOptions) {
87
- if (this.currentTimespan) throw Error('Timespan already in progress');
88
-
89
- const result = await navigation(
90
- requestor,
91
- this._getNextNavigationOptions(stepOptions)
92
- );
93
- if (!result) throw Error('Navigation returned undefined');
94
-
95
- const providedName = stepOptions?.stepName;
96
- this.steps.push({
97
- lhr: result.lhr,
98
- name: providedName || this._getDefaultStepName(result.lhr),
99
- });
110
+ if (this.currentTimespan) throw new Error('Timespan already in progress');
111
+
112
+ const options = this._getNextNavigationOptions(stepOptions);
113
+ const gatherResult = await navigationGather(requestor, options);
100
114
 
101
- return result;
115
+ this._addGatherStep(gatherResult, options);
116
+
117
+ return gatherResult;
102
118
  }
103
119
 
104
120
  /**
105
121
  * @param {StepOptions=} stepOptions
106
122
  */
107
123
  async startTimespan(stepOptions) {
108
- if (this.currentTimespan) throw Error('Timespan already in progress');
124
+ if (this.currentTimespan) throw new Error('Timespan already in progress');
109
125
 
110
126
  const options = {...this.options, ...stepOptions};
111
- const timespan = await startTimespan(options);
127
+ const timespan = await startTimespanGather(options);
112
128
  this.currentTimespan = {timespan, options};
113
129
  }
114
130
 
115
131
  async endTimespan() {
116
- if (!this.currentTimespan) throw Error('No timespan in progress');
132
+ if (!this.currentTimespan) throw new Error('No timespan in progress');
117
133
 
118
134
  const {timespan, options} = this.currentTimespan;
119
- const result = await timespan.endTimespan();
135
+ const gatherResult = await timespan.endTimespanGather();
120
136
  this.currentTimespan = undefined;
121
- if (!result) throw Error('Timespan returned undefined');
122
137
 
123
- const providedName = options?.stepName;
124
- this.steps.push({
125
- lhr: result.lhr,
126
- name: providedName || this._getDefaultStepName(result.lhr),
127
- });
138
+ this._addGatherStep(gatherResult, options);
128
139
 
129
- return result;
140
+ return gatherResult;
130
141
  }
131
142
 
132
143
  /**
133
144
  * @param {StepOptions=} stepOptions
134
145
  */
135
146
  async snapshot(stepOptions) {
136
- if (this.currentTimespan) throw Error('Timespan already in progress');
147
+ if (this.currentTimespan) throw new Error('Timespan already in progress');
137
148
 
138
149
  const options = {...this.options, ...stepOptions};
139
- const result = await snapshot(options);
140
- if (!result) throw Error('Snapshot returned undefined');
150
+ const gatherResult = await snapshotGather(options);
141
151
 
142
- const providedName = stepOptions?.stepName;
143
- this.steps.push({
144
- lhr: result.lhr,
145
- name: providedName || this._getDefaultStepName(result.lhr),
146
- });
152
+ this._addGatherStep(gatherResult, options);
147
153
 
148
- return result;
154
+ return gatherResult;
149
155
  }
150
156
 
151
157
  /**
152
- * @return {LH.FlowResult}
158
+ * @returns {Promise<LH.FlowResult>}
153
159
  */
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};
160
+ async createFlowResult() {
161
+ return auditGatherSteps(this._gatherSteps, {
162
+ name: this.name,
163
+ config: this.options.config,
164
+ gatherStepRunnerOptions: this._gatherStepRunnerOptions,
165
+ });
159
166
  }
160
167
 
161
168
  /**
162
- * @return {string}
169
+ * @return {Promise<string>}
163
170
  */
164
- generateReport() {
165
- const flowResult = this.getFlowResult();
171
+ async generateReport() {
172
+ const flowResult = await this.createFlowResult();
166
173
  return generateFlowReportHtml(flowResult);
167
174
  }
175
+
176
+ /**
177
+ * @return {LH.UserFlow.FlowArtifacts}
178
+ */
179
+ createArtifactsJson() {
180
+ return {
181
+ gatherSteps: this._gatherSteps,
182
+ name: this.name,
183
+ };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * @param {Array<LH.UserFlow.GatherStep>} gatherSteps
189
+ * @param {{name?: string, config?: LH.Config.Json, gatherStepRunnerOptions?: GatherStepRunnerOptions}} options
190
+ */
191
+ async function auditGatherSteps(gatherSteps, options) {
192
+ if (!gatherSteps.length) {
193
+ throw new Error('Need at least one step before getting the result');
194
+ }
195
+
196
+ /** @type {LH.FlowResult['steps']} */
197
+ const steps = [];
198
+ for (const gatherStep of gatherSteps) {
199
+ const {artifacts, name, configContext} = gatherStep;
200
+
201
+ let runnerOptions = options.gatherStepRunnerOptions?.get(gatherStep);
202
+
203
+ // If the gather step is not active, we must recreate the runner options.
204
+ if (!runnerOptions) {
205
+ // Step specific configs take precedence over a config for the entire flow.
206
+ const configJson = gatherStep.config || options.config;
207
+ const {gatherMode} = artifacts.GatherContext;
208
+ const {config} = initializeConfig(configJson, {...configContext, gatherMode});
209
+ runnerOptions = {
210
+ config,
211
+ computedCache: new Map(),
212
+ };
213
+ }
214
+
215
+ const result = await Runner.audit(artifacts, runnerOptions);
216
+ if (!result) throw new Error(`Step "${name}" did not return a result`);
217
+ steps.push({lhr: result.lhr, name});
218
+ }
219
+
220
+ const url = new URL(gatherSteps[0].artifacts.URL.finalUrl);
221
+ const flowName = options.name || `User flow (${url.hostname})`;
222
+ return {steps, name: flowName};
168
223
  }
169
224
 
170
- module.exports = UserFlow;
225
+
226
+ module.exports = {
227
+ UserFlow,
228
+ auditGatherSteps,
229
+ };
@@ -70,21 +70,22 @@ class FullPageScreenshot extends FRGatherer {
70
70
 
71
71
  /**
72
72
  * @param {LH.Gatherer.FRTransitionalContext} context
73
+ * @param {{height: number, width: number, mobile: boolean}} deviceMetrics
73
74
  * @return {Promise<LH.Artifacts.FullPageScreenshot['screenshot']>}
74
75
  */
75
- async _takeScreenshot(context) {
76
+ async _takeScreenshot(context, deviceMetrics) {
76
77
  const session = context.driver.defaultSession;
77
78
  const maxTextureSize = await this.getMaxTextureSize(context);
78
79
  const metrics = await session.sendCommand('Page.getLayoutMetrics');
79
80
 
80
- // Width should match emulated width, without considering content overhang.
81
- // Both layoutViewport and visualViewport capture this. visualViewport accounts
82
- // for page zoom/scale, which we currently don't account for (or expect). So we use layoutViewport.width.
83
- // Note: If the page is zoomed, many assumptions fail.
84
- //
85
- // Height should be as tall as the content. So we use contentSize.height
86
- const width = Math.min(metrics.layoutViewport.clientWidth, maxTextureSize);
87
- const height = Math.min(metrics.contentSize.height, maxTextureSize);
81
+ // Height should be as tall as the content.
82
+ // Scale the emulated height to reach the content height.
83
+ const fullHeight = Math.round(
84
+ deviceMetrics.height *
85
+ metrics.contentSize.height /
86
+ metrics.layoutViewport.clientHeight
87
+ );
88
+ const height = Math.min(fullHeight, maxTextureSize);
88
89
 
89
90
  // Setup network monitor before we change the viewport.
90
91
  const networkMonitor = new NetworkMonitor(session);
@@ -98,13 +99,10 @@ class FullPageScreenshot extends FRGatherer {
98
99
  await networkMonitor.enable();
99
100
 
100
101
  await session.sendCommand('Emulation.setDeviceMetricsOverride', {
101
- // If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
102
- mobile: context.settings.screenEmulation.mobile,
103
- height,
104
- width,
102
+ mobile: deviceMetrics.mobile,
105
103
  deviceScaleFactor: 1,
106
- scale: 1,
107
- screenOrientation: {angle: 0, type: 'portraitPrimary'},
104
+ height,
105
+ width: 0, // Leave width unchanged
108
106
  });
109
107
 
110
108
  // Now that the viewport is taller, give the page some time to fetch new resources that
@@ -127,7 +125,7 @@ class FullPageScreenshot extends FRGatherer {
127
125
 
128
126
  return {
129
127
  data,
130
- width,
128
+ width: deviceMetrics.width,
131
129
  height,
132
130
  };
133
131
  }
@@ -185,29 +183,37 @@ class FullPageScreenshot extends FRGatherer {
185
183
  const session = context.driver.defaultSession;
186
184
  const executionContext = context.driver.executionContext;
187
185
  const settings = context.settings;
188
-
189
- // In case some other program is controlling emulation, remember what the device looks
190
- // like now and reset after gatherer is done.
191
- let observedDeviceMetrics;
192
186
  const lighthouseControlsEmulation = !settings.screenEmulation.disabled;
187
+
188
+ // Make a copy so we don't modify the config settings.
189
+ /** @type {{width: number, height: number, deviceScaleFactor: number, mobile: boolean}} */
190
+ const deviceMetrics = {...settings.screenEmulation};
191
+
192
+ // In case some other program is controlling emulation, remember what the device looks like now and reset after gatherer is done.
193
+ // If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
193
194
  if (!lighthouseControlsEmulation) {
194
- observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
195
+ const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
195
196
  args: [],
196
197
  useIsolation: true,
197
198
  deps: [kebabCaseToCamelCase],
198
199
  });
200
+ deviceMetrics.height = observedDeviceMetrics.height;
201
+ deviceMetrics.width = observedDeviceMetrics.width;
202
+ deviceMetrics.deviceScaleFactor = observedDeviceMetrics.deviceScaleFactor;
203
+ // If screen emulation is disabled, use formFactor to determine if we are on mobile.
204
+ deviceMetrics.mobile = settings.formFactor === 'mobile';
199
205
  }
200
206
 
201
207
  try {
202
208
  return {
203
- screenshot: await this._takeScreenshot(context),
209
+ screenshot: await this._takeScreenshot(context, deviceMetrics),
204
210
  nodes: await this._resolveNodes(context),
205
211
  };
206
212
  } finally {
207
213
  // Revert resized page.
208
214
  if (lighthouseControlsEmulation) {
209
215
  await emulation.emulate(session, settings);
210
- } else if (observedDeviceMetrics) {
216
+ } else {
211
217
  // Best effort to reset emulation to what it was.
212
218
  // https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
213
219
  // TODO: seems like this would be brittle. Should at least work for devtools, but what
@@ -216,8 +222,10 @@ class FullPageScreenshot extends FRGatherer {
216
222
  // and then just call that to reset?
217
223
  // https://github.com/GoogleChrome/lighthouse/issues/11122
218
224
  await session.sendCommand('Emulation.setDeviceMetricsOverride', {
219
- mobile: settings.formFactor === 'mobile',
220
- ...observedDeviceMetrics,
225
+ mobile: deviceMetrics.mobile,
226
+ deviceScaleFactor: deviceMetrics.deviceScaleFactor,
227
+ height: deviceMetrics.height,
228
+ width: 0, // Leave width unchanged
221
229
  });
222
230
  }
223
231
  }
@@ -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.
@@ -313,6 +313,20 @@ async function saveLanternNetworkData(devtoolsLog, outputPath) {
313
313
  fs.writeFileSync(outputPath, JSON.stringify(lanternData));
314
314
  }
315
315
 
316
+ /**
317
+ * Normalize timing data so it doesn't change every update.
318
+ * @param {LH.Result.MeasureEntry[]} timings
319
+ */
320
+ function normalizeTimingEntries(timings) {
321
+ let baseTime = 0;
322
+ for (const timing of timings) {
323
+ // @ts-expect-error: Value actually is writeable at this point.
324
+ timing.startTime = baseTime++;
325
+ // @ts-expect-error: Value actually is writeable at this point.
326
+ timing.duration = 1;
327
+ }
328
+ }
329
+
316
330
  module.exports = {
317
331
  saveArtifacts,
318
332
  saveLhr,
@@ -323,4 +337,5 @@ module.exports = {
323
337
  saveDevtoolsLog,
324
338
  saveLanternNetworkData,
325
339
  stringifyReplacer,
340
+ normalizeTimingEntries,
326
341
  };
@@ -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');
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "module",
3
+ "//": "Any directory that uses `import ... from` or `export ...` must be type module. Temporary file until root package.json is type: module"
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.4.0",
3
+ "version": "9.5.0",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "build-extension": "yarn build-extension-chrome && yarn build-extension-firefox",
21
21
  "build-extension-chrome": "node ./build/build-extension.js chrome",
22
22
  "build-extension-firefox": "node ./build/build-extension.js firefox",
23
- "build-devtools": "yarn reset-link && node ./build/build-bundle.js clients/devtools-entry.js dist/lighthouse-dt-bundle.js && node ./build/build-dt-report-resources.js",
23
+ "build-devtools": "yarn reset-link && node ./build/build-bundle.js clients/devtools/devtools-entry.js dist/lighthouse-dt-bundle.js && node ./build/build-dt-report-resources.js",
24
24
  "build-smokehouse-bundle": "node ./build/build-smokehouse-bundle.js",
25
25
  "build-lr": "yarn reset-link && node ./build/build-lightrider-bundles.js",
26
26
  "build-pack": "bash build/build-pack.sh",
@@ -76,6 +76,7 @@
76
76
  "update:sample-artifacts": "node lighthouse-core/scripts/update-report-fixtures.js",
77
77
  "update:sample-json": "yarn i18n:collect-strings && node ./lighthouse-cli -A=./lighthouse-core/test/results/artifacts --config-path=./lighthouse-core/test/results/sample-config.js --output=json --output-path=./lighthouse-core/test/results/sample_v2.json && node lighthouse-core/scripts/cleanup-LHR-for-diff.js ./lighthouse-core/test/results/sample_v2.json --only-remove-timing",
78
78
  "update:flow-sample-json": "node ./lighthouse-core/scripts/update-flow-fixtures.js",
79
+ "update:snapshot-sample-json": "node ./lighthouse-core/scripts/update-snapshot-sample.js",
79
80
  "update:test-devtools": "bash lighthouse-core/test/chromium-web-tests/test-locally.sh --reset-results",
80
81
  "test-devtools": "bash lighthouse-core/test/chromium-web-tests/test-locally.sh",
81
82
  "open-devtools": "bash lighthouse-core/scripts/open-devtools.sh",
@@ -115,10 +116,7 @@
115
116
  "@types/jest": "^27.0.3",
116
117
  "@types/jpeg-js": "^0.3.7",
117
118
  "@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",
119
+ "@types/lodash": "^4.14.178",
122
120
  "@types/node": "*",
123
121
  "@types/pako": "^1.0.1",
124
122
  "@types/resize-observer-browser": "^0.1.1",
@@ -140,7 +138,7 @@
140
138
  "cpy": "^8.1.2",
141
139
  "cross-env": "^7.0.2",
142
140
  "csv-validator": "^0.0.3",
143
- "devtools-protocol": "0.0.964215",
141
+ "devtools-protocol": "0.0.975298",
144
142
  "es-main": "^1.0.2",
145
143
  "eslint": "^8.4.1",
146
144
  "eslint-config-google": "^0.14.0",
@@ -195,10 +193,7 @@
195
193
  "js-library-detector": "^6.4.0",
196
194
  "lighthouse-logger": "^1.3.0",
197
195
  "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",
196
+ "lodash": "^4.17.21",
202
197
  "lookup-closest-locale": "6.2.0",
203
198
  "metaviewport-parser": "0.2.0",
204
199
  "open": "^8.4.0",
@@ -213,7 +208,7 @@
213
208
  "yargs-parser": "^21.0.0"
214
209
  },
215
210
  "resolutions": {
216
- "puppeteer/**/devtools-protocol": "0.0.964215"
211
+ "puppeteer/**/devtools-protocol": "0.0.975298"
217
212
  },
218
213
  "repository": "GoogleChrome/lighthouse",
219
214
  "keywords": [
@@ -189,7 +189,8 @@ limitations under the License.
189
189
  animation: none;
190
190
  }
191
191
 
192
- .lh-sticky-header .lh-gauge__label {
192
+ .lh-sticky-header .lh-gauge__label,
193
+ .lh-sticky-header .lh-fraction__label {
193
194
  display: none;
194
195
  }
195
196
 
@@ -445,7 +445,7 @@ function createScorescaleComponent(dom) {
445
445
  function createScoresWrapperComponent(dom) {
446
446
  const el0 = dom.createFragment();
447
447
  const el1 = dom.createElement('style');
448
- el1.append('\n .lh-scores-container {\n display: flex;\n flex-direction: column;\n padding: var(--default-padding) 0;\n position: relative;\n width: 100%;\n }\n\n .lh-sticky-header {\n --gauge-circle-size: var(--gauge-circle-size-sm);\n --plugin-badge-size: 16px;\n --plugin-icon-size: 75%;\n --gauge-wrapper-width: 60px;\n --gauge-percentage-font-size: 13px;\n position: fixed;\n left: 0;\n right: 0;\n top: var(--topbar-height);\n font-weight: 500;\n display: none;\n justify-content: center;\n background-color: var(--sticky-header-background-color);\n border-bottom: 1px solid var(--color-gray-200);\n padding-top: var(--score-container-padding);\n padding-bottom: 4px;\n z-index: 1;\n pointer-events: none;\n }\n\n .lh-devtools .lh-sticky-header {\n /* The report within DevTools is placed in a container with overflow, which changes the placement of this header unless we change `position` to `sticky.` */\n position: sticky;\n }\n\n .lh-sticky-header--visible {\n display: grid;\n grid-auto-flow: column;\n pointer-events: auto;\n }\n\n /* Disable the gauge arc animation for the sticky header, so toggling display: none\n does not play the animation. */\n .lh-sticky-header .lh-gauge-arc {\n animation: none;\n }\n\n .lh-sticky-header .lh-gauge__label {\n display: none;\n }\n\n .lh-highlighter {\n width: var(--gauge-wrapper-width);\n height: 1px;\n background-color: var(--highlighter-background-color);\n /* Position at bottom of first gauge in sticky header. */\n position: absolute;\n grid-column: 1;\n bottom: -1px;\n }\n\n .lh-gauge__wrapper:first-of-type {\n contain: none;\n }\n ');
448
+ el1.append('\n .lh-scores-container {\n display: flex;\n flex-direction: column;\n padding: var(--default-padding) 0;\n position: relative;\n width: 100%;\n }\n\n .lh-sticky-header {\n --gauge-circle-size: var(--gauge-circle-size-sm);\n --plugin-badge-size: 16px;\n --plugin-icon-size: 75%;\n --gauge-wrapper-width: 60px;\n --gauge-percentage-font-size: 13px;\n position: fixed;\n left: 0;\n right: 0;\n top: var(--topbar-height);\n font-weight: 500;\n display: none;\n justify-content: center;\n background-color: var(--sticky-header-background-color);\n border-bottom: 1px solid var(--color-gray-200);\n padding-top: var(--score-container-padding);\n padding-bottom: 4px;\n z-index: 1;\n pointer-events: none;\n }\n\n .lh-devtools .lh-sticky-header {\n /* The report within DevTools is placed in a container with overflow, which changes the placement of this header unless we change `position` to `sticky.` */\n position: sticky;\n }\n\n .lh-sticky-header--visible {\n display: grid;\n grid-auto-flow: column;\n pointer-events: auto;\n }\n\n /* Disable the gauge arc animation for the sticky header, so toggling display: none\n does not play the animation. */\n .lh-sticky-header .lh-gauge-arc {\n animation: none;\n }\n\n .lh-sticky-header .lh-gauge__label,\n .lh-sticky-header .lh-fraction__label {\n display: none;\n }\n\n .lh-highlighter {\n width: var(--gauge-wrapper-width);\n height: 1px;\n background-color: var(--highlighter-background-color);\n /* Position at bottom of first gauge in sticky header. */\n position: absolute;\n grid-column: 1;\n bottom: -1px;\n }\n\n .lh-gauge__wrapper:first-of-type {\n contain: none;\n }\n ');
449
449
  el0.append(el1);
450
450
  const el2 = dom.createElement('div', 'lh-scores-wrapper');
451
451
  const el3 = dom.createElement('div', 'lh-scores-container');
@@ -306,7 +306,8 @@ export class TopbarFeatures {
306
306
  categoriesAboveTheMiddle.length > 0 ? categoriesAboveTheMiddle.length - 1 : 0;
307
307
 
308
308
  // Category order matches gauge order in sticky header.
309
- const gaugeWrapperEls = this.stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper');
309
+ const gaugeWrapperEls =
310
+ this.stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper, .lh-fraction__wrapper');
310
311
  const gaugeToHighlight = gaugeWrapperEls[highlightIndex];
311
312
  const origin = gaugeWrapperEls[0].getBoundingClientRect().left;
312
313
  const offset = gaugeToHighlight.getBoundingClientRect().left - origin;
@@ -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
  ]