lighthouse 13.5.0-dev.20261002 → 13.5.0-dev.20261006
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/cli/test/smokehouse/config/exclusions.js +2 -0
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse.js +29 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.js +1 -1
- package/core/audits/baseline.d.ts +14 -0
- package/core/audits/baseline.js +15 -3
- package/core/audits/predictive-perf.js +2 -2
- package/core/computed/metrics/lcp-breakdown.js +4 -4
- package/core/lib/baseline/web-features-data.json +2 -2
- package/core/lib/page-functions.js +2 -5
- package/package.json +3 -3
|
@@ -78,6 +78,8 @@ const exclusions = {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
for (const array of Object.values(exclusions)) {
|
|
81
|
+
// https://github.com/GoogleChrome/lighthouse/issues/17277
|
|
82
|
+
array.push('webmcp');
|
|
81
83
|
// https://github.com/GoogleChrome/lighthouse/issues/14271
|
|
82
84
|
array.push('lantern-idle-callback-short');
|
|
83
85
|
// glitch is gone.
|
|
@@ -216,7 +216,9 @@ async function begin() {
|
|
|
216
216
|
if (testDefnPath === coreTestDefnsPath) {
|
|
217
217
|
const {createServers} = await import('../../fixtures/static-server.js');
|
|
218
218
|
servers = await createServers();
|
|
219
|
-
|
|
219
|
+
if (servers.length) {
|
|
220
|
+
takeNetworkRequestUrls = servers[0].takeRequestUrls.bind(servers[0]);
|
|
221
|
+
}
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
const prunedTestDefns = pruneExpectedNetworkRequests(testDefns, takeNetworkRequestUrls);
|
|
@@ -234,7 +236,7 @@ async function begin() {
|
|
|
234
236
|
|
|
235
237
|
smokehouseResult = (await runSmokehouse(prunedTestDefns, options));
|
|
236
238
|
} finally {
|
|
237
|
-
servers
|
|
239
|
+
if (servers) await Promise.all(servers.map(s => s.close()));
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
let smokehouseOutputDir;
|
|
@@ -244,7 +246,7 @@ async function begin() {
|
|
|
244
246
|
smokehouseOutputDir = `${LH_ROOT}/.tmp/smokehouse-failures`;
|
|
245
247
|
testResultsToOutput = smokehouseResult.testResults.filter(r => r.failed);
|
|
246
248
|
} else if (!process.env.CI) {
|
|
247
|
-
// Otherwise,
|
|
249
|
+
// Otherwise, write all results to disk when running locally.
|
|
248
250
|
smokehouseOutputDir = `${LH_ROOT}/.tmp/smokehouse-output`;
|
|
249
251
|
testResultsToOutput = smokehouseResult.testResults;
|
|
250
252
|
}
|
|
@@ -73,6 +73,31 @@ describe('findDiffersences', () => {
|
|
|
73
73
|
expected: {duration: '<100'},
|
|
74
74
|
diffs: [{path: '.duration', actual: 100, expected: '<100'}],
|
|
75
75
|
},
|
|
76
|
+
'range (negative 1)': {
|
|
77
|
+
actual: {duration: -3},
|
|
78
|
+
expected: {duration: '>-5'},
|
|
79
|
+
diffs: null,
|
|
80
|
+
},
|
|
81
|
+
'range (negative 2)': {
|
|
82
|
+
actual: {duration: -9},
|
|
83
|
+
expected: {duration: '-10 +/- 2'},
|
|
84
|
+
diffs: null,
|
|
85
|
+
},
|
|
86
|
+
'range (invalid numerical expectation without prefix)': {
|
|
87
|
+
actual: {duration: 3},
|
|
88
|
+
expected: {duration: '+/- 5'},
|
|
89
|
+
diffs: [{path: '.duration', actual: 3, expected: '+/- 5'}],
|
|
90
|
+
},
|
|
91
|
+
'range (invalid numerical expectation with prefix)': {
|
|
92
|
+
actual: {duration: 15},
|
|
93
|
+
expected: {duration: '10 < 20'},
|
|
94
|
+
diffs: [{path: '.duration', actual: 15, expected: '10 < 20'}],
|
|
95
|
+
},
|
|
96
|
+
'range (numeric actual vs array expected)': {
|
|
97
|
+
actual: {duration: 10},
|
|
98
|
+
expected: {duration: ['>0']},
|
|
99
|
+
diffs: [{path: '.duration', actual: 10, expected: ['>0']}],
|
|
100
|
+
},
|
|
76
101
|
|
|
77
102
|
'array (1)': {
|
|
78
103
|
actual: {prices: [0, 1, 2, 3, 4, 5]},
|
|
@@ -112,6 +137,11 @@ describe('findDiffersences', () => {
|
|
|
112
137
|
expected: {prices: {'3': {nested: '>3'}}},
|
|
113
138
|
diffs: [{path: '.prices[3].nested', actual: 3, expected: '>3'}],
|
|
114
139
|
},
|
|
140
|
+
'array (8)': {
|
|
141
|
+
actual: {prices: {0: 1, length: 1}},
|
|
142
|
+
expected: {prices: [1]},
|
|
143
|
+
diffs: [{path: '.prices.length', actual: {0: 1, length: 1}, expected: [1]}],
|
|
144
|
+
},
|
|
115
145
|
|
|
116
146
|
'_includes (1)': {
|
|
117
147
|
actual: {prices: [0, 1, 2, 3, 4, 5]},
|
|
@@ -295,4 +325,39 @@ describe('getAssertionReport', () => {
|
|
|
295
325
|
expect(report).toMatchObject({passed: 3, failed: 1});
|
|
296
326
|
expect(clean(report.log)).toMatchSnapshot();
|
|
297
327
|
});
|
|
328
|
+
|
|
329
|
+
it('asserts expected.lhr.userAgent', () => {
|
|
330
|
+
const passingReport = getAssertionReport({lhr, artifacts}, {
|
|
331
|
+
lhr: {
|
|
332
|
+
audits: {},
|
|
333
|
+
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
334
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
335
|
+
userAgent: /Chrome\//,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
expect(passingReport).toMatchObject({passed: 4, failed: 0, log: ''});
|
|
339
|
+
|
|
340
|
+
const failingReport = getAssertionReport({lhr, artifacts}, {
|
|
341
|
+
lhr: {
|
|
342
|
+
audits: {},
|
|
343
|
+
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
344
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
345
|
+
userAgent: 'NotTheUserAgent',
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
expect(failingReport).toMatchObject({passed: 3, failed: 1});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('lazily evaluates hostUserAgent only when chromium version check is used', () => {
|
|
352
|
+
const customLhr = structuredClone(lhr);
|
|
353
|
+
customLhr.environment.hostUserAgent = 'InvalidUserAgentWithoutChromeVersion';
|
|
354
|
+
const report = getAssertionReport({lhr: customLhr, artifacts}, {
|
|
355
|
+
lhr: {
|
|
356
|
+
audits: {},
|
|
357
|
+
requestedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
358
|
+
finalDisplayedUrl: 'http://localhost:10200/dobetterweb/dbw_tester.html',
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
expect(report).toMatchObject({passed: 3, failed: 0, log: ''});
|
|
362
|
+
});
|
|
298
363
|
});
|
|
@@ -30,7 +30,7 @@ import {chromiumVersionCheck} from './version-check.js';
|
|
|
30
30
|
* @property {Difference[]|null} diffs
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
const NUMBER_REGEXP =
|
|
33
|
+
const NUMBER_REGEXP = /-?(?:\d|\.)+/.source;
|
|
34
34
|
const OPS_REGEXP = /<=?|>=?|\+\/-|±/.source;
|
|
35
35
|
// An optional number, optional whitespace, an operator, optional whitespace, a number.
|
|
36
36
|
const NUMERICAL_EXPECTATION_REGEXP =
|
|
@@ -48,21 +48,27 @@ const NUMERICAL_EXPECTATION_REGEXP =
|
|
|
48
48
|
* @return {boolean}
|
|
49
49
|
*/
|
|
50
50
|
function matchesExpectation(actual, expected) {
|
|
51
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
typeof actual === 'number' &&
|
|
53
|
+
typeof expected === 'string' &&
|
|
54
|
+
NUMERICAL_EXPECTATION_REGEXP.test(expected)
|
|
55
|
+
) {
|
|
52
56
|
const parts = expected.match(NUMERICAL_EXPECTATION_REGEXP);
|
|
57
|
+
if (!parts) return false;
|
|
53
58
|
const [, prefixNumber, operator, postfixNumber] = parts;
|
|
54
59
|
switch (operator) {
|
|
55
60
|
case '>':
|
|
56
|
-
return actual > postfixNumber;
|
|
61
|
+
return prefixNumber === undefined && actual > Number(postfixNumber);
|
|
57
62
|
case '>=':
|
|
58
|
-
return actual >= postfixNumber;
|
|
63
|
+
return prefixNumber === undefined && actual >= Number(postfixNumber);
|
|
59
64
|
case '<':
|
|
60
|
-
return actual < postfixNumber;
|
|
65
|
+
return prefixNumber === undefined && actual < Number(postfixNumber);
|
|
61
66
|
case '<=':
|
|
62
|
-
return actual <= postfixNumber;
|
|
67
|
+
return prefixNumber === undefined && actual <= Number(postfixNumber);
|
|
63
68
|
case '+/-':
|
|
64
69
|
case '±':
|
|
65
|
-
return
|
|
70
|
+
return prefixNumber !== undefined &&
|
|
71
|
+
Math.abs(actual - Number(prefixNumber)) <= Number(postfixNumber);
|
|
66
72
|
default:
|
|
67
73
|
throw new Error(`unexpected operator ${operator}`);
|
|
68
74
|
}
|
|
@@ -117,21 +123,8 @@ function findDifferences(path, actual, expected) {
|
|
|
117
123
|
const expectedValue = expected[key];
|
|
118
124
|
|
|
119
125
|
if (key === '_includes') {
|
|
120
|
-
if (Array.isArray(actual)) {
|
|
121
|
-
inclExclCopy = [...actual];
|
|
122
|
-
} else if (typeof actual === 'object') {
|
|
123
|
-
inclExclCopy = Object.entries(actual);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
126
|
if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
|
|
127
|
-
|
|
128
|
-
diffs.push({
|
|
129
|
-
path,
|
|
130
|
-
actual: 'Actual value is not an array or object',
|
|
131
|
-
expected,
|
|
132
|
-
});
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
127
|
+
inclExclCopy = Array.isArray(actual) ? [...actual] : Object.entries(actual);
|
|
135
128
|
|
|
136
129
|
for (const expectedEntry of expectedValue) {
|
|
137
130
|
const matchingIndex =
|
|
@@ -153,24 +146,11 @@ function findDifferences(path, actual, expected) {
|
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
if (key === '_excludes') {
|
|
156
|
-
// Re-use state from `_includes` check, if there was one.
|
|
157
|
-
if (!inclExclCopy) {
|
|
158
|
-
if (Array.isArray(actual)) {
|
|
159
|
-
// We won't be removing items, so we can just copy the reference.
|
|
160
|
-
inclExclCopy = actual;
|
|
161
|
-
} else if (typeof actual === 'object') {
|
|
162
|
-
inclExclCopy = Object.entries(actual);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
149
|
if (!Array.isArray(expectedValue)) throw new Error('Array subset must be array');
|
|
150
|
+
// Re-use state from `_includes` check, if there was one.
|
|
167
151
|
if (!inclExclCopy) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
actual: 'Actual value is not an array or object',
|
|
171
|
-
expected,
|
|
172
|
-
});
|
|
173
|
-
continue;
|
|
152
|
+
// We won't be removing items, so we can just copy the reference.
|
|
153
|
+
inclExclCopy = Array.isArray(actual) ? actual : Object.entries(actual);
|
|
174
154
|
}
|
|
175
155
|
|
|
176
156
|
const expectedExclusions = expectedValue;
|
|
@@ -201,7 +181,7 @@ function findDifferences(path, actual, expected) {
|
|
|
201
181
|
// This still allows for asserting that the first n elements of an array are specified elements,
|
|
202
182
|
// but requires using an object literal (ex: {0: x, 1: y, 2: z} matches [x, y, z, q, w, e] and
|
|
203
183
|
// {0: x, 1: y, 2: z, length: 5} does not match [x, y, z].
|
|
204
|
-
if (Array.isArray(expected) && actual.length !== expected.length) {
|
|
184
|
+
if (Array.isArray(expected) && (!Array.isArray(actual) || actual.length !== expected.length)) {
|
|
205
185
|
diffs.push({
|
|
206
186
|
path: `${path}.length`,
|
|
207
187
|
actual,
|
|
@@ -256,6 +236,7 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
256
236
|
* @param {*} obj
|
|
257
237
|
*/
|
|
258
238
|
function failsChromeVersionCheck(obj) {
|
|
239
|
+
if (!obj._minChromiumVersion && !obj._maxChromiumVersion) return false;
|
|
259
240
|
return !chromiumVersionCheck({
|
|
260
241
|
version: getChromeVersionString(),
|
|
261
242
|
min: obj._minChromiumVersion,
|
|
@@ -376,6 +357,10 @@ function collateResults(localConsole, actual, expected) {
|
|
|
376
357
|
/** @type {Comparison[]} */
|
|
377
358
|
const extraAssertions = [];
|
|
378
359
|
|
|
360
|
+
if (expected.lhr.userAgent) {
|
|
361
|
+
extraAssertions.push(makeComparison('userAgent', actual.lhr.userAgent, expected.lhr.userAgent));
|
|
362
|
+
}
|
|
363
|
+
|
|
379
364
|
if (expected.lhr.timing) {
|
|
380
365
|
const comparison = makeComparison('timing', actual.lhr.timing, expected.lhr.timing);
|
|
381
366
|
extraAssertions.push(comparison);
|
|
@@ -425,7 +410,7 @@ function reportAssertion(localConsole, assertion) {
|
|
|
425
410
|
RegExp.prototype.toJSON = RegExp.prototype.toString;
|
|
426
411
|
|
|
427
412
|
if (assertion.equal) {
|
|
428
|
-
if (isPlainObject(assertion.actual)) {
|
|
413
|
+
if (isPlainObject(assertion.actual) || Array.isArray(assertion.actual)) {
|
|
429
414
|
localConsole.log(` ${log.greenify(log.tick)} ${assertion.name}`);
|
|
430
415
|
} else {
|
|
431
416
|
localConsole.log(` ${log.greenify(log.tick)} ${assertion.name}: ` +
|
|
@@ -160,6 +160,8 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
|
160
160
|
const bufferedConsole = new LocalConsole();
|
|
161
161
|
bufferedConsole.log(`\n${purpleify(id)}: testing '${requestedUrl}'…`);
|
|
162
162
|
for (let i = 0; i <= retries; i++) {
|
|
163
|
+
result = undefined;
|
|
164
|
+
report = undefined;
|
|
163
165
|
if (i !== 0) {
|
|
164
166
|
bufferedConsole.log(` Retrying run (${i} out of ${retries} retries)…`);
|
|
165
167
|
}
|
|
@@ -167,42 +169,54 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
|
167
169
|
const logger = new LocalConsole();
|
|
168
170
|
|
|
169
171
|
// Run Lighthouse.
|
|
172
|
+
let isTimeout = false;
|
|
170
173
|
try {
|
|
171
174
|
// Each individual runner has internal timeouts, but we've had bugs where
|
|
172
175
|
// that didn't cover some edge case. So to be safe give a (long) timeout here.
|
|
176
|
+
let timeoutId;
|
|
173
177
|
const timeoutPromise = new Promise((_, reject) => {
|
|
174
|
-
setTimeout(() =>
|
|
175
|
-
|
|
178
|
+
timeoutId = setTimeout(() => {
|
|
179
|
+
isTimeout = true;
|
|
180
|
+
reject(new Error('Timed out waiting for provided lighthouseRunner'));
|
|
181
|
+
}, 1000 * 120);
|
|
176
182
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
let timedResult;
|
|
184
|
+
try {
|
|
185
|
+
timedResult = await Promise.race([
|
|
186
|
+
lighthouseRunner(requestedUrl, config, logger, mergedTestRunnerOptions),
|
|
187
|
+
timeoutPromise,
|
|
188
|
+
]);
|
|
189
|
+
} finally {
|
|
190
|
+
clearTimeout(timeoutId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!timedResult.lhr?.audits || !timedResult.artifacts) {
|
|
194
|
+
// Something went really wrong and the runner didn't catch it.
|
|
195
|
+
throw new Error('lighthouse runner returned a bad result. got lhr:\n' +
|
|
196
|
+
JSON.stringify(timedResult.lhr, null, 2));
|
|
197
|
+
}
|
|
198
|
+
|
|
181
199
|
result = {
|
|
182
200
|
...timedResult,
|
|
183
201
|
networkRequests: takeNetworkRequestUrls ? takeNetworkRequestUrls() : undefined,
|
|
184
202
|
log: logger.getLog(),
|
|
185
203
|
};
|
|
186
|
-
|
|
187
|
-
if (!result.lhr?.audits || !result.artifacts) {
|
|
188
|
-
// Something went really wrong and the runner didn't catch it.
|
|
189
|
-
throw new Error('lighthouse runner returned a bad result. got lhr:\n' +
|
|
190
|
-
JSON.stringify(result.lhr, null, 2));
|
|
191
|
-
}
|
|
192
204
|
} catch (e) {
|
|
193
205
|
// Clear the network requests so that when we retry, we don't see duplicates.
|
|
194
206
|
if (takeNetworkRequestUrls) takeNetworkRequestUrls();
|
|
195
207
|
|
|
196
208
|
logChildProcessError(bufferedConsole, e);
|
|
197
|
-
|
|
198
|
-
|
|
209
|
+
if (isTimeout) {
|
|
210
|
+
bufferedConsole.log('Timed out. log from lighthouseRunner:');
|
|
211
|
+
bufferedConsole.log(logger.getLog());
|
|
212
|
+
}
|
|
199
213
|
continue; // Retry, if possible.
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
// Assert result.
|
|
203
217
|
report = getAssertionReport(result, expectations, {
|
|
204
218
|
runner: lighthouseRunner.runnerName,
|
|
205
|
-
...
|
|
219
|
+
...mergedTestRunnerOptions,
|
|
206
220
|
});
|
|
207
221
|
|
|
208
222
|
runs.push({
|
|
@@ -19,6 +19,10 @@ describe('version check', () => {
|
|
|
19
19
|
expect(compareVersions([100, 0, 0, 0], [100])).toBe(0);
|
|
20
20
|
expect(compareVersions([100, 0, 0, 1], [100])).toBe(1);
|
|
21
21
|
expect(compareVersions([99, 0, 0, 0], [100])).toBe(-1);
|
|
22
|
+
|
|
23
|
+
expect(compareVersions([100], [100, 0, 0, 0])).toBe(0);
|
|
24
|
+
expect(compareVersions([100], [100, 0, 0, 1])).toBe(-1);
|
|
25
|
+
expect(compareVersions([101], [100, 0, 0, 1])).toBe(1);
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
it('chromiumVersionCheck', async () => {
|
|
@@ -27,6 +31,7 @@ describe('version check', () => {
|
|
|
27
31
|
expect(chromiumVersionCheck({version: '100', max: '100'})).toBe(true);
|
|
28
32
|
expect(chromiumVersionCheck({version: '100', min: '101'})).toBe(false);
|
|
29
33
|
expect(chromiumVersionCheck({version: '100', max: '99'})).toBe(false);
|
|
34
|
+
expect(chromiumVersionCheck({version: '100', min: '100.0.1.0'})).toBe(false);
|
|
30
35
|
|
|
31
36
|
expect(chromiumVersionCheck({version: '100.0.2331.3'})).toBe(true);
|
|
32
37
|
expect(chromiumVersionCheck({version: '100.0.2331.3', min: '100.0.2331.3'})).toBe(true);
|
|
@@ -22,7 +22,7 @@ function parseVersion(versionString) {
|
|
|
22
22
|
* @param {number[]} versionB
|
|
23
23
|
*/
|
|
24
24
|
function compareVersions(versionA, versionB) {
|
|
25
|
-
for (let i = 0; i < versionA.length; i++) {
|
|
25
|
+
for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) {
|
|
26
26
|
if ((versionA[i] ?? 0) > (versionB[i] ?? 0)) return 1;
|
|
27
27
|
if ((versionA[i] ?? 0) < (versionB[i] ?? 0)) return -1;
|
|
28
28
|
}
|
|
@@ -789,6 +789,16 @@ declare class Baseline extends Audit {
|
|
|
789
789
|
};
|
|
790
790
|
/**
|
|
791
791
|
* Determines the baseline status and display string for a given feature ID.
|
|
792
|
+
*
|
|
793
|
+
* Baseline tiers & dates (built by `build/build-baseline-data.js`):
|
|
794
|
+
* - `limited`: `baseline === false` (Limited Availability — not yet in all core browsers, no date).
|
|
795
|
+
* - `low`: `baseline === 'low'` (Newly Available — in all core browsers; value is `baseline_low_date`).
|
|
796
|
+
* - `high`: `baseline === 'high'` (Widely Available — 30 months past `baseline_low_date`; note that
|
|
797
|
+
* `featureData.high[id]` still stores the feature's `baseline_low_date`).
|
|
798
|
+
*
|
|
799
|
+
* If a feature in `featureData.low` has reached `baseline_low_date + 30 months <= currentDate` at runtime,
|
|
800
|
+
* we dynamically promote it from `low` (Newly Available) to `high` (Widely Available).
|
|
801
|
+
*
|
|
792
802
|
* @param {string} featureId
|
|
793
803
|
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
|
|
794
804
|
* @param {Date} currentDate
|
|
@@ -803,6 +813,10 @@ declare class Baseline extends Audit {
|
|
|
803
813
|
baselineTier: "high" | "low" | "limited";
|
|
804
814
|
} | null;
|
|
805
815
|
/**
|
|
816
|
+
* Returns the `baseline_low_date` (YYYY-MM-DD, when the feature became Newly Available)
|
|
817
|
+
* for both `low` and `high` features, since `build/build-baseline-data.js` stores
|
|
818
|
+
* `baseline_low_date` in both `featureData.low` and `featureData.high`.
|
|
819
|
+
*
|
|
806
820
|
* @param {string} featureId
|
|
807
821
|
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
|
|
808
822
|
* @return {string|null}
|
package/core/audits/baseline.js
CHANGED
|
@@ -48,6 +48,16 @@ class Baseline extends Audit {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Determines the baseline status and display string for a given feature ID.
|
|
51
|
+
*
|
|
52
|
+
* Baseline tiers & dates (built by `build/build-baseline-data.js`):
|
|
53
|
+
* - `limited`: `baseline === false` (Limited Availability — not yet in all core browsers, no date).
|
|
54
|
+
* - `low`: `baseline === 'low'` (Newly Available — in all core browsers; value is `baseline_low_date`).
|
|
55
|
+
* - `high`: `baseline === 'high'` (Widely Available — 30 months past `baseline_low_date`; note that
|
|
56
|
+
* `featureData.high[id]` still stores the feature's `baseline_low_date`).
|
|
57
|
+
*
|
|
58
|
+
* If a feature in `featureData.low` has reached `baseline_low_date + 30 months <= currentDate` at runtime,
|
|
59
|
+
* we dynamically promote it from `low` (Newly Available) to `high` (Widely Available).
|
|
60
|
+
*
|
|
51
61
|
* @param {string} featureId
|
|
52
62
|
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
|
|
53
63
|
* @param {Date} currentDate
|
|
@@ -92,6 +102,10 @@ class Baseline extends Audit {
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
/**
|
|
105
|
+
* Returns the `baseline_low_date` (YYYY-MM-DD, when the feature became Newly Available)
|
|
106
|
+
* for both `low` and `high` features, since `build/build-baseline-data.js` stores
|
|
107
|
+
* `baseline_low_date` in both `featureData.low` and `featureData.high`.
|
|
108
|
+
*
|
|
95
109
|
* @param {string} featureId
|
|
96
110
|
* @param {{high: Record<string, string>, low: Record<string, string>, limited: string[]}} featureData
|
|
97
111
|
* @return {string|null}
|
|
@@ -103,9 +117,7 @@ class Baseline extends Audit {
|
|
|
103
117
|
}
|
|
104
118
|
if (featureId in featureData.high) {
|
|
105
119
|
const highData = /** @type {Record<string, string>} */ (featureData.high);
|
|
106
|
-
|
|
107
|
-
date.setUTCMonth(date.getUTCMonth() - 30);
|
|
108
|
-
return date.toISOString().slice(0, 10);
|
|
120
|
+
return highData[featureId];
|
|
109
121
|
}
|
|
110
122
|
return null;
|
|
111
123
|
}
|
|
@@ -76,8 +76,8 @@ class PredictivePerf extends Audit {
|
|
|
76
76
|
pessimisticLCP: lcp.pessimisticEstimate.timeInMs,
|
|
77
77
|
|
|
78
78
|
roughEstimateOfTTFB: timingSummary.metrics.timeToFirstByte,
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
roughEstimateOfLCPLoadDelay: timingSummary.metrics.lcpLoadDelay,
|
|
80
|
+
roughEstimateOfLCPLoadDuration: timingSummary.metrics.lcpLoadDuration,
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const score = Audit.computeLogNormalScore(
|
|
@@ -42,15 +42,15 @@ class LCPBreakdown {
|
|
|
42
42
|
const throttleRatio = metricLcp / observedLcp;
|
|
43
43
|
|
|
44
44
|
const unclampedLoadStart = (lcpRecord.networkRequestTime - timeOrigin) * throttleRatio;
|
|
45
|
-
const
|
|
45
|
+
const loadStart = Math.max(ttfb, Math.min(unclampedLoadStart, metricLcp));
|
|
46
46
|
|
|
47
47
|
const unclampedLoadEnd = (lcpRecord.networkEndTime - timeOrigin) * throttleRatio;
|
|
48
|
-
const
|
|
48
|
+
const loadEnd = Math.max(loadStart, Math.min(unclampedLoadEnd, metricLcp));
|
|
49
49
|
|
|
50
50
|
return {
|
|
51
51
|
ttfb,
|
|
52
|
-
loadDelay,
|
|
53
|
-
loadDuration,
|
|
52
|
+
loadDelay: loadStart - ttfb,
|
|
53
|
+
loadDuration: loadEnd - loadStart,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -401,12 +401,12 @@
|
|
|
401
401
|
"oklab": "2023-05-09",
|
|
402
402
|
"online": "2015-07-29",
|
|
403
403
|
"opacity": "2015-07-29",
|
|
404
|
-
"opacity-svg": "
|
|
404
|
+
"opacity-svg": "2020-03-24",
|
|
405
405
|
"optional-catch-binding": "2020-01-15",
|
|
406
406
|
"origin-private-file-system": "2023-03-27",
|
|
407
407
|
"outline": "2023-03-27",
|
|
408
408
|
"outlines": "2017-04-05",
|
|
409
|
-
"output": "
|
|
409
|
+
"output": "2018-10-02",
|
|
410
410
|
"overflow": "2023-09-18",
|
|
411
411
|
"overflow-clip": "2022-09-12",
|
|
412
412
|
"overflow-shorthand": "2020-03-24",
|
|
@@ -97,13 +97,10 @@ function getOuterHTMLSnippet(element, ignoreAttrs = [], snippetCharacterLimit =
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
try {
|
|
100
|
-
|
|
101
|
-
// @ts-expect-error - clone will be same type as element - see https://github.com/microsoft/TypeScript/issues/283
|
|
102
|
-
const clone = element.cloneNode();
|
|
103
|
-
|
|
104
|
-
// Prevent any potential side-effects by appending to a template element.
|
|
100
|
+
// Prevent any potential side-effects by cloning into a template element.
|
|
105
101
|
// See https://github.com/GoogleChrome/lighthouse/issues/11465
|
|
106
102
|
const template = element.ownerDocument.createElement('template');
|
|
103
|
+
const clone = template.content.ownerDocument.importNode(element);
|
|
107
104
|
template.content.append(clone);
|
|
108
105
|
ignoreAttrs.concat(autoFillIgnoreAttrs).forEach(attribute =>{
|
|
109
106
|
clone.removeAttribute(attribute);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "13.5.0-dev.
|
|
4
|
+
"version": "13.5.0-dev.20261006",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -179,6 +179,7 @@
|
|
|
179
179
|
"typed-query-selector": "^2.12.0",
|
|
180
180
|
"typescript": "6.0.3",
|
|
181
181
|
"wait-for-expect": "^3.0.2",
|
|
182
|
+
"web-features": "^3.40.0",
|
|
182
183
|
"webtreemap-cdt": "^3.2.1"
|
|
183
184
|
},
|
|
184
185
|
"dependencies": {
|
|
@@ -186,7 +187,7 @@
|
|
|
186
187
|
"@sentry/node": "^10.0.0",
|
|
187
188
|
"ajv": "^8.20.0",
|
|
188
189
|
"ajv-formats": "^3.0.1",
|
|
189
|
-
"axe-core": "
|
|
190
|
+
"axe-core": "4.13.0",
|
|
190
191
|
"chrome-launcher": "^1.2.2",
|
|
191
192
|
"configstore": "^7.0.0",
|
|
192
193
|
"csp_evaluator": "1.1.8",
|
|
@@ -206,7 +207,6 @@
|
|
|
206
207
|
"speedline-core": "^1.4.3",
|
|
207
208
|
"third-party-web": "^0.30.0",
|
|
208
209
|
"tldts-icann": "^7.4.16",
|
|
209
|
-
"web-features": "^3.40.0",
|
|
210
210
|
"ws": "^7.0.0",
|
|
211
211
|
"yargs": "^17.3.1",
|
|
212
212
|
"yargs-parser": "^21.0.0"
|