lighthouse 9.5.0-dev.20220502 → 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.
- package/dist/report/bundle.esm.js +3 -1
- package/jest.config.js +1 -1
- package/lighthouse-cli/cli-flags.js +2 -2
- package/lighthouse-cli/run.js +6 -5
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +97 -22
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +2 -1
- package/lighthouse-core/audits/accessibility/listitem.js +4 -4
- package/lighthouse-core/computed/critical-request-chains.js +3 -0
- package/lighthouse-core/fraggle-rock/gather/driver.js +1 -1
- package/lighthouse-core/fraggle-rock/gather/session.js +8 -2
- package/lighthouse-core/gather/driver.js +1 -1
- package/lighthouse-core/gather/fetcher.js +34 -210
- package/lighthouse-core/gather/gather-runner.js +0 -11
- package/lighthouse-core/gather/gatherers/seo/robots-txt.js +0 -29
- package/lighthouse-core/gather/gatherers/source-maps.js +0 -1
- package/lighthouse-core/index.js +1 -1
- package/lighthouse-core/lib/stack-packs.js +4 -5
- package/lighthouse-core/lib/tracehouse/task-groups.js +1 -0
- package/package.json +5 -5
- package/report/clients/bundle.js +1 -0
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +6 -98
- package/report/test/renderer/report-renderer-axe-test.js +3 -15
- package/report/test-assets/faux-psi-template.html +3 -2
- package/report/test-assets/faux-psi.js +22 -1
- package/shared/localization/locales/en-US.json +3 -3
- package/shared/localization/locales/en-XL.json +3 -3
- package/types/externs.d.ts +2 -2
|
@@ -6108,4 +6108,6 @@ function renderReport(lhr, opts = {}) {
|
|
|
6108
6108
|
return rootEl;
|
|
6109
6109
|
}
|
|
6110
6110
|
|
|
6111
|
-
|
|
6111
|
+
const swapLocale = _ => {}; const format = _ => {};
|
|
6112
|
+
|
|
6113
|
+
export { DOM, ReportRenderer, ReportUIFeatures, format, renderReport, swapLocale };
|
package/jest.config.js
CHANGED
|
@@ -123,10 +123,10 @@ function getFlags(manualArgv, options = {}) {
|
|
|
123
123
|
type: 'boolean',
|
|
124
124
|
describe: 'Pause after page load to wait for permission to continue the run, evaluate `continueLighthouseRun` in the console to continue.',
|
|
125
125
|
},
|
|
126
|
-
'
|
|
126
|
+
'legacy-navigation': {
|
|
127
127
|
type: 'boolean',
|
|
128
128
|
default: false,
|
|
129
|
-
describe: '[
|
|
129
|
+
describe: '[DEPRECATED] Use the legacy navigation runner to gather results. Only use this if you are using a pre-10.0 custom Lighthouse config, or if Lighthouse unexpectedly fails after updating to 10.0. Please file a bug if you need this flag for Lighthouse to work.',
|
|
130
130
|
},
|
|
131
131
|
'additional-trace-categories': {
|
|
132
132
|
type: 'string',
|
package/lighthouse-cli/run.js
CHANGED
|
@@ -225,15 +225,16 @@ async function runLighthouse(url, flags, config) {
|
|
|
225
225
|
flags.port = launchedChrome.port;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
if (flags.
|
|
229
|
-
|
|
228
|
+
if (flags.legacyNavigation) {
|
|
229
|
+
log.warn('CLI', 'Legacy navigation CLI is deprecated');
|
|
230
|
+
flags.channel = 'legacy-navigation-cli';
|
|
230
231
|
} else {
|
|
231
232
|
flags.channel = 'cli';
|
|
232
233
|
}
|
|
233
234
|
|
|
234
|
-
const runnerResult = flags.
|
|
235
|
-
await lighthouse(url, flags, config) :
|
|
236
|
-
await lighthouse
|
|
235
|
+
const runnerResult = flags.legacyNavigation ?
|
|
236
|
+
await lighthouse.legacyNavigation(url, flags, config) :
|
|
237
|
+
await lighthouse(url, flags, config);
|
|
237
238
|
|
|
238
239
|
// If in gatherMode only, there will be no runnerResult.
|
|
239
240
|
if (runnerResult) {
|
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
42
|
-
* @param {{isDebug?: boolean, useFraggleRock?: boolean}
|
|
43
|
-
* @return {Promise<{lhr: LH.Result, artifacts: LH.Artifacts
|
|
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
|
|
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
|
};
|
|
@@ -82,8 +82,9 @@ async function internalRun(url, tmpPath, configJson, options) {
|
|
|
82
82
|
];
|
|
83
83
|
|
|
84
84
|
if (useFraggleRock) {
|
|
85
|
-
args.push('--fraggle-rock');
|
|
86
85
|
configJson = convertToFraggleRockConfig(configJson);
|
|
86
|
+
} else {
|
|
87
|
+
args.push('--legacy-navigation');
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// Config can be optionally provided.
|
|
@@ -15,13 +15,13 @@ const i18n = require('../../lib/i18n/i18n.js');
|
|
|
15
15
|
|
|
16
16
|
const UIStrings = {
|
|
17
17
|
/** Title of an accesibility audit that evaluates if any list item elements do not have list parent elements. This title is descriptive of the successful state and is shown to users when no user action is required. */
|
|
18
|
-
title: 'List items (`<li>`) are contained within `<ul>` or `<
|
|
18
|
+
title: 'List items (`<li>`) are contained within `<ul>`, `<ol>` or `<menu>` parent elements',
|
|
19
19
|
/** Title of an accesibility audit that evaluates if any list item elements do not have list parent elements. 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: 'List items (`<li>`) are not contained within `<ul
|
|
21
|
-
'or `<
|
|
20
|
+
failureTitle: 'List items (`<li>`) are not contained within `<ul>`, ' +
|
|
21
|
+
'`<ol>` or `<menu>` parent elements.',
|
|
22
22
|
/** 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. */
|
|
23
23
|
description: 'Screen readers require list items (`<li>`) to be contained within a ' +
|
|
24
|
-
'parent `<ul>` or `<
|
|
24
|
+
'parent `<ul>`, `<ol>` or `<menu>` to be announced properly. ' +
|
|
25
25
|
'[Learn more](https://dequeuniversity.com/rules/axe/4.4/listitem).',
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -117,6 +117,9 @@ class CriticalRequestChains {
|
|
|
117
117
|
// Ignore if some ancestor is not a critical request.
|
|
118
118
|
if (networkPath.some(r => !CriticalRequestChains.isCritical(r, mainResource))) return;
|
|
119
119
|
|
|
120
|
+
// Ignore non-network things (like data urls).
|
|
121
|
+
if (NetworkRequest.isNonNetworkRequest(node.record)) return;
|
|
122
|
+
|
|
120
123
|
addChain(networkPath);
|
|
121
124
|
}, getNextNodes);
|
|
122
125
|
|
|
@@ -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
|
|
77
|
+
this._fetcher = new Fetcher(this._session);
|
|
78
78
|
log.timeEnd(status);
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -96,7 +96,7 @@ class ProtocolSession {
|
|
|
96
96
|
/** @param {LH.Puppeteer.CDPSession} session */
|
|
97
97
|
const listener = session => callback(new ProtocolSession(session));
|
|
98
98
|
this._callbackMap.set(callback, listener);
|
|
99
|
-
this.
|
|
99
|
+
this._getConnection().on('sessionattached', listener);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
@@ -106,7 +106,7 @@ class ProtocolSession {
|
|
|
106
106
|
removeSessionAttachedListener(callback) {
|
|
107
107
|
const listener = this._callbackMap.get(callback);
|
|
108
108
|
if (!listener) return;
|
|
109
|
-
this.
|
|
109
|
+
this._getConnection().off('sessionattached', listener);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
@@ -171,6 +171,12 @@ class ProtocolSession {
|
|
|
171
171
|
this._session.removeAllListeners();
|
|
172
172
|
await this._session.detach();
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
_getConnection() {
|
|
176
|
+
const connection = this._session.connection();
|
|
177
|
+
if (!connection) throw new Error('Connection has been closed.');
|
|
178
|
+
return connection;
|
|
179
|
+
}
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
module.exports = ProtocolSession;
|
|
@@ -6,124 +6,55 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @fileoverview Fetcher is a utility for making requests
|
|
10
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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 {
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
throw new Error('Must call `enable` before using fetchResource');
|
|
120
|
-
}
|
|
46
|
+
async _fetchWithFetchApi(url) {
|
|
47
|
+
const response = await fetch(url);
|
|
121
48
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
49
|
+
let content = null;
|
|
50
|
+
try {
|
|
51
|
+
content = await response.text();
|
|
52
|
+
} catch {}
|
|
125
53
|
|
|
126
|
-
return
|
|
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
|
-
*
|
|
217
|
-
* @param {
|
|
218
|
-
* @param {
|
|
219
|
-
* @return {Promise<FetchResponse>}
|
|
124
|
+
* @template T
|
|
125
|
+
* @param {Promise<T>} promise
|
|
126
|
+
* @param {number} ms
|
|
220
127
|
*/
|
|
221
|
-
async
|
|
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
|
|
285
|
-
/** @type {Promise<never>} */
|
|
130
|
+
let timeoutHandle;
|
|
286
131
|
const timeoutPromise = new Promise((_, reject) => {
|
|
287
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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);
|
package/lighthouse-core/index.js
CHANGED
|
@@ -94,7 +94,7 @@ lighthouse.generateConfig = generateConfig;
|
|
|
94
94
|
lighthouse.getAuditList = Runner.getAuditList;
|
|
95
95
|
lighthouse.traceCategories = require('./gather/driver.js').traceCategories;
|
|
96
96
|
lighthouse.Audit = require('./audits/audit.js');
|
|
97
|
-
lighthouse.Gatherer = require('./gather/
|
|
97
|
+
lighthouse.Gatherer = require('./fraggle-rock/gather/base-gatherer.js');
|
|
98
98
|
|
|
99
99
|
// Explicit type reference (hidden by makeComputedArtifact) for d.ts export.
|
|
100
100
|
// TODO(esmodules): should be a workaround for module.export and can be removed when in esm.
|
|
@@ -19,11 +19,10 @@ const stackPacksToInclude = [
|
|
|
19
19
|
packId: 'wordpress',
|
|
20
20
|
requiredStacks: ['js:wordpress'],
|
|
21
21
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// },
|
|
22
|
+
{
|
|
23
|
+
packId: 'ezoic',
|
|
24
|
+
requiredStacks: ['js:ezoic'],
|
|
25
|
+
},
|
|
27
26
|
{
|
|
28
27
|
packId: 'drupal',
|
|
29
28
|
requiredStacks: ['js:drupal'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.5.0-dev.
|
|
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
|
|
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\"",
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"pako": "^2.0.3",
|
|
164
164
|
"preact": "^10.5.14",
|
|
165
165
|
"pretty-json-stringify": "^0.0.2",
|
|
166
|
-
"puppeteer": "
|
|
166
|
+
"puppeteer": "13.7.0",
|
|
167
167
|
"resolve": "^1.20.0",
|
|
168
168
|
"rollup": "^2.52.7",
|
|
169
169
|
"rollup-plugin-node-resolve": "^5.2.0",
|
|
@@ -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.
|
|
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",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
"open": "^8.4.0",
|
|
200
200
|
"parse-cache-control": "1.0.1",
|
|
201
201
|
"ps-list": "^8.0.0",
|
|
202
|
-
"puppeteer-core": "^
|
|
202
|
+
"puppeteer-core": "^13.7.0",
|
|
203
203
|
"robots-parser": "^3.0.0",
|
|
204
204
|
"semver": "^5.3.0",
|
|
205
205
|
"speedline-core": "^1.4.3",
|
package/report/clients/bundle.js
CHANGED
|
@@ -15,3 +15,4 @@ export {DOM} from '../renderer/dom.js';
|
|
|
15
15
|
export {ReportRenderer} from '../renderer/report-renderer.js';
|
|
16
16
|
export {ReportUIFeatures} from '../renderer/report-ui-features.js';
|
|
17
17
|
export {renderReport} from '../renderer/api.js';
|
|
18
|
+
export {swapLocale, format} from '../../shared/localization/i18n-module.js';
|
|
@@ -1,107 +1,15 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`ReportRendererAxe with aXe renders without axe violations
|
|
3
|
+
exports[`ReportRendererAxe with aXe renders without axe violations 1`] = `
|
|
4
4
|
Array [
|
|
5
5
|
Object {
|
|
6
|
-
"description": "Ensures every id attribute value is unique",
|
|
7
|
-
"help": "id attribute value must be unique",
|
|
8
|
-
"helpUrl": "https://dequeuniversity.com/rules/axe/4.4/duplicate-id?application=axeAPI",
|
|
9
6
|
"id": "duplicate-id",
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Object {
|
|
16
|
-
"data": "viewport",
|
|
17
|
-
"id": "duplicate-id",
|
|
18
|
-
"impact": "minor",
|
|
19
|
-
"message": "Document has multiple static elements with the same id attribute: viewport",
|
|
20
|
-
"relatedNodes": Array [
|
|
21
|
-
Object {
|
|
22
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--pass\\" id=\\"viewport\\">",
|
|
23
|
-
"target": Array [
|
|
24
|
-
"#seo > .lh-audit-group:nth-child(4) > .lh-clump--passed.lh-clump > .lh-audit--binary.lh-audit--pass.lh-audit:nth-child(2)",
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
Object {
|
|
28
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--pass\\" id=\\"viewport\\">",
|
|
29
|
-
"target": Array [
|
|
30
|
-
".lh-audit-group--pwa-optimized > .lh-audit--binary.lh-audit--pass.lh-audit:nth-child(6)",
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
"failureSummary": "Fix any of the following:
|
|
37
|
-
Document has multiple static elements with the same id attribute: viewport",
|
|
38
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--pass\\" id=\\"viewport\\">",
|
|
39
|
-
"impact": "minor",
|
|
40
|
-
"none": Array [],
|
|
41
|
-
"target": Array [
|
|
42
|
-
".lh-audit--binary.lh-audit--pass.lh-audit:nth-child(20)",
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
Object {
|
|
46
|
-
"all": Array [],
|
|
47
|
-
"any": Array [
|
|
48
|
-
Object {
|
|
49
|
-
"data": "image-alt",
|
|
50
|
-
"id": "duplicate-id",
|
|
51
|
-
"impact": "minor",
|
|
52
|
-
"message": "Document has multiple static elements with the same id attribute: image-alt",
|
|
53
|
-
"relatedNodes": Array [
|
|
54
|
-
Object {
|
|
55
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--fail\\" id=\\"image-alt\\">",
|
|
56
|
-
"target": Array [
|
|
57
|
-
".lh-audit-group--seo-content > .lh-audit--fail.lh-audit--binary.lh-audit:nth-child(3)",
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
"failureSummary": "Fix any of the following:
|
|
64
|
-
Document has multiple static elements with the same id attribute: image-alt",
|
|
65
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--fail\\" id=\\"image-alt\\">",
|
|
66
|
-
"impact": "minor",
|
|
67
|
-
"none": Array [],
|
|
68
|
-
"target": Array [
|
|
69
|
-
".lh-audit-group--a11y-names-labels > .lh-audit--fail.lh-audit--binary.lh-audit:nth-child(2)",
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
Object {
|
|
73
|
-
"all": Array [],
|
|
74
|
-
"any": Array [
|
|
75
|
-
Object {
|
|
76
|
-
"data": "document-title",
|
|
77
|
-
"id": "duplicate-id",
|
|
78
|
-
"impact": "minor",
|
|
79
|
-
"message": "Document has multiple static elements with the same id attribute: document-title",
|
|
80
|
-
"relatedNodes": Array [
|
|
81
|
-
Object {
|
|
82
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--pass\\" id=\\"document-title\\">",
|
|
83
|
-
"target": Array [
|
|
84
|
-
"#seo > .lh-audit-group:nth-child(4) > .lh-clump--passed.lh-clump > .lh-audit--binary.lh-audit--pass.lh-audit:nth-child(3)",
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
"failureSummary": "Fix any of the following:
|
|
7
|
+
"message": "Fix any of the following:
|
|
8
|
+
Document has multiple static elements with the same id attribute: viewport
|
|
9
|
+
Fix any of the following:
|
|
10
|
+
Document has multiple static elements with the same id attribute: image-alt
|
|
11
|
+
Fix any of the following:
|
|
91
12
|
Document has multiple static elements with the same id attribute: document-title",
|
|
92
|
-
"html": "<div class=\\"lh-audit lh-audit--binary lh-audit--pass\\" id=\\"document-title\\">",
|
|
93
|
-
"impact": "minor",
|
|
94
|
-
"none": Array [],
|
|
95
|
-
"target": Array [
|
|
96
|
-
".lh-audit--binary.lh-audit--pass.lh-audit:nth-child(13)",
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
"tags": Array [
|
|
101
|
-
"cat.parsing",
|
|
102
|
-
"wcag2a",
|
|
103
|
-
"wcag411",
|
|
104
|
-
],
|
|
105
13
|
},
|
|
106
14
|
]
|
|
107
15
|
`;
|
|
@@ -45,6 +45,8 @@ describe('ReportRendererAxe', () => {
|
|
|
45
45
|
'heading-order': {enabled: true},
|
|
46
46
|
'meta-viewport': {enabled: true},
|
|
47
47
|
'aria-treeitem-name': {enabled: true},
|
|
48
|
+
// TODO: re-enable. https://github.com/GoogleChrome/lighthouse/issues/13918
|
|
49
|
+
'color-contrast': {enabled: false},
|
|
48
50
|
},
|
|
49
51
|
};
|
|
50
52
|
|
|
@@ -75,21 +77,7 @@ describe('ReportRendererAxe', () => {
|
|
|
75
77
|
message: v.nodes.map((n) => n.failureSummary).join('\n'),
|
|
76
78
|
};
|
|
77
79
|
});
|
|
78
|
-
expect(axeSummary).
|
|
79
|
-
Array [
|
|
80
|
-
Object {
|
|
81
|
-
"id": "duplicate-id",
|
|
82
|
-
"message": "Fix any of the following:
|
|
83
|
-
Document has multiple static elements with the same id attribute: viewport
|
|
84
|
-
Fix any of the following:
|
|
85
|
-
Document has multiple static elements with the same id attribute: image-alt
|
|
86
|
-
Fix any of the following:
|
|
87
|
-
Document has multiple static elements with the same id attribute: document-title",
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
`);
|
|
91
|
-
|
|
92
|
-
expect(axeResults.violations).toMatchSnapshot();
|
|
80
|
+
expect(axeSummary).toMatchSnapshot();
|
|
93
81
|
},
|
|
94
82
|
// This test takes 10s on fast hardware, but can take longer in CI.
|
|
95
83
|
// https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
|
|
@@ -119,7 +119,7 @@ body {
|
|
|
119
119
|
border-width: 1px 0;
|
|
120
120
|
padding: 4px;
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
#reanalyze {
|
|
123
123
|
background-color: hsl(216deg 100% 50%);
|
|
124
124
|
border-radius: 4px;
|
|
125
125
|
color: white;
|
|
@@ -133,8 +133,9 @@ button {
|
|
|
133
133
|
</head>
|
|
134
134
|
<body >
|
|
135
135
|
<noscript>PSI requires JavaScript. Please enable.</noscript>
|
|
136
|
+
<button type=button id="translate">→ Español</button>
|
|
136
137
|
<div class="page">
|
|
137
|
-
<button type=button>Reanalyze</button>
|
|
138
|
+
<button type=button id="reanalyze">Reanalyze</button>
|
|
138
139
|
<div class="tabset">
|
|
139
140
|
<input type="radio" name="tabset" id="tab1" aria-controls="mobile" checked />
|
|
140
141
|
<label for="tab1">Mobile</label>
|
|
@@ -17,7 +17,12 @@ const wait = (ms = 100) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
17
17
|
(async function __initPsiReports__() {
|
|
18
18
|
renderLHReport();
|
|
19
19
|
|
|
20
|
-
document.querySelector('button')?.addEventListener('click', () => {
|
|
20
|
+
document.querySelector('button#reanalyze')?.addEventListener('click', () => {
|
|
21
|
+
renderLHReport();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
document.querySelector('button#translate')?.addEventListener('click', async () => {
|
|
25
|
+
await swapLhrLocale('es');
|
|
21
26
|
renderLHReport();
|
|
22
27
|
});
|
|
23
28
|
})();
|
|
@@ -82,6 +87,22 @@ async function renderLHReport() {
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @param {LH.Locale} locale
|
|
92
|
+
*/
|
|
93
|
+
async function swapLhrLocale(locale) {
|
|
94
|
+
const response = await fetch(`https://www.gstatic.com/pagespeed/insights/ui/locales/${locale}.json`);
|
|
95
|
+
/** @type {import('../../shared/localization/locales').LhlMessages} */
|
|
96
|
+
const lhlMessages = await response.json();
|
|
97
|
+
console.log(lhlMessages);
|
|
98
|
+
if (!lhlMessages) throw new Error(`could not fetch data for locale: ${locale}`);
|
|
99
|
+
|
|
100
|
+
lighthouseRenderer.format.registerLocaleData(locale, lhlMessages);
|
|
101
|
+
// @ts-expect-error LHR global
|
|
102
|
+
window.__LIGHTHOUSE_JSON__ = lighthouseRenderer.swapLocale(window.__LIGHTHOUSE_JSON__, locale)
|
|
103
|
+
.lhr;
|
|
104
|
+
}
|
|
105
|
+
|
|
85
106
|
|
|
86
107
|
/**
|
|
87
108
|
* Tweak the LHR to make the desktop and mobile reports easier to identify.
|
|
@@ -450,13 +450,13 @@
|
|
|
450
450
|
"message": "Lists contain only `<li>` elements and script supporting elements (`<script>` and `<template>`)."
|
|
451
451
|
},
|
|
452
452
|
"lighthouse-core/audits/accessibility/listitem.js | description": {
|
|
453
|
-
"message": "Screen readers require list items (`<li>`) to be contained within a parent `<ul>` or `<
|
|
453
|
+
"message": "Screen readers require list items (`<li>`) to be contained within a parent `<ul>`, `<ol>` or `<menu>` to be announced properly. [Learn more](https://dequeuniversity.com/rules/axe/4.4/listitem)."
|
|
454
454
|
},
|
|
455
455
|
"lighthouse-core/audits/accessibility/listitem.js | failureTitle": {
|
|
456
|
-
"message": "List items (`<li>`) are not contained within `<ul>` or `<
|
|
456
|
+
"message": "List items (`<li>`) are not contained within `<ul>`, `<ol>` or `<menu>` parent elements."
|
|
457
457
|
},
|
|
458
458
|
"lighthouse-core/audits/accessibility/listitem.js | title": {
|
|
459
|
-
"message": "List items (`<li>`) are contained within `<ul>` or `<
|
|
459
|
+
"message": "List items (`<li>`) are contained within `<ul>`, `<ol>` or `<menu>` parent elements"
|
|
460
460
|
},
|
|
461
461
|
"lighthouse-core/audits/accessibility/meta-refresh.js | description": {
|
|
462
462
|
"message": "Users do not expect a page to refresh automatically, and doing so will move focus back to the top of the page. This may create a frustrating or confusing experience. [Learn more](https://dequeuniversity.com/rules/axe/4.4/meta-refresh)."
|
|
@@ -450,13 +450,13 @@
|
|
|
450
450
|
"message": "L̂íŝt́ŝ ćôńt̂áîń ôńl̂ý `<li>` êĺêḿêńt̂ś âńd̂ śĉŕîṕt̂ śûṕp̂ór̂t́îńĝ él̂ém̂én̂t́ŝ (`<script>` án̂d́ `<template>`)."
|
|
451
451
|
},
|
|
452
452
|
"lighthouse-core/audits/accessibility/listitem.js | description": {
|
|
453
|
-
"message": "Ŝćr̂éêń r̂éâd́êŕŝ ŕêq́ûír̂é l̂íŝt́ ît́êḿŝ (`<li>`) t́ô b́ê ćôńt̂áîńêd́ ŵít̂h́îń â ṕâŕêńt̂ `<ul>` ór̂ `<
|
|
453
|
+
"message": "Ŝćr̂éêń r̂éâd́êŕŝ ŕêq́ûír̂é l̂íŝt́ ît́êḿŝ (`<li>`) t́ô b́ê ćôńt̂áîńêd́ ŵít̂h́îń â ṕâŕêńt̂ `<ul>`, `<ol>` ór̂ `<menu>` t́ô b́ê án̂ńôún̂ćêd́ p̂ŕôṕêŕl̂ý. [L̂éâŕn̂ ḿôŕê](https://dequeuniversity.com/rules/axe/4.4/listitem)."
|
|
454
454
|
},
|
|
455
455
|
"lighthouse-core/audits/accessibility/listitem.js | failureTitle": {
|
|
456
|
-
"message": "L̂íŝt́ ît́êḿŝ (`<li>`) ár̂é n̂ót̂ ćôńt̂áîńêd́ ŵít̂h́îń `<ul>` ôŕ `<
|
|
456
|
+
"message": "L̂íŝt́ ît́êḿŝ (`<li>`) ár̂é n̂ót̂ ćôńt̂áîńêd́ ŵít̂h́îń `<ul>`, `<ol>` ôŕ `<menu>` p̂ár̂én̂t́ êĺêḿêńt̂ś."
|
|
457
457
|
},
|
|
458
458
|
"lighthouse-core/audits/accessibility/listitem.js | title": {
|
|
459
|
-
"message": "L̂íŝt́ ît́êḿŝ (`<li>`) ár̂é ĉón̂t́âín̂éd̂ ẃît́ĥín̂ `<ul>` ór̂ `<
|
|
459
|
+
"message": "L̂íŝt́ ît́êḿŝ (`<li>`) ár̂é ĉón̂t́âín̂éd̂ ẃît́ĥín̂ `<ul>`, `<ol>` ór̂ `<menu>` ṕâŕêńt̂ él̂ém̂én̂t́ŝ"
|
|
460
460
|
},
|
|
461
461
|
"lighthouse-core/audits/accessibility/meta-refresh.js | description": {
|
|
462
462
|
"message": "Ûśêŕŝ d́ô ńôt́ êx́p̂éĉt́ â ṕâǵê t́ô ŕêf́r̂éŝh́ âút̂óm̂át̂íĉál̂ĺŷ, án̂d́ d̂óîńĝ śô ẃîĺl̂ ḿôv́ê f́ôćûś b̂áĉḱ t̂ó t̂h́ê t́ôṕ ôf́ t̂h́ê ṕâǵê. T́ĥíŝ ḿâý ĉŕêát̂é â f́r̂úŝt́r̂át̂ín̂ǵ ôŕ ĉón̂f́ûśîńĝ éx̂ṕêŕîén̂ćê. [Ĺêár̂ń m̂ór̂é](https://dequeuniversity.com/rules/axe/4.4/meta-refresh)."
|
package/types/externs.d.ts
CHANGED
|
@@ -149,8 +149,8 @@ export interface CliFlags extends Flags {
|
|
|
149
149
|
quiet: boolean;
|
|
150
150
|
/** A flag to print the normalized config for the given config and options, then exit. */
|
|
151
151
|
printConfig: boolean;
|
|
152
|
-
/** Use the
|
|
153
|
-
|
|
152
|
+
/** Use the legacy navigation runner to gather CLI results. */
|
|
153
|
+
legacyNavigation: boolean;
|
|
154
154
|
/** Path to the file where precomputed lantern data should be read from. */
|
|
155
155
|
precomputedLanternDataPath?: string;
|
|
156
156
|
/** Path to the file where precomputed lantern data should be written to. */
|