lighthouse 10.2.0-dev.20230530 → 10.2.0-dev.20230601

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.
@@ -149,9 +149,10 @@ export class Audit {
149
149
  /**
150
150
  * @param {typeof Audit} audit
151
151
  * @param {string | LH.IcuMessage} errorMessage
152
+ * @param {string=} errorStack
152
153
  * @return {LH.RawIcu<LH.Audit.Result>}
153
154
  */
154
- static generateErrorAuditResult(audit: typeof Audit, errorMessage: string | LH.IcuMessage): LH.RawIcu<LH.Audit.Result>;
155
+ static generateErrorAuditResult(audit: typeof Audit, errorMessage: string | LH.IcuMessage, errorStack?: string | undefined): LH.RawIcu<LH.Audit.Result>;
155
156
  /**
156
157
  * @param {typeof Audit} audit
157
158
  * @param {LH.Audit.Product} product
@@ -348,12 +348,14 @@ class Audit {
348
348
  /**
349
349
  * @param {typeof Audit} audit
350
350
  * @param {string | LH.IcuMessage} errorMessage
351
+ * @param {string=} errorStack
351
352
  * @return {LH.RawIcu<LH.Audit.Result>}
352
353
  */
353
- static generateErrorAuditResult(audit, errorMessage) {
354
+ static generateErrorAuditResult(audit, errorMessage, errorStack) {
354
355
  return Audit.generateAuditResult(audit, {
355
356
  score: null,
356
357
  errorMessage,
358
+ errorStack,
357
359
  });
358
360
  }
359
361
 
@@ -371,7 +373,7 @@ class Audit {
371
373
  let scoreDisplayMode = audit.meta.scoreDisplayMode || Audit.SCORING_MODES.BINARY;
372
374
 
373
375
  // But override if product contents require it.
374
- if (product.errorMessage) {
376
+ if (product.errorMessage !== undefined) {
375
377
  // Error result.
376
378
  scoreDisplayMode = Audit.SCORING_MODES.ERROR;
377
379
  } else if (product.notApplicable) {
@@ -407,6 +409,7 @@ class Audit {
407
409
  displayValue: product.displayValue,
408
410
  explanation: product.explanation,
409
411
  errorMessage: product.errorMessage,
412
+ errorStack: product.errorStack,
410
413
  warnings: product.warnings,
411
414
 
412
415
  details: product.details,
@@ -125,14 +125,22 @@ class ExecutionContext {
125
125
 
126
126
  this._session.setNextProtocolTimeout(timeout);
127
127
  const response = await this._session.sendCommand('Runtime.evaluate', evaluationParams);
128
- if (response.exceptionDetails) {
128
+
129
+ const ex = response.exceptionDetails;
130
+ if (ex) {
129
131
  // An error occurred before we could even create a Promise, should be *very* rare.
130
132
  // Also occurs when the expression is not valid JavaScript.
131
- const errorMessage = response.exceptionDetails.exception ?
132
- response.exceptionDetails.exception.description :
133
- response.exceptionDetails.text;
134
- return Promise.reject(new Error(`Evaluation exception: ${errorMessage}`));
133
+ const elidedExpression = expression.replace(/\s+/g, ' ').substring(0, 100);
134
+ const messageLines = [
135
+ 'Runtime.evaluate exception',
136
+ `Expression: ${elidedExpression}\n---- (elided)`,
137
+ !ex.stackTrace ? `Parse error at: ${ex.lineNumber + 1}:${ex.columnNumber + 1}` : null,
138
+ ex.exception?.description || ex.text,
139
+ ].filter(Boolean);
140
+ const evaluationError = new Error(messageLines.join('\n'));
141
+ return Promise.reject(evaluationError);
135
142
  }
143
+
136
144
  // Protocol should always return a 'result' object, but it is sometimes undefined. See #6026.
137
145
  if (response.result === undefined) {
138
146
  return Promise.reject(
@@ -77,7 +77,7 @@ export class LegacyResolvedConfig implements LH.Config.LegacyResolvedConfig {
77
77
  */
78
78
  static requireGatherers(passes: Array<Required<LH.Config.PassJson>> | null, configDir?: string | undefined): Promise<LegacyResolvedConfig['passes']>;
79
79
  /**
80
- * @deprecated `Config.fromJson` should be used instead.
80
+ * @deprecated `LegacyResolvedConfig.fromJson` should be used instead.
81
81
  * @constructor
82
82
  * @param {LH.Config} config
83
83
  * @param {{settings: LH.Config.Settings, passes: ?LH.Config.Pass[], audits: ?LH.Config.AuditDefn[]}} opts
@@ -205,7 +205,7 @@ class LegacyResolvedConfig {
205
205
  }
206
206
 
207
207
  /**
208
- * @deprecated `Config.fromJson` should be used instead.
208
+ * @deprecated `LegacyResolvedConfig.fromJson` should be used instead.
209
209
  * @constructor
210
210
  * @param {LH.Config} config
211
211
  * @param {{settings: LH.Config.Settings, passes: ?LH.Config.Pass[], audits: ?LH.Config.AuditDefn[]}} opts
@@ -98,4 +98,8 @@ export function stringifyReplacer(key: string, value: any): any;
98
98
  * @param {LH.Result.MeasureEntry[]} timings
99
99
  */
100
100
  export function normalizeTimingEntries(timings: LH.Result.MeasureEntry[]): void;
101
+ /**
102
+ * @param {LH.Result} lhr
103
+ */
104
+ export function elideAuditErrorStacks(lhr: LH.Result): void;
101
105
  //# sourceMappingURL=asset-saver.d.ts.map
@@ -7,6 +7,7 @@
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
  import stream from 'stream';
10
+ import url from 'url';
10
11
 
11
12
  import log from 'lighthouse-logger';
12
13
 
@@ -16,6 +17,7 @@ import {MetricTraceEvents} from './traces/metric-trace-events.js';
16
17
  import {NetworkAnalysis} from '../computed/network-analysis.js';
17
18
  import {LoadSimulator} from '../computed/load-simulator.js';
18
19
  import {LighthouseError} from '../lib/lh-error.js';
20
+ import {LH_ROOT} from '../../root.js';
19
21
 
20
22
  const optionsFilename = 'options.json';
21
23
  const artifactsFilename = 'artifacts.json';
@@ -425,6 +427,22 @@ function normalizeTimingEntries(timings) {
425
427
  }
426
428
  }
427
429
 
430
+ /**
431
+ * @param {LH.Result} lhr
432
+ */
433
+ function elideAuditErrorStacks(lhr) {
434
+ const baseCallFrameUrl = url.pathToFileURL(LH_ROOT);
435
+ for (const auditResult of Object.values(lhr.audits)) {
436
+ if (auditResult.errorStack) {
437
+ auditResult.errorStack = auditResult.errorStack
438
+ // Make paths relative to the repo root.
439
+ .replaceAll(baseCallFrameUrl.pathname, '')
440
+ // Remove line/col info.
441
+ .replaceAll(/:\d+:\d+/g, '');
442
+ }
443
+ }
444
+ }
445
+
428
446
  export {
429
447
  saveArtifacts,
430
448
  saveFlowArtifacts,
@@ -438,4 +456,5 @@ export {
438
456
  saveLanternNetworkData,
439
457
  stringifyReplacer,
440
458
  normalizeTimingEntries,
459
+ elideAuditErrorStacks,
441
460
  };
@@ -8,20 +8,24 @@ export type LighthouseErrorDefinition = {
8
8
  lhrRuntimeError?: boolean | undefined;
9
9
  };
10
10
  export type SerializedLighthouseError = {
11
- [p: string]: string | undefined;
12
11
  sentinel: '__LighthouseErrorSentinel';
13
12
  code: string;
14
13
  stack?: string | undefined;
14
+ cause?: unknown;
15
+ properties?: {
16
+ [p: string]: string | undefined;
17
+ } | undefined;
15
18
  };
16
19
  export type SerializedBaseError = {
17
20
  sentinel: '__ErrorSentinel';
18
21
  message: string;
19
22
  code?: string;
20
23
  stack?: string;
24
+ cause?: unknown;
21
25
  };
22
26
  /**
23
- * @typedef {{sentinel: '__LighthouseErrorSentinel', code: string, stack?: string, [p: string]: string|undefined}} SerializedLighthouseError
24
- * @typedef {{sentinel: '__ErrorSentinel', message: string, code?: string, stack?: string}} SerializedBaseError
27
+ * @typedef {{sentinel: '__LighthouseErrorSentinel', code: string, stack?: string, cause?: unknown, properties?: {[p: string]: string|undefined}}} SerializedLighthouseError
28
+ * @typedef {{sentinel: '__ErrorSentinel', message: string, code?: string, stack?: string, cause?: unknown}} SerializedBaseError
25
29
  */
26
30
  export class LighthouseError extends Error {
27
31
  /**
@@ -55,8 +59,9 @@ export class LighthouseError extends Error {
55
59
  /**
56
60
  * @param {LighthouseErrorDefinition} errorDefinition
57
61
  * @param {Record<string, string|undefined>=} properties
62
+ * @param {ErrorOptions=} options
58
63
  */
59
- constructor(errorDefinition: LighthouseErrorDefinition, properties?: Record<string, string | undefined> | undefined);
64
+ constructor(errorDefinition: LighthouseErrorDefinition, properties?: Record<string, string | undefined> | undefined, options?: ErrorOptions | undefined);
60
65
  code: string;
61
66
  friendlyMessage: import("../index.js").IcuMessage;
62
67
  lhrRuntimeError: boolean;
@@ -107,17 +107,18 @@ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
107
107
  const LHERROR_SENTINEL = '__LighthouseErrorSentinel';
108
108
  const ERROR_SENTINEL = '__ErrorSentinel';
109
109
  /**
110
- * @typedef {{sentinel: '__LighthouseErrorSentinel', code: string, stack?: string, [p: string]: string|undefined}} SerializedLighthouseError
111
- * @typedef {{sentinel: '__ErrorSentinel', message: string, code?: string, stack?: string}} SerializedBaseError
110
+ * @typedef {{sentinel: '__LighthouseErrorSentinel', code: string, stack?: string, cause?: unknown, properties?: {[p: string]: string|undefined}}} SerializedLighthouseError
111
+ * @typedef {{sentinel: '__ErrorSentinel', message: string, code?: string, stack?: string, cause?: unknown}} SerializedBaseError
112
112
  */
113
113
 
114
114
  class LighthouseError extends Error {
115
115
  /**
116
116
  * @param {LighthouseErrorDefinition} errorDefinition
117
117
  * @param {Record<string, string|undefined>=} properties
118
+ * @param {ErrorOptions=} options
118
119
  */
119
- constructor(errorDefinition, properties) {
120
- super(errorDefinition.code);
120
+ constructor(errorDefinition, properties, options) {
121
+ super(errorDefinition.code, options);
121
122
  this.name = 'LighthouseError';
122
123
  this.code = errorDefinition.code;
123
124
  // Add additional properties to be ICU replacements in the error string.
@@ -163,19 +164,20 @@ class LighthouseError extends Error {
163
164
  if (err instanceof LighthouseError) {
164
165
  // Remove class props so that remaining values were what was passed in as `properties`.
165
166
  // eslint-disable-next-line no-unused-vars
166
- const {name, code, message, friendlyMessage, lhrRuntimeError, stack, ...properties} = err;
167
+ const {name, code, message, friendlyMessage, lhrRuntimeError, stack, cause, ...properties} = err;
167
168
 
168
169
  return {
169
170
  sentinel: LHERROR_SENTINEL,
170
171
  code,
171
172
  stack,
172
- ...properties,
173
+ cause,
174
+ properties: /** @type {{ [p: string]: string | undefined }} */ (properties),
173
175
  };
174
176
  }
175
177
 
176
178
  // Unexpected errors won't be LighthouseErrors, but we want them serialized as well.
177
179
  if (err instanceof Error) {
178
- const {message, stack} = err;
180
+ const {message, stack, cause} = err;
179
181
  // @ts-expect-error - code can be helpful for e.g. node errors, so preserve it if it's present.
180
182
  const code = err.code;
181
183
  return {
@@ -183,6 +185,7 @@ class LighthouseError extends Error {
183
185
  message,
184
186
  code,
185
187
  stack,
188
+ cause,
186
189
  };
187
190
  }
188
191
 
@@ -203,17 +206,18 @@ class LighthouseError extends Error {
203
206
  if (possibleError.sentinel === LHERROR_SENTINEL) {
204
207
  // Include sentinel in destructuring so it doesn't end up in `properties`.
205
208
  // eslint-disable-next-line no-unused-vars
206
- const {sentinel, code, stack, ...properties} = /** @type {SerializedLighthouseError} */ (possibleError);
209
+ const {code, stack, cause, properties} = /** @type {SerializedLighthouseError} */ (possibleError);
207
210
  const errorDefinition = LighthouseError.errors[/** @type {keyof typeof ERRORS} */ (code)];
208
- const lhError = new LighthouseError(errorDefinition, properties);
211
+ const lhError = new LighthouseError(errorDefinition, properties, {cause});
209
212
  lhError.stack = stack;
210
213
 
211
214
  return lhError;
212
215
  }
213
216
 
214
217
  if (possibleError.sentinel === ERROR_SENTINEL) {
215
- const {message, code, stack} = /** @type {SerializedBaseError} */ (possibleError);
216
- const error = new Error(message);
218
+ const {message, code, stack, cause} = /** @type {SerializedBaseError} */ (possibleError);
219
+ const opts = cause ? {cause} : undefined;
220
+ const error = new Error(message, opts);
217
221
  Object.assign(error, {code, stack});
218
222
  return error;
219
223
  }
package/core/runner.js CHANGED
@@ -429,7 +429,7 @@ class Runner {
429
429
 
430
430
  // Create a friendlier display error and mark it as expected to avoid duplicates in Sentry
431
431
  const error = new LighthouseError(LighthouseError.errors.ERRORED_REQUIRED_ARTIFACT,
432
- {artifactName, errorMessage: artifactError.message});
432
+ {artifactName, errorMessage: artifactError.message}, {cause: artifactError});
433
433
  // @ts-expect-error Non-standard property added to Error
434
434
  error.expected = true;
435
435
  throw error;
@@ -468,7 +468,9 @@ class Runner {
468
468
  Sentry.captureException(err, {tags: {audit: audit.meta.id}, level: 'error'});
469
469
  // Errors become error audit result.
470
470
  const errorMessage = err.friendlyMessage ? err.friendlyMessage : err.message;
471
- auditResult = Audit.generateErrorAuditResult(audit, errorMessage);
471
+ // Prefer the stack trace closest to the error.
472
+ const stack = err.cause?.stack ?? err.stack;
473
+ auditResult = Audit.generateErrorAuditResult(audit, errorMessage, stack);
472
474
  }
473
475
 
474
476
  log.timeEnd(status);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "10.2.0-dev.20230530",
4
+ "version": "10.2.0-dev.20230601",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -179,7 +179,7 @@
179
179
  "rollup-plugin-terser": "^7.0.2",
180
180
  "tabulator-tables": "^4.9.3",
181
181
  "terser": "^5.3.8",
182
- "testdouble": "^3.16.8",
182
+ "testdouble": "^3.17.2",
183
183
  "typed-query-selector": "^2.6.1",
184
184
  "typescript": "^5.0.4",
185
185
  "wait-for-expect": "^3.0.2",
@@ -11,7 +11,7 @@
11
11
  "outDir": ".tmp/tsbuildinfo/",
12
12
  "rootDir": ".",
13
13
 
14
- "target": "es2020",
14
+ "target": "es2022",
15
15
  "module": "es2022",
16
16
  "moduleResolution": "node",
17
17
  "esModuleInterop": true,
package/types/audit.d.ts CHANGED
@@ -75,6 +75,8 @@ declare module Audit {
75
75
  explanation?: string | IcuMessage;
76
76
  /** Error message from any exception thrown while running this audit. */
77
77
  errorMessage?: string | IcuMessage;
78
+ /** Error stack from any exception thrown while running this audit. */
79
+ errorStack?: string;
78
80
  warnings?: Array<string | IcuMessage>;
79
81
  /** Overrides scoreDisplayMode with notApplicable if set to true */
80
82
  notApplicable?: boolean;
@@ -39,6 +39,8 @@ export interface Result {
39
39
  explanation?: string;
40
40
  /** Error message from any exception thrown while running this audit. */
41
41
  errorMessage?: string;
42
+ /** Error stack from any exception thrown while running this audit. */
43
+ errorStack?: string;
42
44
  warnings?: string[];
43
45
  /** The scored value of the audit, provided in the range `0-1`, or null if `scoreDisplayMode` indicates not scored. */
44
46
  score: number|null;