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.
@@ -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
- takeNetworkRequestUrls = servers[0].takeRequestUrls.bind(servers[0]);
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?.forEach(s => s.close());
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, only write to disk in debug mode.
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 = /(?:\d|\.)+/.source;
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 (typeof actual === 'number' && NUMERICAL_EXPECTATION_REGEXP.test(expected)) {
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 Math.abs(actual - prefixNumber) <= postfixNumber;
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
- if (!inclExclCopy) {
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
- diffs.push({
169
- path,
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
- reject(new Error('Timed out waiting for provided lighthouseRunner')), 1000 * 120);
178
+ timeoutId = setTimeout(() => {
179
+ isTimeout = true;
180
+ reject(new Error('Timed out waiting for provided lighthouseRunner'));
181
+ }, 1000 * 120);
176
182
  });
177
- const timedResult = await Promise.race([
178
- lighthouseRunner(requestedUrl, config, logger, mergedTestRunnerOptions),
179
- timeoutPromise,
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
- bufferedConsole.log('Timed out. log from lighthouseRunner:');
198
- bufferedConsole.log(logger.getLog());
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
- ...testRunnerOptions,
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}
@@ -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
- const date = new Date(highData[featureId]);
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
- roughEstimateOfLCPLoadStart: timingSummary.metrics.lcpLoadDelay,
80
- roughEstimateOfLCPLoadEnd: timingSummary.metrics.lcpLoadDuration,
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 loadDelay = Math.max(ttfb, Math.min(unclampedLoadStart, metricLcp));
45
+ const loadStart = Math.max(ttfb, Math.min(unclampedLoadStart, metricLcp));
46
46
 
47
47
  const unclampedLoadEnd = (lcpRecord.networkEndTime - timeOrigin) * throttleRatio;
48
- const loadDuration = Math.max(loadDelay, Math.min(unclampedLoadEnd, metricLcp));
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": "≤2020-03-24",
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": "≤2018-10-02",
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
- /** @type {Element} */
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.20261002",
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": "^4.13.0",
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"