lighthouse 9.5.0-dev.20220504 → 9.5.0-dev.20220505

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.
@@ -8,47 +8,76 @@
8
8
  * @fileoverview A runner that launches Chrome and executes Lighthouse via a
9
9
  * bundle to test that bundling has produced correct and runnable code.
10
10
  * Currently uses `lighthouse-dt-bundle.js`.
11
+ * Runs in a worker to avoid messing up marky's global state.
11
12
  */
12
13
 
13
14
  import fs from 'fs';
15
+ import os from 'os';
16
+ import {Worker, isMainThread, parentPort, workerData} from 'worker_threads';
17
+ import {once} from 'events';
14
18
 
15
19
  import puppeteer from 'puppeteer-core';
16
20
  import ChromeLauncher from 'chrome-launcher';
17
21
 
18
22
  import ChromeProtocol from '../../../../lighthouse-core/gather/connections/cri.js';
19
23
  import {LH_ROOT} from '../../../../root.js';
24
+ import {loadArtifacts, saveArtifacts} from '../../../../lighthouse-core/lib/asset-saver.js';
20
25
 
21
- const originalBuffer = global.Buffer;
22
- const originalRequire = global.require;
23
- if (typeof globalThis === 'undefined') {
24
- // @ts-expect-error - exposing for loading of dt-bundle.
25
- global.globalThis = global;
26
+ // This runs only in the worker. The rest runs on the main thread.
27
+ if (!isMainThread && parentPort) {
28
+ (async () => {
29
+ const {url, configJson, testRunnerOptions} = workerData;
30
+ try {
31
+ const result = await runBundledLighthouse(url, configJson, testRunnerOptions);
32
+ // Save to assets directory because LighthouseError won't survive postMessage.
33
+ const assetsDir = fs.mkdtempSync(os.tmpdir() + '/smoke-bundle-assets-');
34
+ await saveArtifacts(result.artifacts, assetsDir);
35
+ const value = {
36
+ lhr: result.lhr,
37
+ assetsDir,
38
+ };
39
+ parentPort?.postMessage({type: 'result', value});
40
+ } catch (err) {
41
+ console.error(err);
42
+ parentPort?.postMessage({type: 'error', value: err});
43
+ }
44
+ })();
26
45
  }
27
46
 
28
- // Load bundle, which creates a `global.runBundledLighthouse`.
29
- eval(fs.readFileSync(LH_ROOT + '/dist/lighthouse-dt-bundle.js', 'utf-8'));
30
-
31
- global.require = originalRequire;
32
- global.Buffer = originalBuffer;
33
-
34
- /** @type {import('../../../../lighthouse-core/index.js')} */
35
- // @ts-expect-error - not worth giving test global an actual type.
36
- const lighthouse = global.runBundledLighthouse;
37
-
38
47
  /**
39
- * Launch Chrome and do a full Lighthouse run via the Lighthouse DevTools bundle.
40
48
  * @param {string} url
41
- * @param {LH.Config.Json=} configJson
42
- * @param {{isDebug?: boolean, useFraggleRock?: boolean}=} testRunnerOptions
43
- * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
49
+ * @param {LH.Config.Json|undefined} configJson
50
+ * @param {{isDebug?: boolean, useFraggleRock?: boolean}} testRunnerOptions
51
+ * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts}>}
44
52
  */
45
- async function runLighthouse(url, configJson, testRunnerOptions = {}) {
53
+ async function runBundledLighthouse(url, configJson, testRunnerOptions) {
54
+ if (isMainThread || !parentPort) {
55
+ throw new Error('must be called in worker');
56
+ }
57
+
58
+ const originalBuffer = global.Buffer;
59
+ const originalRequire = global.require;
60
+ if (typeof globalThis === 'undefined') {
61
+ // @ts-expect-error - exposing for loading of dt-bundle.
62
+ global.globalThis = global;
63
+ }
64
+
65
+ // Load bundle, which creates a `global.runBundledLighthouse`.
66
+ eval(fs.readFileSync(LH_ROOT + '/dist/lighthouse-dt-bundle.js', 'utf-8'));
67
+
68
+ global.require = originalRequire;
69
+ global.Buffer = originalBuffer;
70
+
71
+ /** @type {import('../../../../lighthouse-core/index.js')} */
72
+ // @ts-expect-error - not worth giving test global an actual type.
73
+ const lighthouse = global.runBundledLighthouse;
74
+
46
75
  // Launch and connect to Chrome.
47
76
  const launchedChrome = await ChromeLauncher.launch();
48
77
  const port = launchedChrome.port;
49
78
 
79
+ // Run Lighthouse.
50
80
  try {
51
- // Run Lighthouse.
52
81
  const logLevel = testRunnerOptions.isDebug ? 'info' : undefined;
53
82
  let runnerResult;
54
83
  if (testRunnerOptions.useFraggleRock) {
@@ -66,7 +95,6 @@ async function runLighthouse(url, configJson, testRunnerOptions = {}) {
66
95
  return {
67
96
  lhr: runnerResult.lhr,
68
97
  artifacts: runnerResult.artifacts,
69
- log: '', // TODO: if want to run in parallel, need to capture lighthouse-logger output.
70
98
  };
71
99
  } finally {
72
100
  // Clean up and return results.
@@ -74,6 +102,53 @@ async function runLighthouse(url, configJson, testRunnerOptions = {}) {
74
102
  }
75
103
  }
76
104
 
105
+ /**
106
+ * Launch Chrome and do a full Lighthouse run via the Lighthouse DevTools bundle.
107
+ * @param {string} url
108
+ * @param {LH.Config.Json=} configJson
109
+ * @param {{isDebug?: boolean, useFraggleRock?: boolean}=} testRunnerOptions
110
+ * @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
111
+ */
112
+ async function runLighthouse(url, configJson, testRunnerOptions = {}) {
113
+ /** @type {string[]} */
114
+ const logs = [];
115
+ const worker = new Worker(new URL(import.meta.url), {
116
+ stdout: true,
117
+ stderr: true,
118
+ workerData: {url, configJson, testRunnerOptions},
119
+ });
120
+ worker.stdout.setEncoding('utf8');
121
+ worker.stderr.setEncoding('utf8');
122
+ worker.stdout.addListener('data', (data) => {
123
+ process.stdout.write(data);
124
+ logs.push(`STDOUT: ${data}`);
125
+ });
126
+ worker.stderr.addListener('data', (data) => {
127
+ process.stderr.write(data);
128
+ logs.push(`STDERR: ${data}`);
129
+ });
130
+ const [workerResponse] = await once(worker, 'message');
131
+ const log = logs.join('') + '\n';
132
+
133
+ if (workerResponse.type === 'error') {
134
+ new Error(`Worker returned an error: ${workerResponse.value}\nLog:\n${log}`);
135
+ }
136
+
137
+ const result = workerResponse.value;
138
+ if (!result.lhr || !result.assetsDir) {
139
+ throw new Error(`invalid response from worker:\n${JSON.stringify(result, null, 2)}`);
140
+ }
141
+
142
+ const artifacts = loadArtifacts(result.assetsDir);
143
+ fs.rmSync(result.assetsDir, {recursive: true});
144
+
145
+ return {
146
+ lhr: result.lhr,
147
+ artifacts,
148
+ log,
149
+ };
150
+ }
151
+
77
152
  export {
78
153
  runLighthouse,
79
154
  };
@@ -74,7 +74,7 @@ class Driver {
74
74
  const session = await this._page.target().createCDPSession();
75
75
  this._session = this.defaultSession = new ProtocolSession(session);
76
76
  this._executionContext = new ExecutionContext(this._session);
77
- this._fetcher = new Fetcher(this._session, this._executionContext);
77
+ this._fetcher = new Fetcher(this._session);
78
78
  log.timeEnd(status);
79
79
  }
80
80
 
@@ -64,7 +64,7 @@ class Driver {
64
64
  defaultSession = this;
65
65
 
66
66
  // eslint-disable-next-line no-invalid-this
67
- fetcher = new Fetcher(this.defaultSession, this.executionContext);
67
+ fetcher = new Fetcher(this.defaultSession);
68
68
 
69
69
  /**
70
70
  * @param {Connection} connection
@@ -6,124 +6,55 @@
6
6
  'use strict';
7
7
 
8
8
  /**
9
- * @fileoverview Fetcher is a utility for making requests within the context of the page.
10
- * Requests can circumvent CORS, and so are good for fetching source maps that may be hosted
11
- * on a different origin.
9
+ * @fileoverview Fetcher is a utility for making requests to any arbitrary resource,
10
+ * ignoring normal browser constraints such as CORS.
12
11
  */
13
12
 
14
- /* global document */
13
+ /* global fetch */
15
14
 
16
15
  /** @typedef {{content: string|null, status: number|null}} FetchResponse */
17
16
 
18
- const log = require('lighthouse-logger');
19
- const {getBrowserVersion} = require('./driver/environment.js');
20
-
21
17
  class Fetcher {
22
18
  /**
23
19
  * @param {LH.Gatherer.FRProtocolSession} session
24
- * @param {import('./driver/execution-context.js')} executionContext
25
20
  */
26
- constructor(session, executionContext) {
21
+ constructor(session) {
27
22
  this.session = session;
28
- this.executionContext = executionContext;
29
- /** @type {Map<string, (event: LH.Crdp.Fetch.RequestPausedEvent) => void>} */
30
- this._onRequestPausedHandlers = new Map();
31
- this._onRequestPaused = this._onRequestPaused.bind(this);
32
- this._enabled = false;
33
23
  }
34
24
 
35
25
  /**
36
- * Chrome M92 and above:
37
- * We use `Network.loadNetworkResource` to fetch each resource.
38
- *
39
- * Chrome <M92:
40
- * The Fetch domain accepts patterns for controlling what requests are intercepted, but we
41
- * enable the domain for all patterns and filter events at a lower level to support multiple
42
- * concurrent usages. Reasons for this:
43
- *
44
- * 1) only one set of patterns may be applied for the entire domain.
45
- * 2) every request that matches the patterns are paused and only resumes when certain Fetch
46
- * commands are sent. So a listener of the `Fetch.requestPaused` event must either handle
47
- * the requests it cares about, or explicitly allow them to continue.
48
- * 3) if multiple commands to continue the same request are sent, protocol errors occur.
26
+ * Fetches any resource using the network directly.
49
27
  *
50
- * So instead we have one global `Fetch.enable` / `Fetch.requestPaused` pair, and allow specific
51
- * urls to be intercepted via `fetcher._setOnRequestPausedHandler`.
52
- */
53
- async enable() {
54
- if (this._enabled) return;
55
-
56
- this._enabled = true;
57
- await this.session.sendCommand('Fetch.enable', {
58
- patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}],
59
- });
60
- await this.session.on('Fetch.requestPaused', this._onRequestPaused);
61
- }
62
-
63
- async disable() {
64
- if (!this._enabled) return;
65
-
66
- this._enabled = false;
67
- await this.session.off('Fetch.requestPaused', this._onRequestPaused);
68
- await this.session.sendCommand('Fetch.disable');
69
- this._onRequestPausedHandlers.clear();
70
- }
71
-
72
- /**
73
28
  * @param {string} url
74
- * @param {(event: LH.Crdp.Fetch.RequestPausedEvent) => void} handler
75
- */
76
- async _setOnRequestPausedHandler(url, handler) {
77
- this._onRequestPausedHandlers.set(url, handler);
78
- }
79
-
80
- /**
81
- * @param {LH.Crdp.Fetch.RequestPausedEvent} event
29
+ * @param {{timeout: number}=} options timeout is in ms
30
+ * @return {Promise<FetchResponse>}
82
31
  */
83
- _onRequestPaused(event) {
84
- const handler = this._onRequestPausedHandlers.get(event.request.url);
85
- if (handler) {
86
- handler(event);
87
- } else {
88
- // Nothing cares about this URL, so continue.
89
- this.session.sendCommand('Fetch.continueRequest', {requestId: event.requestId}).catch(err => {
90
- log.error('Fetcher', `Failed to continueRequest: ${err.message}`);
91
- });
32
+ async fetchResource(url, options = {timeout: 2_000}) {
33
+ // In Lightrider, `Network.loadNetworkResource` is not implemented, but fetch
34
+ // is configured to work for any resource.
35
+ if (global.isLightrider) {
36
+ return this._wrapWithTimeout(this._fetchWithFetchApi(url), options.timeout);
92
37
  }
93
- }
94
38
 
95
- /**
96
- * `Network.loadNetworkResource` was introduced in M88.
97
- * The long timeout bug with `IO.read` was fixed in M92:
98
- * https://bugs.chromium.org/p/chromium/issues/detail?id=1191757
99
- * Lightrider has a bug forcing us to use the old version for now:
100
- * https://docs.google.com/document/d/1V-DxgsOFMPxUuFrdGPQpyiCqSljvgNlOqXCtqDtd0b8/edit?usp=sharing&resourcekey=0-aIaIqcHFKG-0dX4MAudBEw
101
- * @return {Promise<boolean>}
102
- */
103
- async shouldUseLegacyFetcher() {
104
- const {milestone} = await getBrowserVersion(this.session);
105
- return milestone < 92 || Boolean(global.isLightrider);
39
+ return this._fetchResourceOverProtocol(url, options);
106
40
  }
107
41
 
108
42
  /**
109
- * Requires that `fetcher.enable` has been called.
110
- *
111
- * Fetches any resource in a way that circumvents CORS.
112
- *
113
43
  * @param {string} url
114
- * @param {{timeout: number}=} options timeout is in ms
115
44
  * @return {Promise<FetchResponse>}
116
45
  */
117
- async fetchResource(url, options = {timeout: 2_000}) {
118
- if (!this._enabled) {
119
- throw new Error('Must call `enable` before using fetchResource');
120
- }
46
+ async _fetchWithFetchApi(url) {
47
+ const response = await fetch(url);
121
48
 
122
- if (await this.shouldUseLegacyFetcher()) {
123
- return this._fetchResourceIframe(url, options);
124
- }
49
+ let content = null;
50
+ try {
51
+ content = await response.text();
52
+ } catch {}
125
53
 
126
- return this._fetchResourceOverProtocol(url, options);
54
+ return {
55
+ content,
56
+ status: response.status,
57
+ };
127
58
  }
128
59
 
129
60
  /**
@@ -172,18 +103,6 @@ class Fetcher {
172
103
  };
173
104
  }
174
105
 
175
- /**
176
- * @param {string} requestId
177
- * @return {Promise<string>}
178
- */
179
- async _resolveResponseBody(requestId) {
180
- const responseBody = await this.session.sendCommand('Fetch.getResponseBody', {requestId});
181
- if (responseBody.base64Encoded) {
182
- return Buffer.from(responseBody.body, 'base64').toString();
183
- }
184
- return responseBody.body;
185
- }
186
-
187
106
  /**
188
107
  * @param {string} url
189
108
  * @param {{timeout: number}} options timeout is in ms
@@ -191,18 +110,7 @@ class Fetcher {
191
110
  */
192
111
  async _fetchResourceOverProtocol(url, options) {
193
112
  const startTime = Date.now();
194
-
195
- /** @type {NodeJS.Timeout} */
196
- let timeoutHandle;
197
- const timeoutPromise = new Promise((_, reject) => {
198
- timeoutHandle = setTimeout(reject, options.timeout, new Error('Timed out fetching resource'));
199
- });
200
-
201
- const responsePromise = this._loadNetworkResource(url);
202
-
203
- /** @type {{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}} */
204
- const response = await Promise.race([responsePromise, timeoutPromise])
205
- .finally(() => clearTimeout(timeoutHandle));
113
+ const response = await this._wrapWithTimeout(this._loadNetworkResource(url), options.timeout);
206
114
 
207
115
  const isOk = response.status && response.status >= 200 && response.status <= 299;
208
116
  if (!response.stream || !isOk) return {status: response.status, content: null};
@@ -213,105 +121,21 @@ class Fetcher {
213
121
  }
214
122
 
215
123
  /**
216
- * Fetches resource by injecting an iframe into the page.
217
- * @param {string} url
218
- * @param {{timeout: number}} options timeout is in ms
219
- * @return {Promise<FetchResponse>}
124
+ * @template T
125
+ * @param {Promise<T>} promise
126
+ * @param {number} ms
220
127
  */
221
- async _fetchResourceIframe(url, options) {
222
- /** @type {Promise<FetchResponse>} */
223
- const requestInterceptionPromise = new Promise((resolve, reject) => {
224
- /** @param {LH.Crdp.Fetch.RequestPausedEvent} event */
225
- const handlerAsync = async event => {
226
- const {requestId, responseStatusCode} = event;
227
-
228
- // The first requestPaused event is for the request stage. Continue it.
229
- if (!responseStatusCode) {
230
- // Remove cookies so we aren't buying stuff on Amazon.
231
- const headers = Object.entries(event.request.headers)
232
- .filter(([name]) => name !== 'Cookie')
233
- .map(([name, value]) => {
234
- return {name, value};
235
- });
236
-
237
- await this.session.sendCommand('Fetch.continueRequest', {
238
- requestId,
239
- headers,
240
- });
241
- return;
242
- }
243
-
244
- if (responseStatusCode >= 200 && responseStatusCode <= 299) {
245
- resolve({
246
- status: responseStatusCode,
247
- content: await this._resolveResponseBody(requestId),
248
- });
249
- } else {
250
- resolve({status: responseStatusCode, content: null});
251
- }
252
-
253
- // Fail the request (from the page's perspective) so that the iframe never loads.
254
- await this.session.sendCommand('Fetch.failRequest', {requestId, errorReason: 'Aborted'});
255
- };
256
- this._setOnRequestPausedHandler(url, event => handlerAsync(event).catch(reject));
257
- });
258
-
259
- /**
260
- * @param {string} src
261
- */
262
- /* c8 ignore start */
263
- function injectIframe(src) {
264
- const iframe = document.createElement('iframe');
265
- // Try really hard not to affect the page.
266
- iframe.style.display = 'none';
267
- iframe.style.visibility = 'hidden';
268
- iframe.style.position = 'absolute';
269
- iframe.style.top = '-1000px';
270
- iframe.style.left = '-1000px';
271
- iframe.style.width = '1px';
272
- iframe.style.height = '1px';
273
- iframe.src = src;
274
- iframe.onload = iframe.onerror = () => {
275
- iframe.remove();
276
- iframe.onload = null;
277
- iframe.onerror = null;
278
- };
279
- document.body.appendChild(iframe);
280
- }
281
- /* c8 ignore stop */
282
-
128
+ async _wrapWithTimeout(promise, ms) {
283
129
  /** @type {NodeJS.Timeout} */
284
- let asyncTimeout;
285
- /** @type {Promise<never>} */
130
+ let timeoutHandle;
286
131
  const timeoutPromise = new Promise((_, reject) => {
287
- asyncTimeout = setTimeout(reject, options.timeout, new Error('Timed out fetching resource.'));
288
- });
289
-
290
- const racePromise = Promise.race([
291
- timeoutPromise,
292
- requestInterceptionPromise,
293
- ]).finally(() => clearTimeout(asyncTimeout));
294
-
295
- // Temporarily disable auto-attaching for this iframe.
296
- await this.session.sendCommand('Target.setAutoAttach', {
297
- autoAttach: false,
298
- waitForDebuggerOnStart: false,
299
- });
300
-
301
- const injectionPromise = this.executionContext.evaluate(injectIframe, {
302
- args: [url],
303
- useIsolation: true,
132
+ timeoutHandle = setTimeout(reject, ms, new Error('Timed out fetching resource'));
304
133
  });
305
134
 
306
- const [fetchResult] = await Promise.all([racePromise, injectionPromise]);
307
-
308
- await this.session.sendCommand('Target.setAutoAttach', {
309
- flatten: true,
310
- autoAttach: true,
311
- waitForDebuggerOnStart: true,
312
- });
313
-
314
- return fetchResult;
135
+ /** @type {Promise<T>} */
136
+ const wrappedPromise = await Promise.race([promise, timeoutPromise])
137
+ .finally(() => clearTimeout(timeoutHandle));
138
+ return wrappedPromise;
315
139
  }
316
140
  }
317
141
 
@@ -183,11 +183,6 @@ class GatherRunner {
183
183
  const resetStorage = !options.settings.disableStorageReset;
184
184
  if (resetStorage) await storage.clearDataForOrigin(session, options.requestedUrl);
185
185
 
186
- // Disable fetcher, in case a gatherer enabled it.
187
- // This cleanup should be removed once the only usage of
188
- // fetcher (fetching arbitrary URLs) is replaced by new protocol support.
189
- await driver.fetcher.disable();
190
-
191
186
  await driver.disconnect();
192
187
  } catch (err) {
193
188
  // Ignore disconnecting error if browser was already closed.
@@ -524,12 +519,6 @@ class GatherRunner {
524
519
  await GatherRunner.populateBaseArtifacts(passContext);
525
520
  isFirstPass = false;
526
521
  }
527
-
528
- // Disable fetcher for every pass, in case a gatherer enabled it.
529
- // Noop if fetcher was never enabled.
530
- // This cleanup should be removed once the only usage of
531
- // fetcher (fetching arbitrary URLs) is replaced by new protocol support.
532
- await driver.fetcher.disable();
533
522
  }
534
523
 
535
524
  await GatherRunner.disposeDriver(driver, options);
@@ -7,25 +7,6 @@
7
7
 
8
8
  const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
9
9
 
10
- /* global fetch, location */
11
-
12
- /** @return {Promise<LH.Artifacts['RobotsTxt']>} */
13
- /* c8 ignore start */
14
- async function getRobotsTxtContent() {
15
- try {
16
- const response = await fetch(new URL('/robots.txt', location.href).href);
17
- if (!response.ok) {
18
- return {status: response.status, content: null};
19
- }
20
-
21
- const content = await response.text();
22
- return {status: response.status, content};
23
- } catch (err) {
24
- return {status: null, content: null, errorMessage: err.message};
25
- }
26
- }
27
- /* c8 ignore stop */
28
-
29
10
  class RobotsTxt extends FRGatherer {
30
11
  /** @type {LH.Gatherer.GathererMeta} */
31
12
  meta = {
@@ -37,18 +18,8 @@ class RobotsTxt extends FRGatherer {
37
18
  * @return {Promise<LH.Artifacts['RobotsTxt']>}
38
19
  */
39
20
  async getArtifact(passContext) {
40
- // Iframe fetcher still has issues with CSPs.
41
- // Only use the fetcher if we are fetching over the protocol.
42
- if (await passContext.driver.fetcher.shouldUseLegacyFetcher()) {
43
- return passContext.driver.executionContext.evaluate(getRobotsTxtContent, {
44
- args: [],
45
- useIsolation: true,
46
- });
47
- }
48
-
49
21
  const {finalUrl} = passContext.baseArtifacts.URL;
50
22
  const robotsUrl = new URL('/robots.txt', finalUrl).href;
51
- await passContext.driver.fetcher.enable();
52
23
  return passContext.driver.fetcher.fetchResource(robotsUrl)
53
24
  .catch(err => ({status: null, content: null, errorMessage: err.message}));
54
25
  }
@@ -143,7 +143,6 @@ class SourceMaps extends FRGatherer {
143
143
  * @return {Promise<LH.Artifacts['SourceMaps']>}
144
144
  */
145
145
  async getArtifact(context) {
146
- await context.driver.fetcher.enable();
147
146
  const eventProcessPromises = this._scriptParsedEvents
148
147
  .map((event) => this._retrieveMapFromScriptParsedEvent(context.driver, event));
149
148
  return Promise.all(eventProcessPromises);
@@ -19,11 +19,10 @@ const stackPacksToInclude = [
19
19
  packId: 'wordpress',
20
20
  requiredStacks: ['js:wordpress'],
21
21
  },
22
- // waiting for https://github.com/johnmichel/Library-Detector-for-Chrome/pull/193
23
- // {
24
- // packId: 'ezoic',
25
- // requiredStacks: ['js:ezoic'],
26
- // },
22
+ {
23
+ packId: 'ezoic',
24
+ requiredStacks: ['js:ezoic'],
25
+ },
27
26
  {
28
27
  packId: 'drupal',
29
28
  requiredStacks: ['js:drupal'],
@@ -50,6 +50,7 @@ const taskGroups = {
50
50
  'UpdateLayer',
51
51
  'UpdateLayerTree',
52
52
  'CompositeLayers',
53
+ 'PrePaint', // New name for UpdateLayerTree: https://crrev.com/c/3519012
53
54
  ],
54
55
  },
55
56
  scriptParseCompile: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.5.0-dev.20220504",
3
+ "version": "9.5.0-dev.20220505",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "start": "yarn build-report --standalone && node ./lighthouse-cli/index.js",
38
38
  "jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
39
39
  "test": "yarn diff:sample-json && yarn lint --quiet && yarn unit && yarn type-check",
40
- "test-bundle": "yarn smoke --runner bundle -j=1 --retries=2",
40
+ "test-bundle": "yarn smoke --runner bundle --retries=2",
41
41
  "test-clients": "yarn jest \"$PWD/clients/\" && yarn jest --testMatch=\"**/clients/test/**/*-test-pptr.js\"",
42
42
  "test-viewer": "yarn unit-viewer && yarn jest --testMatch=\"**/viewer/**/*-test-pptr.js\"",
43
43
  "test-treemap": "yarn unit-treemap && yarn jest --testMatch=\"**/treemap/**/*-test-pptr.js\"",
@@ -190,7 +190,7 @@
190
190
  "http-link-header": "^0.8.0",
191
191
  "intl-messageformat": "^4.4.0",
192
192
  "jpeg-js": "^0.4.3",
193
- "js-library-detector": "^6.4.0",
193
+ "js-library-detector": "^6.5.0",
194
194
  "lighthouse-logger": "^1.3.0",
195
195
  "lighthouse-stack-packs": "^1.8.1",
196
196
  "lodash": "^4.17.21",