lighthouse 12.6.1-dev.20250602 → 12.6.1-dev.20250604

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.
@@ -291,7 +291,7 @@ class RenderBlockingResources extends Audit {
291
291
  const headings = [
292
292
  {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
293
293
  {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
294
- {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
294
+ {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnDuration)},
295
295
  ];
296
296
 
297
297
  const details = Audit.makeOpportunityDetails(headings, results,
@@ -114,7 +114,7 @@ class CLSCulpritsInsight extends Audit {
114
114
  /** @type {LH.Audit.Details.Table['items']} */
115
115
  const items = events.map(event => {
116
116
  const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
117
- event.args.data.impacted_nodes || [], impactByNodeId, event);
117
+ event.args.data.impacted_nodes || [], impactByNodeId);
118
118
  return {
119
119
  node: makeNodeItemForNodeId(artifacts.TraceElements, biggestImpactNodeId),
120
120
  score: event.args.data?.weighted_score_delta,
@@ -41,7 +41,7 @@ class RenderBlockingInsight extends Audit {
41
41
  const headings = [
42
42
  {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
43
43
  {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
44
- {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
44
+ {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnDuration)},
45
45
  ];
46
46
  /** @type {LH.Audit.Details.Table['items']} */
47
47
  const items = insight.renderBlockingRequests.map(request => ({
@@ -89,7 +89,7 @@ class LayoutShifts extends Audit {
89
89
  .slice(0, MAX_LAYOUT_SHIFTS);
90
90
  for (const event of topLayoutShiftEvents) {
91
91
  const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
92
- event.args.data.impacted_nodes || [], impactByNodeId, event);
92
+ event.args.data.impacted_nodes || [], impactByNodeId);
93
93
  const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
94
94
 
95
95
  // Turn root causes into sub-items.
@@ -21,6 +21,7 @@ import {TotalBlockingTime} from './total-blocking-time.js';
21
21
  import {makeComputedArtifact} from '../computed-artifact.js';
22
22
  import {TimeToFirstByte} from './time-to-first-byte.js';
23
23
  import {LCPBreakdown} from './lcp-breakdown.js';
24
+ import {isUnderTest} from '../../lib/lh-env.js';
24
25
 
25
26
  class TimingSummary {
26
27
  /**
@@ -46,7 +47,9 @@ class TimingSummary {
46
47
  */
47
48
  const requestOrUndefined = (Artifact, artifact) => {
48
49
  return Artifact.request(artifact, context).catch(err => {
49
- log.error('lh:computed:TimingSummary', err);
50
+ if (isUnderTest) {
51
+ log.error('lh:computed:TimingSummary', err);
52
+ }
50
53
  return undefined;
51
54
  });
52
55
  };
@@ -58,11 +58,13 @@ export class ExecutionContext {
58
58
  */
59
59
  _evaluateInContext(expression: string, contextId: number | undefined, timeout: number): Promise<any>;
60
60
  /**
61
- * Note: Prefer `evaluate` instead.
62
61
  * Evaluate an expression in the context of the current page. If useIsolation is true, the expression
63
62
  * will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
64
63
  * is completely separate.
65
64
  * Returns a promise that resolves on the expression's value.
65
+ *
66
+ * @deprecated Use `evaluate` instead! It has a better API, and unlike `evaluateAsync` doesn't sometimes
67
+ * execute invalid code.
66
68
  * @param {string} expression
67
69
  * @param {{useIsolation?: boolean}=} options
68
70
  * @return {Promise<*>}
@@ -151,11 +151,13 @@ class ExecutionContext {
151
151
  }
152
152
 
153
153
  /**
154
- * Note: Prefer `evaluate` instead.
155
154
  * Evaluate an expression in the context of the current page. If useIsolation is true, the expression
156
155
  * will be evaluated in a content script that has access to the page's DOM but whose JavaScript state
157
156
  * is completely separate.
158
157
  * Returns a promise that resolves on the expression's value.
158
+ *
159
+ * @deprecated Use `evaluate` instead! It has a better API, and unlike `evaluateAsync` doesn't sometimes
160
+ * execute invalid code.
159
161
  * @param {string} expression
160
162
  * @param {{useIsolation?: boolean}=} options
161
163
  * @return {Promise<*>}
@@ -30,7 +30,10 @@ class CSSUsage extends BaseGatherer {
30
30
 
31
31
  // Force style to recompute.
32
32
  // Doesn't appear to be necessary in newer versions of Chrome.
33
- await executionContext.evaluateAsync('getComputedStyle(document.body)');
33
+ /* global window, document */
34
+ await executionContext.evaluate(() => window.getComputedStyle(document.body), {
35
+ args: [],
36
+ });
34
37
 
35
38
  const {ruleUsage} = await session.sendCommand('CSS.stopRuleUsageTracking');
36
39
  await session.sendCommand('CSS.disable');
@@ -74,7 +74,10 @@ class Stylesheets extends BaseGatherer {
74
74
 
75
75
  // Force style to recompute.
76
76
  // Doesn't appear to be necessary in newer versions of Chrome.
77
- await executionContext.evaluateAsync('getComputedStyle(document.body)');
77
+ /* global window, document */
78
+ await executionContext.evaluate(() => window.getComputedStyle(document.body), {
79
+ args: [],
80
+ });
78
81
 
79
82
  session.off('CSS.styleSheetAdded', this._onStylesheetAdded);
80
83
 
@@ -23,10 +23,9 @@ declare class TraceElements extends BaseGatherer {
23
23
  *
24
24
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
25
25
  * @param {Map<number, number>} impactByNodeId
26
- * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
27
26
  * @return {number|undefined}
28
27
  */
29
- static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>, event: import("../../lib/trace-engine.js").SaneSyntheticLayoutShift): number | undefined;
28
+ static getBiggestImpactNodeForShiftEvent(impactedNodes: LH.Artifacts.TraceImpactedNode[], impactByNodeId: Map<number, number>): number | undefined;
30
29
  /**
31
30
  * This function finds the top (up to 15) layout shifts on the page, and returns
32
31
  * the id of the largest impacted node of each shift, along with any related nodes
@@ -152,49 +152,19 @@ class TraceElements extends BaseGatherer {
152
152
  *
153
153
  * @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
154
154
  * @param {Map<number, number>} impactByNodeId
155
- * @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
156
155
  * @return {number|undefined}
157
156
  */
158
- static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event) {
159
- try {
160
- let biggestImpactNodeId;
161
- let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
162
- for (const node of impactedNodes) {
163
- const impactScore = impactByNodeId.get(node.node_id);
164
- if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
165
- biggestImpactNodeId = node.node_id;
166
- biggestImpactNodeScore = impactScore;
167
- }
157
+ static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId) {
158
+ let biggestImpactNodeId;
159
+ let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
160
+ for (const node of impactedNodes) {
161
+ const impactScore = impactByNodeId.get(node.node_id);
162
+ if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
163
+ biggestImpactNodeId = node.node_id;
164
+ biggestImpactNodeScore = impactScore;
168
165
  }
169
- return biggestImpactNodeId;
170
- } catch (err) {
171
- // See https://github.com/GoogleChrome/lighthouse/issues/15870
172
- // `impactedNodes` should always be an array here, but it can randomly be something else for
173
- // currently unknown reasons. This exception handling will help us identify what
174
- // `impactedNodes` really is and also prevent the error from being fatal.
175
-
176
- // It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
177
- // fields just in case.
178
- const impactedNodesType = typeof impactedNodes;
179
- const impactedNodesClassName = impactedNodes?.constructor?.name;
180
-
181
- let impactedNodesJson;
182
- let eventJson;
183
- try {
184
- impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
185
- eventJson = JSON.parse(JSON.stringify(event));
186
- } catch {}
187
-
188
- Sentry.captureException(err, {
189
- extra: {
190
- impactedNodes: impactedNodesJson,
191
- event: eventJson,
192
- impactedNodesType,
193
- impactedNodesClassName,
194
- },
195
- });
196
- return;
197
166
  }
167
+ return biggestImpactNodeId;
198
168
  }
199
169
 
200
170
  /**
@@ -222,7 +192,7 @@ class TraceElements extends BaseGatherer {
222
192
  const nodeIds = [];
223
193
  const impactedNodes = event.args.data.impacted_nodes || [];
224
194
  const biggestImpactedNodeId =
225
- this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event);
195
+ this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
226
196
  if (biggestImpactedNodeId !== undefined) {
227
197
  nodeIds.push(biggestImpactedNodeId);
228
198
  }
@@ -102,5 +102,5 @@ export function normalizeTimingEntries(timings: LH.Result.MeasureEntry[]): void;
102
102
  /**
103
103
  * @param {LH.Result} lhr
104
104
  */
105
- export function elideAuditErrorStacks(lhr: LH.Result): void;
105
+ export function elideLhrErrorStacks(lhr: LH.Result): void;
106
106
  //# sourceMappingURL=asset-saver.d.ts.map
@@ -515,19 +515,31 @@ function normalizeTimingEntries(timings) {
515
515
  }
516
516
 
517
517
  /**
518
- * @param {LH.Result} lhr
518
+ * @param {string} errorStack
519
+ * @return {string}
519
520
  */
520
- function elideAuditErrorStacks(lhr) {
521
+ function elideErrorStack(errorStack) {
521
522
  const baseCallFrameUrl = url.pathToFileURL(LH_ROOT);
523
+ return errorStack
524
+ // Make paths relative to the repo root.
525
+ .replaceAll(baseCallFrameUrl.pathname, '')
526
+ // Remove line/col info.
527
+ .replaceAll(/:\d+:\d+/g, '');
528
+ }
529
+
530
+ /**
531
+ * @param {LH.Result} lhr
532
+ */
533
+ function elideLhrErrorStacks(lhr) {
522
534
  for (const auditResult of Object.values(lhr.audits)) {
523
535
  if (auditResult.errorStack) {
524
- auditResult.errorStack = auditResult.errorStack
525
- // Make paths relative to the repo root.
526
- .replaceAll(baseCallFrameUrl.pathname, '')
527
- // Remove line/col info.
528
- .replaceAll(/:\d+:\d+/g, '');
536
+ auditResult.errorStack = elideErrorStack(auditResult.errorStack);
529
537
  }
530
538
  }
539
+
540
+ if (lhr.runtimeError?.errorStack) {
541
+ lhr.runtimeError.errorStack = elideErrorStack(lhr.runtimeError.errorStack);
542
+ }
531
543
  }
532
544
 
533
545
  export {
@@ -543,5 +555,5 @@ export {
543
555
  saveLanternNetworkData,
544
556
  stringifyReplacer,
545
557
  normalizeTimingEntries,
546
- elideAuditErrorStacks,
558
+ elideLhrErrorStacks,
547
559
  };
package/core/runner.js CHANGED
@@ -312,10 +312,8 @@ class Runner {
312
312
  if (!isEqual(normalizedGatherSettings[k], normalizedAuditSettings[k])) {
313
313
  throw new Error(
314
314
  `Cannot change settings between gathering and auditing…
315
- Difference found at: \`${k}\`
316
- ${normalizedGatherSettings[k]}
317
- vs
318
- ${normalizedAuditSettings[k]}`);
315
+ Difference found at: \`${k}\`: ${JSON.stringify(normalizedGatherSettings[k], null, 2)}
316
+ vs: ${JSON.stringify(normalizedAuditSettings[k], null, 2)}`);
319
317
  }
320
318
  }
321
319
 
@@ -451,21 +449,27 @@ vs
451
449
  * @return {LH.RawIcu<LH.Result['runtimeError']>|undefined}
452
450
  */
453
451
  static getArtifactRuntimeError(artifacts) {
452
+ /** @type {Array<[string, LighthouseError|object]>} */
454
453
  const possibleErrorArtifacts = [
455
- artifacts.PageLoadError, // Preferentially use `PageLoadError`, if it exists.
456
- ...Object.values(artifacts), // Otherwise check amongst all artifacts.
454
+ ['PageLoadError', artifacts.PageLoadError], // Preferentially use `PageLoadError`, if it exists.
455
+ ...Object.entries(artifacts), // Otherwise check amongst all artifacts.
457
456
  ];
458
457
 
459
- for (const possibleErrorArtifact of possibleErrorArtifacts) {
458
+ for (const [artifactKey, possibleErrorArtifact] of possibleErrorArtifacts) {
460
459
  const isError = possibleErrorArtifact instanceof LighthouseError;
461
460
 
462
461
  // eslint-disable-next-line max-len
463
462
  if (isError && possibleErrorArtifact.lhrRuntimeError) {
464
463
  const errorMessage = possibleErrorArtifact.friendlyMessage || possibleErrorArtifact.message;
464
+ // Prefer the stack trace closest to the error.
465
+ const stack =
466
+ /** @type {any} */ (possibleErrorArtifact.cause)?.stack ?? possibleErrorArtifact.stack;
465
467
 
466
468
  return {
467
469
  code: possibleErrorArtifact.code,
468
470
  message: errorMessage,
471
+ errorStack: stack,
472
+ artifactKey,
469
473
  };
470
474
  }
471
475
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "12.6.1-dev.20250602",
4
+ "version": "12.6.1-dev.20250604",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -41,7 +41,14 @@ interface Result {
41
41
  /** List of top-level warnings for this Lighthouse run. */
42
42
  runWarnings: string[];
43
43
  /** A top-level error message that, if present, indicates a serious enough problem that this Lighthouse result may need to be discarded. */
44
- runtimeError?: {code: string, message: string};
44
+ runtimeError?: {
45
+ code: string;
46
+ message: string;
47
+ /** Error stack from any fatal error. */
48
+ errorStack?: string;
49
+ /** Artifact the threw the fatal error. */
50
+ artifactKey?: string;
51
+ };
45
52
  /** The User-Agent string of the browser used run Lighthouse for these results. */
46
53
  userAgent: string;
47
54
  /** Information about the environment in which Lighthouse was run. */