lighthouse 9.2.0 → 9.3.0
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/changelog.md +67 -0
- package/dist/report/bundle.esm.js +23 -11
- package/dist/report/flow.js +11 -11
- package/dist/report/standalone.js +8 -8
- package/flow-report/src/common.tsx +1 -1
- package/flow-report/src/sidebar/flow.tsx +1 -1
- package/flow-report/src/util.ts +1 -2
- package/lighthouse-cli/bin.js +0 -4
- package/lighthouse-cli/cli-flags.js +22 -8
- package/lighthouse-cli/run.js +5 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -2
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +103 -0
- package/lighthouse-cli/test/smokehouse/readme.md +10 -8
- package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
- package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -5
- package/lighthouse-core/audits/accessibility/axe-audit.js +4 -4
- package/lighthouse-core/audits/accessibility/object-alt.js +3 -3
- package/lighthouse-core/audits/audit.js +19 -4
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
- package/lighthouse-core/audits/deprecations.js +2 -2
- package/lighthouse-core/audits/dobetterweb/inspector-issues.js +11 -11
- package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -1
- package/lighthouse-core/audits/font-display.js +1 -1
- package/lighthouse-core/audits/installable-manifest.js +1 -1
- package/lighthouse-core/audits/is-on-https.js +1 -1
- package/lighthouse-core/audits/maskable-icon.js +1 -1
- package/lighthouse-core/audits/multi-check-audit.js +1 -1
- package/lighthouse-core/audits/network-requests.js +4 -4
- package/lighthouse-core/audits/preload-fonts.js +2 -3
- package/lighthouse-core/audits/preload-lcp-image.js +2 -12
- package/lighthouse-core/audits/seo/is-crawlable.js +2 -1
- package/lighthouse-core/audits/themed-omnibox.js +1 -1
- package/lighthouse-core/audits/uses-rel-preload.js +2 -3
- package/lighthouse-core/audits/valid-source-maps.js +2 -2
- package/lighthouse-core/computed/image-records.js +1 -1
- package/lighthouse-core/computed/js-bundles.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-metric.js +1 -1
- package/lighthouse-core/computed/metrics/timing-summary.js +35 -35
- package/lighthouse-core/computed/page-dependency-graph.js +2 -2
- package/lighthouse-core/computed/resource-summary.js +1 -1
- package/lighthouse-core/config/config-helpers.js +2 -2
- package/lighthouse-core/config/config.js +1 -1
- package/lighthouse-core/fraggle-rock/config/filters.js +23 -1
- package/lighthouse-core/fraggle-rock/config/validation.js +2 -2
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +5 -4
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +0 -1
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +0 -1
- package/lighthouse-core/fraggle-rock/user-flow.js +4 -4
- package/lighthouse-core/gather/connections/connection.js +1 -1
- package/lighthouse-core/gather/driver/execution-context.js +1 -1
- package/lighthouse-core/gather/driver/network-monitor.js +2 -2
- package/lighthouse-core/gather/driver/target-manager.js +1 -1
- package/lighthouse-core/gather/driver.js +1 -1
- package/lighthouse-core/gather/gatherers/console-messages.js +2 -2
- package/lighthouse-core/gather/gatherers/inspector-issues.js +34 -46
- package/lighthouse-core/gather/gatherers/js-usage.js +1 -1
- package/lighthouse-core/gather/gatherers/seo/font-size.js +1 -1
- package/lighthouse-core/gather/gatherers/trace-elements.js +8 -43
- package/lighthouse-core/index.js +4 -3
- package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
- package/lighthouse-core/lib/asset-saver.js +6 -4
- package/lighthouse-core/lib/dependency-graph/simulator/connection-pool.js +1 -1
- package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +1 -1
- package/lighthouse-core/lib/emulation.js +1 -1
- package/lighthouse-core/lib/icons.js +1 -2
- package/lighthouse-core/lib/median-run.js +1 -2
- package/lighthouse-core/lib/network-recorder.js +2 -2
- package/lighthouse-core/lib/network-request.js +1 -1
- package/lighthouse-core/lib/stack-packs.js +5 -0
- package/lighthouse-core/lib/tracehouse/cpu-profile-model.js +4 -6
- package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +1 -1
- package/lighthouse-core/lib/tracehouse/trace-processor.js +14 -17
- package/lighthouse-core/lib/url-shim.js +15 -0
- package/lighthouse-core/runner.js +62 -49
- package/lighthouse-core/scripts/download-chrome.sh +15 -2
- package/package.json +21 -25
- package/report/assets/templates.html +2 -1
- package/report/clients/standalone.js +5 -1
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/components.js +2 -2
- package/report/renderer/performance-category-renderer.js +4 -4
- package/report/renderer/report-renderer.js +1 -1
- package/report/renderer/report-ui-features.js +15 -3
- package/report/test/generator/report-generator-test.js +1 -1
- package/report/test/renderer/report-ui-features-test.js +22 -4
- package/report/test-assets/faux-psi.js +2 -3
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +40 -4
- package/shared/localization/locales/ar.json +59 -23
- package/shared/localization/locales/bg.json +40 -4
- package/shared/localization/locales/ca.json +45 -9
- package/shared/localization/locales/cs.json +39 -3
- package/shared/localization/locales/da.json +43 -7
- package/shared/localization/locales/de.json +42 -6
- package/shared/localization/locales/el.json +59 -23
- package/shared/localization/locales/en-GB.json +43 -7
- package/shared/localization/locales/en-US.json +48 -6
- package/shared/localization/locales/en-XA.json +40 -4
- package/shared/localization/locales/en-XL.json +48 -6
- package/shared/localization/locales/es-419.json +39 -3
- package/shared/localization/locales/es.json +46 -10
- package/shared/localization/locales/fi.json +57 -21
- package/shared/localization/locales/fil.json +40 -4
- package/shared/localization/locales/fr.json +45 -9
- package/shared/localization/locales/he.json +41 -5
- package/shared/localization/locales/hi.json +59 -23
- package/shared/localization/locales/hr.json +57 -21
- package/shared/localization/locales/hu.json +39 -3
- package/shared/localization/locales/id.json +40 -4
- package/shared/localization/locales/it.json +40 -4
- package/shared/localization/locales/ja.json +39 -3
- package/shared/localization/locales/ko.json +40 -4
- package/shared/localization/locales/lt.json +40 -4
- package/shared/localization/locales/lv.json +56 -20
- package/shared/localization/locales/nl.json +40 -4
- package/shared/localization/locales/no.json +56 -20
- package/shared/localization/locales/pl.json +56 -20
- package/shared/localization/locales/pt-PT.json +57 -21
- package/shared/localization/locales/pt.json +41 -5
- package/shared/localization/locales/ro.json +39 -3
- package/shared/localization/locales/ru.json +40 -4
- package/shared/localization/locales/sk.json +39 -3
- package/shared/localization/locales/sl.json +39 -3
- package/shared/localization/locales/sr-Latn.json +40 -4
- package/shared/localization/locales/sr.json +40 -4
- package/shared/localization/locales/sv.json +39 -3
- package/shared/localization/locales/ta.json +47 -11
- package/shared/localization/locales/te.json +61 -25
- package/shared/localization/locales/th.json +39 -3
- package/shared/localization/locales/tr.json +40 -4
- package/shared/localization/locales/uk.json +40 -4
- package/shared/localization/locales/vi.json +63 -27
- package/shared/localization/locales/zh-HK.json +40 -4
- package/shared/localization/locales/zh-TW.json +45 -9
- package/shared/localization/locales/zh.json +41 -5
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/snyk/snapshot.json +4 -1
- package/types/artifacts.d.ts +16 -6
- package/types/robots-parser/index.d.ts +0 -20
|
@@ -93,7 +93,7 @@ const FlowStepThumbnail: FunctionComponent<{
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
let thumbnail;
|
|
96
|
-
if (frames
|
|
96
|
+
if (frames?.length) {
|
|
97
97
|
thumbnail = frames[frames.length - 1].data;
|
|
98
98
|
if (lhr.gatherMode === 'timespan') {
|
|
99
99
|
return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
|
package/flow-report/src/util.ts
CHANGED
|
@@ -44,8 +44,7 @@ function getScreenDimensions(reportResult: LH.Result) {
|
|
|
44
44
|
function getFullPageScreenshot(reportResult: LH.Result) {
|
|
45
45
|
const fullPageScreenshotAudit = reportResult.audits['full-page-screenshot'];
|
|
46
46
|
const fullPageScreenshot =
|
|
47
|
-
fullPageScreenshotAudit &&
|
|
48
|
-
fullPageScreenshotAudit.details &&
|
|
47
|
+
fullPageScreenshotAudit?.details &&
|
|
49
48
|
fullPageScreenshotAudit.details.type === 'full-page-screenshot' &&
|
|
50
49
|
fullPageScreenshotAudit.details;
|
|
51
50
|
|
package/lighthouse-cli/bin.js
CHANGED
|
@@ -24,7 +24,6 @@ import url from 'url';
|
|
|
24
24
|
import module from 'module';
|
|
25
25
|
|
|
26
26
|
import log from 'lighthouse-logger';
|
|
27
|
-
import updateNotifier from 'update-notifier';
|
|
28
27
|
|
|
29
28
|
import * as commands from './commands/commands.js';
|
|
30
29
|
import * as Printer from './printer.js';
|
|
@@ -51,9 +50,6 @@ function isDev() {
|
|
|
51
50
|
* @return {Promise<LH.RunnerResult|void>}
|
|
52
51
|
*/
|
|
53
52
|
async function begin() {
|
|
54
|
-
// Tell user if there's a newer version of LH.
|
|
55
|
-
updateNotifier({pkg}).notify();
|
|
56
|
-
|
|
57
53
|
const cliFlags = getFlags();
|
|
58
54
|
|
|
59
55
|
// Process terminating command
|
|
@@ -178,7 +178,6 @@ function getFlags(manualArgv, options = {}) {
|
|
|
178
178
|
},
|
|
179
179
|
'enable-error-reporting': {
|
|
180
180
|
type: 'boolean',
|
|
181
|
-
default: undefined, // Explicitly `undefined` so prompted by default.
|
|
182
181
|
describe: 'Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://git.io/vFFTO',
|
|
183
182
|
},
|
|
184
183
|
'gather-mode': {
|
|
@@ -343,19 +342,26 @@ function getFlags(manualArgv, options = {}) {
|
|
|
343
342
|
|
|
344
343
|
// Augmenting yargs type with auto-camelCasing breaks in tsc@4.1.2 and @types/yargs@15.0.11,
|
|
345
344
|
// so for now cast to add yarg's camelCase properties to type.
|
|
346
|
-
const argv = parser.argv;
|
|
345
|
+
const argv = /** @type {Awaited<typeof parser.argv>} */ (parser.argv);
|
|
347
346
|
const cliFlags = /** @type {typeof argv & CamelCasify<typeof argv>} */ (argv);
|
|
348
347
|
|
|
348
|
+
// yargs will return `undefined` for options that have a `coerce` function but
|
|
349
|
+
// are not actually present in the user input. Instead of passing properties
|
|
350
|
+
// explicitly set to undefined, delete them from the flags object.
|
|
351
|
+
for (const [k, v] of Object.entries(cliFlags)) {
|
|
352
|
+
if (v === undefined) delete cliFlags[k];
|
|
353
|
+
}
|
|
354
|
+
|
|
349
355
|
return cliFlags;
|
|
350
356
|
}
|
|
351
357
|
|
|
352
358
|
/**
|
|
353
359
|
* Support comma-separated values for some array flags by splitting on any ',' found.
|
|
354
360
|
* @param {Array<string>=} strings
|
|
355
|
-
* @return {Array<string
|
|
361
|
+
* @return {Array<string>=}
|
|
356
362
|
*/
|
|
357
363
|
function splitCommaSeparatedValues(strings) {
|
|
358
|
-
if (!strings) return
|
|
364
|
+
if (!strings) return;
|
|
359
365
|
|
|
360
366
|
return strings.flatMap(value => value.split(','));
|
|
361
367
|
}
|
|
@@ -365,7 +371,9 @@ function splitCommaSeparatedValues(strings) {
|
|
|
365
371
|
* @return {boolean|string|undefined}
|
|
366
372
|
*/
|
|
367
373
|
function coerceOptionalStringBoolean(value) {
|
|
368
|
-
if (
|
|
374
|
+
if (value === undefined) return;
|
|
375
|
+
|
|
376
|
+
if (typeof value !== 'string' && typeof value !== 'boolean') {
|
|
369
377
|
throw new Error('Invalid value: Argument must be a string or a boolean');
|
|
370
378
|
}
|
|
371
379
|
return value;
|
|
@@ -399,9 +407,11 @@ function coerceOutput(values) {
|
|
|
399
407
|
* allowlist specific locales. Why? So we can support the user who requests 'es-MX' (unsupported)
|
|
400
408
|
* and we'll fall back to 'es' (supported).
|
|
401
409
|
* @param {unknown} value
|
|
402
|
-
* @return {LH.Locale}
|
|
410
|
+
* @return {LH.Locale|undefined}
|
|
403
411
|
*/
|
|
404
412
|
function coerceLocale(value) {
|
|
413
|
+
if (value === undefined) return;
|
|
414
|
+
|
|
405
415
|
if (typeof value !== 'string') throw new Error(`Invalid value: Argument 'locale' must be a string`);
|
|
406
416
|
return /** @type {LH.Locale} */ (value);
|
|
407
417
|
}
|
|
@@ -431,9 +441,11 @@ function coerceExtraHeaders(value) {
|
|
|
431
441
|
/**
|
|
432
442
|
* Take yarg's unchecked object value and ensure it's proper throttling settings.
|
|
433
443
|
* @param {unknown} value
|
|
434
|
-
* @return {LH.ThrottlingSettings}
|
|
444
|
+
* @return {LH.ThrottlingSettings|undefined}
|
|
435
445
|
*/
|
|
436
446
|
function coerceThrottling(value) {
|
|
447
|
+
if (value === undefined) return;
|
|
448
|
+
|
|
437
449
|
if (!isObjectOfUnknownValues(value)) {
|
|
438
450
|
throw new Error(`Invalid value: Argument 'throttling' must be an object, specified per-property ('throttling.rttMs', 'throttling.throughputKbps', etc)`);
|
|
439
451
|
}
|
|
@@ -465,9 +477,11 @@ function coerceThrottling(value) {
|
|
|
465
477
|
/**
|
|
466
478
|
* Take yarg's unchecked object value and ensure it is a proper LH.screenEmulationSettings.
|
|
467
479
|
* @param {unknown} value
|
|
468
|
-
* @return {Partial<LH.ScreenEmulationSettings
|
|
480
|
+
* @return {Partial<LH.ScreenEmulationSettings>|undefined}
|
|
469
481
|
*/
|
|
470
482
|
function coerceScreenEmulation(value) {
|
|
483
|
+
if (value === undefined) return;
|
|
484
|
+
|
|
471
485
|
if (!isObjectOfUnknownValues(value)) {
|
|
472
486
|
throw new Error(`Invalid value: Argument 'screenEmulation' must be an object, specified per-property ('screenEmulation.width', 'screenEmulation.deviceScaleFactor', etc)`);
|
|
473
487
|
}
|
package/lighthouse-cli/run.js
CHANGED
|
@@ -232,6 +232,10 @@ async function runLighthouse(url, flags, config) {
|
|
|
232
232
|
let launchedChrome;
|
|
233
233
|
|
|
234
234
|
try {
|
|
235
|
+
if (url && flags.auditMode && !flags.gatherMode) {
|
|
236
|
+
log.warn('CLI', 'URL parameter is ignored if -A flag is used without -G flag');
|
|
237
|
+
}
|
|
238
|
+
|
|
235
239
|
const shouldGather = flags.gatherMode || flags.gatherMode === flags.auditMode;
|
|
236
240
|
const shouldUseLocalChrome = URL.isLikeLocalhost(flags.hostname);
|
|
237
241
|
if (shouldGather && shouldUseLocalChrome) {
|
|
@@ -254,7 +258,7 @@ async function runLighthouse(url, flags, config) {
|
|
|
254
258
|
// Runtime errors indicate something was *very* wrong with the page result.
|
|
255
259
|
// We don't want the user to have to parse the report to figure it out, so we'll still exit
|
|
256
260
|
// with an error code after we saved the results.
|
|
257
|
-
if (runnerResult
|
|
261
|
+
if (runnerResult?.lhr.runtimeError) {
|
|
258
262
|
const {runtimeError} = runnerResult.lhr;
|
|
259
263
|
return printErrorAndExit({
|
|
260
264
|
name: 'LHError',
|
|
@@ -36,6 +36,7 @@ const coreTestDefnsPath =
|
|
|
36
36
|
const runnerPaths = {
|
|
37
37
|
cli: '../lighthouse-runners/cli.js',
|
|
38
38
|
bundle: '../lighthouse-runners/bundle.js',
|
|
39
|
+
devtools: '../lighthouse-runners/devtools.js',
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -151,7 +152,7 @@ async function begin() {
|
|
|
151
152
|
},
|
|
152
153
|
'runner': {
|
|
153
154
|
default: 'cli',
|
|
154
|
-
choices: ['cli', 'bundle'],
|
|
155
|
+
choices: ['cli', 'bundle', 'devtools'],
|
|
155
156
|
describe: 'The method of running Lighthouse',
|
|
156
157
|
},
|
|
157
158
|
'tests-path': {
|
|
@@ -174,7 +175,8 @@ async function begin() {
|
|
|
174
175
|
|
|
175
176
|
// Augmenting yargs type with auto-camelCasing breaks in tsc@4.1.2 and @types/yargs@15.0.11,
|
|
176
177
|
// so for now cast to add yarg's camelCase properties to type.
|
|
177
|
-
const argv =
|
|
178
|
+
const argv =
|
|
179
|
+
/** @type {Awaited<typeof rawArgv> & CamelCasify<Awaited<typeof rawArgv>>} */ (rawArgv);
|
|
178
180
|
|
|
179
181
|
const jobs = Number.isFinite(argv.jobs) ? argv.jobs : undefined;
|
|
180
182
|
const retries = Number.isFinite(argv.retries) ? argv.retries : undefined;
|
|
@@ -133,7 +133,7 @@ async function internalRun(url, tmpPath, configJson, options) {
|
|
|
133
133
|
|
|
134
134
|
// There should either be both an error exitCode and a lhr.runtimeError or neither.
|
|
135
135
|
if (Boolean(exitCode) !== Boolean(lhr.runtimeError)) {
|
|
136
|
-
const runtimeErrorCode = lhr.runtimeError
|
|
136
|
+
const runtimeErrorCode = lhr.runtimeError?.code;
|
|
137
137
|
throw new ChildProcessError(`Lighthouse did not exit with an error correctly, exiting with ${exitCode} but with runtimeError '${runtimeErrorCode}'`, // eslint-disable-line max-len
|
|
138
138
|
localConsole.getLog());
|
|
139
139
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @fileoverview A runner that launches Chrome and executes Lighthouse via DevTools.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import {spawn} from 'child_process';
|
|
15
|
+
|
|
16
|
+
import {LH_ROOT} from '../../../../root.js';
|
|
17
|
+
|
|
18
|
+
const devtoolsDir =
|
|
19
|
+
process.env.DEVTOOLS_PATH || `${LH_ROOT}/.tmp/chromium-web-tests/devtools/devtools-frontend`;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} command
|
|
23
|
+
* @param {string[]} args
|
|
24
|
+
*/
|
|
25
|
+
async function spawnAndLog(command, args) {
|
|
26
|
+
let log = '';
|
|
27
|
+
|
|
28
|
+
/** @type {Promise<void>} */
|
|
29
|
+
const promise = new Promise((resolve, reject) => {
|
|
30
|
+
const spawnHandle = spawn(command, args);
|
|
31
|
+
spawnHandle.on('close', code => {
|
|
32
|
+
if (code === 0) resolve();
|
|
33
|
+
else reject(new Error(`Command exited with code ${code}`));
|
|
34
|
+
});
|
|
35
|
+
spawnHandle.on('error', reject);
|
|
36
|
+
spawnHandle.stdout.on('data', data => {
|
|
37
|
+
console.log(data.toString());
|
|
38
|
+
log += `STDOUT: ${data.toString()}`;
|
|
39
|
+
});
|
|
40
|
+
spawnHandle.stderr.on('data', data => {
|
|
41
|
+
console.log(data.toString());
|
|
42
|
+
log += `STDERR: ${data.toString()}`;
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
await promise;
|
|
46
|
+
|
|
47
|
+
return log;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @type {Promise<void>} */
|
|
51
|
+
let buildDevtoolsPromise;
|
|
52
|
+
/**
|
|
53
|
+
* Download/pull latest DevTools, build Lighthouse for DevTools, roll to DevTools, and build DevTools.
|
|
54
|
+
*/
|
|
55
|
+
async function buildDevtools() {
|
|
56
|
+
if (process.env.CI) return;
|
|
57
|
+
|
|
58
|
+
process.env.DEVTOOLS_PATH = devtoolsDir;
|
|
59
|
+
await spawnAndLog('bash', ['lighthouse-core/test/chromium-web-tests/download-devtools.sh']);
|
|
60
|
+
await spawnAndLog('bash', ['lighthouse-core/test/chromium-web-tests/roll-devtools.sh']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Launch Chrome and do a full Lighthouse run via DevTools.
|
|
65
|
+
* By default, the latest DevTools frontend is used (.tmp/chromium-web-tests/devtools/devtools-frontend)
|
|
66
|
+
* unless DEVTOOLS_PATH is set.
|
|
67
|
+
* CHROME_PATH determines which Chrome is used–otherwise the default is puppeteer's chrome binary.
|
|
68
|
+
* @param {string} url
|
|
69
|
+
* @param {LH.Config.Json=} configJson
|
|
70
|
+
* @param {{isDebug?: boolean}=} testRunnerOptions
|
|
71
|
+
* @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts, log: string}>}
|
|
72
|
+
*/
|
|
73
|
+
async function runLighthouse(url, configJson, testRunnerOptions = {}) {
|
|
74
|
+
if (!buildDevtoolsPromise) buildDevtoolsPromise = buildDevtools();
|
|
75
|
+
await buildDevtoolsPromise;
|
|
76
|
+
|
|
77
|
+
const outputDir = fs.mkdtempSync(os.tmpdir() + '/lh-smoke-cdt-runner-');
|
|
78
|
+
const args = [
|
|
79
|
+
'run-devtools',
|
|
80
|
+
url,
|
|
81
|
+
`--custom-devtools-frontend=file://${devtoolsDir}/out/Default/gen/front_end`,
|
|
82
|
+
'--output-dir', outputDir,
|
|
83
|
+
];
|
|
84
|
+
if (configJson) {
|
|
85
|
+
args.push('--config', JSON.stringify(configJson));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const log = await spawnAndLog('yarn', args);
|
|
89
|
+
const lhr = JSON.parse(fs.readFileSync(`${outputDir}/lhr-0.json`, 'utf-8'));
|
|
90
|
+
const artifacts = JSON.parse(fs.readFileSync(`${outputDir}/artifacts-0.json`, 'utf-8'));
|
|
91
|
+
|
|
92
|
+
if (testRunnerOptions.isDebug) {
|
|
93
|
+
console.log(`${url} results saved at ${outputDir}`);
|
|
94
|
+
} else {
|
|
95
|
+
fs.rmSync(outputDir, {recursive: true, force: true});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {lhr, artifacts, log};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
runLighthouse,
|
|
103
|
+
};
|
|
@@ -118,14 +118,15 @@ Smokehouse Frontends Lighthouse Runners
|
|
|
118
118
|
| | | | | <lhr | +--------------+
|
|
119
119
|
+------------+ | +-------+-------+ | | |
|
|
120
120
|
| ^ +-->+ bundle.js |
|
|
121
|
-
+------------+ | |
|
|
122
|
-
| | | |
|
|
123
|
-
| lib.js +----+ v
|
|
124
|
-
| | +--------+--------+
|
|
125
|
-
+------------+ | |
|
|
126
|
-
| report/assert |
|
|
127
|
-
| |
|
|
128
|
-
+-----------------+
|
|
121
|
+
+------------+ | | | | |
|
|
122
|
+
| | | | | +--------------+
|
|
123
|
+
| lib.js +----+ v |
|
|
124
|
+
| | +--------+--------+ |
|
|
125
|
+
+------------+ | | | +--------------+
|
|
126
|
+
| report/assert | | | |
|
|
127
|
+
| | +-->+ devtools.js |
|
|
128
|
+
+-----------------+ | |
|
|
129
|
+
+--------------+
|
|
129
130
|
```
|
|
130
131
|
|
|
131
132
|
### Smokehouse frontends
|
|
@@ -142,6 +143,7 @@ Smokehouse Frontends Lighthouse Runners
|
|
|
142
143
|
|
|
143
144
|
- `lighthouse-runners/cli.js` - the original test runner, exercising the Lighthouse CLI from command-line argument parsing to the results written to disk on completion.
|
|
144
145
|
- `lighthouse-runners/bundle.js` - a smoke test runner that operates on an already-bundled version of Lighthouse for end-to-end testing of that version.
|
|
146
|
+
- `lighthouse-runners/devtools.js` - a smoke test runner that operates on Lighthouse running from inside DevTools.
|
|
145
147
|
|
|
146
148
|
## Custom smoke tests (for plugins et al.)
|
|
147
149
|
|
|
@@ -162,7 +162,7 @@ function makeComparison(name, actualResult, expectedResult) {
|
|
|
162
162
|
*/
|
|
163
163
|
function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
164
164
|
const isFraggleRock = lhr.configSettings.channel === 'fraggle-rock-cli';
|
|
165
|
-
const isBundled = reportOptions
|
|
165
|
+
const isBundled = reportOptions?.isBundled;
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
168
|
* Lazily compute the Chrome version because some reports are explicitly asserting error conditions.
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* @property {Run[]} runs
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import
|
|
35
|
+
import assert from 'assert';
|
|
36
36
|
|
|
37
37
|
import log from 'lighthouse-logger';
|
|
38
38
|
|
|
@@ -244,7 +244,7 @@ function getShardedDefinitions(testDefns, shardArg) {
|
|
|
244
244
|
// eslint-disable-next-line max-len
|
|
245
245
|
const errorMessage = `'shard' must be of the form 'n/d' and n and d must be positive integers with 1 ≤ n ≤ d. Got '${shardArg}'`;
|
|
246
246
|
const match = /^(?<shardNumber>\d+)\/(?<shardTotal>\d+)$/.exec(shardArg);
|
|
247
|
-
assert(match
|
|
247
|
+
assert(match?.groups, errorMessage);
|
|
248
248
|
const shardNumber = Number(match.groups.shardNumber);
|
|
249
249
|
const shardTotal = Number(match.groups.shardTotal);
|
|
250
250
|
assert(shardNumber > 0 && Number.isInteger(shardNumber), errorMessage);
|
|
@@ -267,15 +267,14 @@ function getShardedDefinitions(testDefns, shardArg) {
|
|
|
267
267
|
shards.push(testDefns.slice(index, index + shardSize));
|
|
268
268
|
index += shardSize;
|
|
269
269
|
}
|
|
270
|
-
assert.
|
|
271
|
-
assert.
|
|
270
|
+
assert.strictEqual(shards.length, shardTotal);
|
|
271
|
+
assert.deepStrictEqual(shards.flat(), testDefns);
|
|
272
272
|
|
|
273
273
|
const shardDefns = shards[shardNumber - 1];
|
|
274
274
|
console.log(`In this shard (${shardArg}), running: ${shardDefns.map(d => d.id).join(' ')}\n`);
|
|
275
275
|
return shardDefns;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
279
278
|
export {
|
|
280
279
|
runSmokehouse,
|
|
281
280
|
getShardedDefinitions,
|
|
@@ -47,7 +47,7 @@ class AxeAudit extends Audit {
|
|
|
47
47
|
// If aXe reports an error, then bubble it up to the caller
|
|
48
48
|
const incomplete = artifacts.Accessibility.incomplete || [];
|
|
49
49
|
const incompleteResult = incomplete.find(result => result.id === this.meta.id);
|
|
50
|
-
if (incompleteResult
|
|
50
|
+
if (incompleteResult?.error) {
|
|
51
51
|
return {
|
|
52
52
|
score: null,
|
|
53
53
|
errorMessage: `axe-core Error: ${incompleteResult.error.message || 'Unknown error'}`,
|
|
@@ -58,8 +58,8 @@ class AxeAudit extends Audit {
|
|
|
58
58
|
const violations = artifacts.Accessibility.violations || [];
|
|
59
59
|
const failureCases = isInformative ? violations.concat(incomplete) : violations;
|
|
60
60
|
const rule = failureCases.find(result => result.id === this.meta.id);
|
|
61
|
-
const impact = rule
|
|
62
|
-
const tags = rule
|
|
61
|
+
const impact = rule?.impact;
|
|
62
|
+
const tags = rule?.tags;
|
|
63
63
|
|
|
64
64
|
// Handle absence of aXe failure results for informative rules as 'not applicable'
|
|
65
65
|
// This scenario indicates that no action is required by the web property owner
|
|
@@ -73,7 +73,7 @@ class AxeAudit extends Audit {
|
|
|
73
73
|
|
|
74
74
|
/** @type {LH.Audit.Details.Table['items']}>} */
|
|
75
75
|
let items = [];
|
|
76
|
-
if (rule
|
|
76
|
+
if (rule?.nodes) {
|
|
77
77
|
items = rule.nodes.map(axeNode => ({
|
|
78
78
|
node: {
|
|
79
79
|
...Audit.makeNodeItem(axeNode.node),
|
|
@@ -15,11 +15,11 @@ const i18n = require('../../lib/i18n/i18n.js');
|
|
|
15
15
|
|
|
16
16
|
const UIStrings = {
|
|
17
17
|
/** Title of an accesibility audit that evaluates if all object elements have an alt HTML attribute that describes their contents. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
18
|
-
title: '`<object>` elements have
|
|
18
|
+
title: '`<object>` elements have alternate text',
|
|
19
19
|
/** Title of an accesibility audit that evaluates if all object elements have an alt HTML attribute that describes their contents. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed. */
|
|
20
|
-
failureTitle: '`<object>` elements do not have
|
|
20
|
+
failureTitle: '`<object>` elements do not have alternate text',
|
|
21
21
|
/** Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
|
|
22
|
-
description: 'Screen readers cannot translate non-text content. Adding
|
|
22
|
+
description: 'Screen readers cannot translate non-text content. Adding alternate text to ' +
|
|
23
23
|
'`<object>` elements helps screen readers convey meaning to users. ' +
|
|
24
24
|
'[Learn more](https://web.dev/object-alt/).',
|
|
25
25
|
};
|
|
@@ -73,13 +73,28 @@ class Audit {
|
|
|
73
73
|
* considering a log-normal distribution governed by two control points (the 10th
|
|
74
74
|
* percentile value and the median value) and represents the percentage of sites that are
|
|
75
75
|
* greater than `value`.
|
|
76
|
+
*
|
|
77
|
+
* Score characteristics:
|
|
78
|
+
* - within [0, 1]
|
|
79
|
+
* - rounded to two digits
|
|
80
|
+
* - value must meet or beat a controlPoint value to meet or exceed its percentile score:
|
|
81
|
+
* - value > median will give a score < 0.5; value ≤ median will give a score ≥ 0.5.
|
|
82
|
+
* - value > p10 will give a score < 0.9; value ≤ p10 will give a score ≥ 0.9.
|
|
83
|
+
* - values < p10 will get a slight boost so a score of 1 is achievable by a
|
|
84
|
+
* `value` other than those close to 0. Scores of > ~0.99524 end up rounded to 1.
|
|
76
85
|
* @param {{median: number, p10: number}} controlPoints
|
|
77
86
|
* @param {number} value
|
|
78
87
|
* @return {number}
|
|
79
88
|
*/
|
|
80
89
|
static computeLogNormalScore(controlPoints, value) {
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
let percentile = statistics.getLogNormalScore(controlPoints, value);
|
|
91
|
+
// Add a boost to scores of 90+, linearly ramping from 0 at 0.9 to half a
|
|
92
|
+
// point (0.005) at 1. Expands scores in (0.9, 1] to (0.9, 1.005], so more top
|
|
93
|
+
// scores will be a perfect 1 after the two-digit `Math.floor()` rounding below.
|
|
94
|
+
if (percentile > 0.9) { // getLogNormalScore ensures `percentile` can't exceed 1.
|
|
95
|
+
percentile += 0.05 * (percentile - 0.9);
|
|
96
|
+
}
|
|
97
|
+
return Math.floor(percentile * 100) / 100;
|
|
83
98
|
}
|
|
84
99
|
|
|
85
100
|
/**
|
|
@@ -360,8 +375,8 @@ class Audit {
|
|
|
360
375
|
|
|
361
376
|
score,
|
|
362
377
|
scoreDisplayMode,
|
|
363
|
-
numericValue: numericProduct
|
|
364
|
-
numericUnit: numericProduct
|
|
378
|
+
numericValue: numericProduct?.numericValue,
|
|
379
|
+
numericUnit: numericProduct?.numericUnit,
|
|
365
380
|
|
|
366
381
|
displayValue: product.displayValue,
|
|
367
382
|
explanation: product.explanation,
|
|
@@ -109,7 +109,7 @@ class UnusedBytes extends Audit {
|
|
|
109
109
|
const gatherContext = artifacts.GatherContext;
|
|
110
110
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
111
111
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
112
|
-
const settings = context
|
|
112
|
+
const settings = context?.settings || {};
|
|
113
113
|
const simulatorOptions = {
|
|
114
114
|
devtoolsLog,
|
|
115
115
|
settings,
|
|
@@ -127,7 +127,7 @@ class DuplicatedJavascript extends ByteEfficiencyAudit {
|
|
|
127
127
|
*/
|
|
128
128
|
static async audit_(artifacts, networkRecords, context) {
|
|
129
129
|
const ignoreThresholdInBytes =
|
|
130
|
-
context.options
|
|
130
|
+
context.options?.ignoreThresholdInBytes || IGNORE_THRESHOLD_IN_BYTES;
|
|
131
131
|
const duplication =
|
|
132
132
|
await DuplicatedJavascript._getDuplicationGroupedByNodeModules(artifacts, context);
|
|
133
133
|
const mainDocumentRecord = NetworkAnalyzer.findOptionalMainDocument(networkRecords);
|
|
@@ -62,8 +62,8 @@ class Deprecations extends Audit {
|
|
|
62
62
|
const bundles = await JsBundles.request(artifacts, context);
|
|
63
63
|
|
|
64
64
|
let deprecations;
|
|
65
|
-
if (artifacts.InspectorIssues.
|
|
66
|
-
deprecations = artifacts.InspectorIssues.
|
|
65
|
+
if (artifacts.InspectorIssues.deprecationIssue.length) {
|
|
66
|
+
deprecations = artifacts.InspectorIssues.deprecationIssue
|
|
67
67
|
.map(deprecation => {
|
|
68
68
|
const {url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
69
69
|
const bundle = bundles.find(bundle => bundle.script.src === url);
|
|
@@ -58,7 +58,7 @@ class IssuesPanelEntries extends Audit {
|
|
|
58
58
|
static getMixedContentRow(mixedContentIssues) {
|
|
59
59
|
const requestUrls = new Set();
|
|
60
60
|
for (const issue of mixedContentIssues) {
|
|
61
|
-
const requestUrl =
|
|
61
|
+
const requestUrl = issue.request?.url || issue.mainResourceURL;
|
|
62
62
|
requestUrls.add(requestUrl);
|
|
63
63
|
}
|
|
64
64
|
return {
|
|
@@ -77,7 +77,7 @@ class IssuesPanelEntries extends Audit {
|
|
|
77
77
|
static getSameSiteCookieRow(sameSiteCookieIssues) {
|
|
78
78
|
const requestUrls = new Set();
|
|
79
79
|
for (const issue of sameSiteCookieIssues) {
|
|
80
|
-
const requestUrl = (issue.request
|
|
80
|
+
const requestUrl = (issue.request?.url) || issue.cookieUrl;
|
|
81
81
|
if (requestUrl) {
|
|
82
82
|
requestUrls.add(requestUrl);
|
|
83
83
|
}
|
|
@@ -102,7 +102,7 @@ class IssuesPanelEntries extends Audit {
|
|
|
102
102
|
static getBlockedByResponseRow(blockedByResponseIssues) {
|
|
103
103
|
const requestUrls = new Set();
|
|
104
104
|
for (const issue of blockedByResponseIssues) {
|
|
105
|
-
const requestUrl = issue.request
|
|
105
|
+
const requestUrl = issue.request?.url;
|
|
106
106
|
if (requestUrl) {
|
|
107
107
|
requestUrls.add(requestUrl);
|
|
108
108
|
}
|
|
@@ -161,19 +161,19 @@ class IssuesPanelEntries extends Audit {
|
|
|
161
161
|
/** @type LH.Audit.Details.TableItem[] */
|
|
162
162
|
const items = [];
|
|
163
163
|
|
|
164
|
-
if (issues.
|
|
165
|
-
items.push(this.getMixedContentRow(issues.
|
|
164
|
+
if (issues.mixedContentIssue.length) {
|
|
165
|
+
items.push(this.getMixedContentRow(issues.mixedContentIssue));
|
|
166
166
|
}
|
|
167
|
-
if (issues.
|
|
168
|
-
items.push(this.getSameSiteCookieRow(issues.
|
|
167
|
+
if (issues.sameSiteCookieIssue.length) {
|
|
168
|
+
items.push(this.getSameSiteCookieRow(issues.sameSiteCookieIssue));
|
|
169
169
|
}
|
|
170
|
-
if (issues.
|
|
171
|
-
items.push(this.getBlockedByResponseRow(issues.
|
|
170
|
+
if (issues.blockedByResponseIssue.length) {
|
|
171
|
+
items.push(this.getBlockedByResponseRow(issues.blockedByResponseIssue));
|
|
172
172
|
}
|
|
173
|
-
if (issues.
|
|
173
|
+
if (issues.heavyAdIssue.length) {
|
|
174
174
|
items.push({issueType: str_(UIStrings.issueTypeHeavyAds)});
|
|
175
175
|
}
|
|
176
|
-
const cspIssues = issues.
|
|
176
|
+
const cspIssues = issues.contentSecurityPolicyIssue.filter(issue => {
|
|
177
177
|
// kTrustedTypesSinkViolation and kTrustedTypesPolicyViolation aren't currently supported by the Issues panel
|
|
178
178
|
return issue.contentSecurityPolicyViolationType !== 'kTrustedTypesSinkViolation' &&
|
|
179
179
|
issue.contentSecurityPolicyViolationType !== 'kTrustedTypesPolicyViolation';
|
|
@@ -79,7 +79,7 @@ class FontDisplay extends Audit {
|
|
|
79
79
|
// Find the font-display value by matching a single token, optionally surrounded by whitespace,
|
|
80
80
|
// followed either by a semicolon or the end of a block.
|
|
81
81
|
const fontDisplayMatch = declaration.match(/font-display\s*:\s*(\w+)\s*(;|\})/);
|
|
82
|
-
const rawFontDisplay =
|
|
82
|
+
const rawFontDisplay = fontDisplayMatch?.[1] || '';
|
|
83
83
|
const hasPassingFontDisplay = passingFontDisplayRegex.test(rawFontDisplay);
|
|
84
84
|
const targetURLSet = hasPassingFontDisplay ? passingURLs : failingURLs;
|
|
85
85
|
|
|
@@ -169,7 +169,7 @@ class InstallableManifest extends Audit {
|
|
|
169
169
|
* If there is an argument value, get it.
|
|
170
170
|
* We only expect a `minimum-icon-size-in-pixels` errorArg[0] for two errorIds, currently.
|
|
171
171
|
*/
|
|
172
|
-
const value0 = err.errorArguments
|
|
172
|
+
const value0 = err.errorArguments?.length && err.errorArguments[0].value;
|
|
173
173
|
|
|
174
174
|
if (matchingString && err.errorArguments.length !== UIStringArguments.length) {
|
|
175
175
|
// Matching string, but have the incorrect number of arguments for the message.
|
|
@@ -87,7 +87,7 @@ class HTTPS extends Audit {
|
|
|
87
87
|
{key: 'resolution', itemType: 'text', text: str_(UIStrings.columnResolution)},
|
|
88
88
|
];
|
|
89
89
|
|
|
90
|
-
for (const details of artifacts.InspectorIssues.
|
|
90
|
+
for (const details of artifacts.InspectorIssues.mixedContentIssue) {
|
|
91
91
|
let item = items.find(item => item.url === details.insecureURL);
|
|
92
92
|
if (!item) {
|
|
93
93
|
item = {url: details.insecureURL};
|
|
@@ -62,7 +62,7 @@ class MaskableIcon extends Audit {
|
|
|
62
62
|
}
|
|
63
63
|
const maskableIconCheck = manifestValues.allChecks.find(i => i.id === 'hasMaskableIcon');
|
|
64
64
|
return {
|
|
65
|
-
score:
|
|
65
|
+
score: maskableIconCheck?.passing ? 1 : 0,
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -35,7 +35,7 @@ class MultiCheckAudit extends Audit {
|
|
|
35
35
|
allChecks: undefined,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
if (result.manifestValues
|
|
38
|
+
if (result.manifestValues?.allChecks) {
|
|
39
39
|
result.manifestValues.allChecks.forEach(check => {
|
|
40
40
|
detailsItem[check.id] = check.passing;
|
|
41
41
|
});
|
|
@@ -41,10 +41,10 @@ class NetworkRequests extends Audit {
|
|
|
41
41
|
undefined : (time - earliestStartTime) * 1000;
|
|
42
42
|
|
|
43
43
|
const results = records.map(record => {
|
|
44
|
-
const endTimeDeltaMs = record.lrStatistics
|
|
45
|
-
const TCPMs = record.lrStatistics
|
|
46
|
-
const requestMs = record.lrStatistics
|
|
47
|
-
const responseMs = record.lrStatistics
|
|
44
|
+
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
|
|
45
|
+
const TCPMs = record.lrStatistics?.TCPMs;
|
|
46
|
+
const requestMs = record.lrStatistics?.requestMs;
|
|
47
|
+
const responseMs = record.lrStatistics?.responseMs;
|
|
48
48
|
|
|
49
49
|
return {
|
|
50
50
|
url: URL.elideDataURI(record.url),
|
|
@@ -97,9 +97,8 @@ class PreloadFontsAudit extends Audit {
|
|
|
97
97
|
* @return {Promise<LH.Audit.Product>}
|
|
98
98
|
*/
|
|
99
99
|
static async audit() {
|
|
100
|
-
// Preload advice is
|
|
101
|
-
//
|
|
102
|
-
// See https://github.com/GoogleChrome/lighthouse/issues/11960 for more discussion.
|
|
100
|
+
// Preload advice is on hold until https://github.com/GoogleChrome/lighthouse/issues/11960
|
|
101
|
+
// is resolved.
|
|
103
102
|
return {score: 1, notApplicable: true};
|
|
104
103
|
}
|
|
105
104
|
}
|