lighthouse 10.2.0-dev.20230529 → 10.2.0-dev.20230531
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.
- package/core/audits/audit.d.ts +2 -1
- package/core/audits/audit.js +5 -2
- package/core/audits/network-requests.js +6 -0
- package/core/gather/driver/execution-context.js +13 -5
- package/core/legacy/config/config.d.ts +1 -1
- package/core/legacy/config/config.js +1 -1
- package/core/lib/asset-saver.d.ts +4 -0
- package/core/lib/asset-saver.js +19 -0
- package/core/lib/lh-error.d.ts +9 -4
- package/core/lib/lh-error.js +15 -11
- package/core/runner.js +4 -2
- package/package.json +2 -2
- package/tsconfig-base.json +1 -1
- package/types/audit.d.ts +2 -0
- package/types/lhr/audit-result.d.ts +2 -0
package/core/audits/audit.d.ts
CHANGED
|
@@ -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
|
package/core/audits/audit.js
CHANGED
|
@@ -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,
|
|
@@ -8,6 +8,7 @@ import {Audit} from './audit.js';
|
|
|
8
8
|
import UrlUtils from '../lib/url-utils.js';
|
|
9
9
|
import {NetworkRecords} from '../computed/network-records.js';
|
|
10
10
|
import {MainResource} from '../computed/main-resource.js';
|
|
11
|
+
import {EntityClassification} from '../computed/entity-classification.js';
|
|
11
12
|
|
|
12
13
|
class NetworkRequests extends Audit {
|
|
13
14
|
/**
|
|
@@ -31,6 +32,8 @@ class NetworkRequests extends Audit {
|
|
|
31
32
|
static async audit(artifacts, context) {
|
|
32
33
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
33
34
|
const records = await NetworkRecords.request(devtoolsLog, context);
|
|
35
|
+
const classifiedEntities = await EntityClassification.request(
|
|
36
|
+
{URL: artifacts.URL, devtoolsLog}, context);
|
|
34
37
|
const earliestRendererStartTime = records.reduce(
|
|
35
38
|
(min, record) => Math.min(min, record.rendererStartTime),
|
|
36
39
|
Infinity
|
|
@@ -61,6 +64,8 @@ class NetworkRequests extends Audit {
|
|
|
61
64
|
((record.frameId === mainFrameId) || undefined) :
|
|
62
65
|
undefined;
|
|
63
66
|
|
|
67
|
+
const entity = classifiedEntities.entityByUrl.get(record.url);
|
|
68
|
+
|
|
64
69
|
return {
|
|
65
70
|
url: UrlUtils.elideDataURI(record.url),
|
|
66
71
|
sessionTargetType: record.sessionTargetType,
|
|
@@ -77,6 +82,7 @@ class NetworkRequests extends Audit {
|
|
|
77
82
|
priority: record.priority,
|
|
78
83
|
isLinkPreload,
|
|
79
84
|
experimentalFromMainFrame,
|
|
85
|
+
entity: entity?.name,
|
|
80
86
|
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
81
87
|
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
82
88
|
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
@@ -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
|
-
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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 `
|
|
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 `
|
|
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
|
package/core/lib/asset-saver.js
CHANGED
|
@@ -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
|
};
|
package/core/lib/lh-error.d.ts
CHANGED
|
@@ -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;
|
package/core/lib/lh-error.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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.
|
|
4
|
+
"version": "10.2.0-dev.20230531",
|
|
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.
|
|
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",
|
package/tsconfig-base.json
CHANGED
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;
|