lighthouse 9.3.1 → 9.4.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/dist/report/bundle.esm.js +38 -24
- package/dist/report/flow.js +5 -5
- package/dist/report/standalone.js +9 -9
- package/flow-report/src/topbar.tsx +1 -1
- package/flow-report/src/wrappers/report.tsx +2 -1
- package/flow-report/test/topbar-test.tsx +1 -1
- package/lighthouse-cli/bin.js +1 -4
- package/lighthouse-cli/run.js +1 -1
- package/lighthouse-cli/test/smokehouse/core-tests.js +4 -5
- package/lighthouse-cli/test/smokehouse/readme.md +4 -0
- package/lighthouse-cli/test/smokehouse/report-assert.js +36 -0
- package/lighthouse-core/audits/autocomplete.js +34 -37
- package/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js +1 -0
- package/lighthouse-core/audits/seo/hreflang.js +2 -17
- package/lighthouse-core/config/default-config.js +2 -2
- package/lighthouse-core/fraggle-rock/config/default-config.js +3 -3
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +50 -31
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +4 -5
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +4 -5
- package/lighthouse-core/fraggle-rock/user-flow.js +8 -6
- package/lighthouse-core/gather/driver/navigation.js +29 -10
- package/lighthouse-core/gather/driver/network-monitor.js +8 -6
- package/lighthouse-core/gather/driver/prepare.js +13 -7
- package/lighthouse-core/gather/driver/wait-for-condition.js +11 -4
- package/lighthouse-core/gather/gather-runner.js +1 -1
- package/lighthouse-core/gather/gatherers/full-page-screenshot.js +35 -9
- package/lighthouse-core/gather/gatherers/inputs.js +113 -0
- package/lighthouse-core/index.js +3 -3
- package/lighthouse-core/lib/page-functions.js +11 -1
- package/lighthouse-core/lib/sentry.js +39 -24
- package/lighthouse-core/runner.js +79 -52
- package/lighthouse-core/util-commonjs.js +4 -0
- package/package.json +2 -3
- package/readme.md +1 -1
- package/report/assets/templates.html +2 -5
- package/report/renderer/components.js +3 -3
- package/report/renderer/dom.js +1 -3
- package/report/renderer/report-ui-features.js +25 -17
- package/report/renderer/topbar-features.js +5 -1
- package/report/renderer/util.js +4 -0
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +107 -0
- package/report/test/renderer/report-renderer-axe-test.js +38 -17
- package/report/test-assets/faux-psi.js +2 -1
- package/report/types/report-renderer.d.ts +21 -2
- package/shared/localization/locales/en-US.json +7 -1
- package/shared/localization/locales/en-XL.json +7 -1
- package/third-party/axe/LICENSE +362 -0
- package/third-party/axe/README.md +6 -0
- package/third-party/axe/valid-langs.js +110 -0
- package/third-party/snyk/snapshot.json +1 -0
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +14 -14
- package/types/global-lh.d.ts +1 -0
- package/types/parse-cache-control/index.d.ts +1 -0
- package/types/smokehouse.d.ts +3 -0
- package/changelog.md +0 -5803
- package/lighthouse-core/gather/gatherers/form-elements.js +0 -113
- package/lighthouse-core/scripts/package.json +0 -4
|
@@ -28,14 +28,16 @@ const {version: lighthouseVersion} = require('../package.json');
|
|
|
28
28
|
class Runner {
|
|
29
29
|
/**
|
|
30
30
|
* @template {LH.Config.Config | LH.Config.FRConfig} TConfig
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {{config: TConfig, computedCache: Map<string, ArbitraryEqualityMap
|
|
31
|
+
* @param {LH.Artifacts} artifacts
|
|
32
|
+
* @param {{config: TConfig, driverMock?: Driver, computedCache: Map<string, ArbitraryEqualityMap>}} options
|
|
33
33
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
34
34
|
*/
|
|
35
|
-
static async
|
|
36
|
-
const
|
|
35
|
+
static async audit(artifacts, options) {
|
|
36
|
+
const {config, computedCache} = options;
|
|
37
|
+
const settings = config.settings;
|
|
38
|
+
|
|
37
39
|
try {
|
|
38
|
-
const runnerStatus = {msg: '
|
|
40
|
+
const runnerStatus = {msg: 'Audit phase', id: 'lh:runner:audit'};
|
|
39
41
|
log.time(runnerStatus, 'verbose');
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -44,24 +46,14 @@ class Runner {
|
|
|
44
46
|
*/
|
|
45
47
|
const lighthouseRunWarnings = [];
|
|
46
48
|
|
|
47
|
-
const sentryContext = Sentry.getContext();
|
|
48
|
-
Sentry.captureBreadcrumb({
|
|
49
|
-
message: 'Run started',
|
|
50
|
-
category: 'lifecycle',
|
|
51
|
-
data: sentryContext?.extra,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const artifacts = await this.gatherAndManageArtifacts(gatherFn, runOpts);
|
|
55
|
-
|
|
56
49
|
// Potentially quit early
|
|
57
50
|
if (settings.gatherMode && !settings.auditMode) return;
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
if (!runOpts.config.audits) {
|
|
52
|
+
if (!config.audits) {
|
|
61
53
|
throw new Error('No audits to evaluate.');
|
|
62
54
|
}
|
|
63
|
-
const auditResultsById = await Runner._runAudits(settings,
|
|
64
|
-
lighthouseRunWarnings,
|
|
55
|
+
const auditResultsById = await Runner._runAudits(settings, config.audits, artifacts,
|
|
56
|
+
lighthouseRunWarnings, computedCache);
|
|
65
57
|
|
|
66
58
|
// LHR construction phase
|
|
67
59
|
const resultsStatus = {msg: 'Generating results...', id: 'lh:runner:generate'};
|
|
@@ -82,8 +74,8 @@ class Runner {
|
|
|
82
74
|
|
|
83
75
|
/** @type {Record<string, LH.RawIcu<LH.Result.Category>>} */
|
|
84
76
|
let categories = {};
|
|
85
|
-
if (
|
|
86
|
-
categories = ReportScoring.scoreAllCategories(
|
|
77
|
+
if (config.categories) {
|
|
78
|
+
categories = ReportScoring.scoreAllCategories(config.categories, auditResultsById);
|
|
87
79
|
}
|
|
88
80
|
|
|
89
81
|
log.timeEnd(resultsStatus);
|
|
@@ -108,7 +100,7 @@ class Runner {
|
|
|
108
100
|
audits: auditResultsById,
|
|
109
101
|
configSettings: settings,
|
|
110
102
|
categories,
|
|
111
|
-
categoryGroups:
|
|
103
|
+
categoryGroups: config.groups || undefined,
|
|
112
104
|
stackPacks: stackPacks.getStackPacks(artifacts.Stacks),
|
|
113
105
|
timing: this._getTiming(artifacts),
|
|
114
106
|
i18n: {
|
|
@@ -134,12 +126,7 @@ class Runner {
|
|
|
134
126
|
|
|
135
127
|
return {lhr, artifacts, report};
|
|
136
128
|
} catch (err) {
|
|
137
|
-
|
|
138
|
-
if (err.friendlyMessage) {
|
|
139
|
-
err.friendlyMessage = format.getFormatted(err.friendlyMessage, settings.locale);
|
|
140
|
-
}
|
|
141
|
-
await Sentry.captureException(err, {level: 'fatal'});
|
|
142
|
-
throw err;
|
|
129
|
+
throw Runner.createRunnerError(err, settings);
|
|
143
130
|
}
|
|
144
131
|
}
|
|
145
132
|
|
|
@@ -150,38 +137,72 @@ class Runner {
|
|
|
150
137
|
*
|
|
151
138
|
* @template {LH.Config.Config | LH.Config.FRConfig} TConfig
|
|
152
139
|
* @param {(runnerData: {config: TConfig, driverMock?: Driver}) => Promise<LH.Artifacts>} gatherFn
|
|
153
|
-
* @param {{config: TConfig, driverMock?: Driver}} options
|
|
140
|
+
* @param {{config: TConfig, driverMock?: Driver, computedCache: Map<string, ArbitraryEqualityMap>}} options
|
|
154
141
|
* @return {Promise<LH.Artifacts>}
|
|
155
142
|
*/
|
|
156
|
-
static async
|
|
143
|
+
static async gather(gatherFn, options) {
|
|
157
144
|
const settings = options.config.settings;
|
|
158
145
|
|
|
159
|
-
// Gather phase
|
|
160
146
|
// Either load saved artifacts from disk or from the browser.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!requestedUrl) {
|
|
169
|
-
throw new Error('Cannot run audit mode on empty URL');
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
artifacts = await gatherFn({
|
|
173
|
-
config: options.config,
|
|
174
|
-
driverMock: options.driverMock,
|
|
147
|
+
try {
|
|
148
|
+
const sentryContext = Sentry.getContext();
|
|
149
|
+
Sentry.captureBreadcrumb({
|
|
150
|
+
message: 'Run started',
|
|
151
|
+
category: 'lifecycle',
|
|
152
|
+
data: sentryContext,
|
|
175
153
|
});
|
|
176
154
|
|
|
177
|
-
|
|
178
|
-
|
|
155
|
+
/** @type {LH.Artifacts} */
|
|
156
|
+
let artifacts;
|
|
157
|
+
if (settings.auditMode && !settings.gatherMode) {
|
|
158
|
+
// No browser required, just load the artifacts from disk.
|
|
179
159
|
const path = this._getDataSavePath(settings);
|
|
180
|
-
|
|
160
|
+
artifacts = assetSaver.loadArtifacts(path);
|
|
161
|
+
const requestedUrl = artifacts.URL.requestedUrl;
|
|
162
|
+
|
|
163
|
+
if (!requestedUrl) {
|
|
164
|
+
throw new Error('Cannot run audit mode on empty URL');
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
const runnerStatus = {msg: 'Gather phase', id: 'lh:runner:gather'};
|
|
168
|
+
log.time(runnerStatus, 'verbose');
|
|
169
|
+
|
|
170
|
+
artifacts = await gatherFn({
|
|
171
|
+
config: options.config,
|
|
172
|
+
driverMock: options.driverMock,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
log.timeEnd(runnerStatus);
|
|
176
|
+
|
|
177
|
+
// If `gather` is run multiple times before `audit`, the timing entries for each `gather` can pollute one another.
|
|
178
|
+
// We need to clear the timing entries at the end of gathering.
|
|
179
|
+
// Set artifacts.Timing again to ensure lh:runner:gather is included.
|
|
180
|
+
artifacts.Timing = log.takeTimeEntries();
|
|
181
|
+
|
|
182
|
+
// -G means save these to disk (e.g. ./latest-run).
|
|
183
|
+
if (settings.gatherMode) {
|
|
184
|
+
const path = this._getDataSavePath(settings);
|
|
185
|
+
await assetSaver.saveArtifacts(artifacts, path);
|
|
186
|
+
}
|
|
181
187
|
}
|
|
188
|
+
|
|
189
|
+
return artifacts;
|
|
190
|
+
} catch (err) {
|
|
191
|
+
throw Runner.createRunnerError(err, settings);
|
|
182
192
|
}
|
|
193
|
+
}
|
|
183
194
|
|
|
184
|
-
|
|
195
|
+
/**
|
|
196
|
+
* @param {any} err
|
|
197
|
+
* @param {LH.Config.Settings} settings
|
|
198
|
+
*/
|
|
199
|
+
static createRunnerError(err, settings) {
|
|
200
|
+
// i18n LighthouseError strings.
|
|
201
|
+
if (err.friendlyMessage) {
|
|
202
|
+
err.friendlyMessage = format.getFormatted(err.friendlyMessage, settings.locale);
|
|
203
|
+
}
|
|
204
|
+
Sentry.captureException(err, {level: 'fatal'});
|
|
205
|
+
return err;
|
|
185
206
|
}
|
|
186
207
|
|
|
187
208
|
/**
|
|
@@ -197,8 +218,11 @@ class Runner {
|
|
|
197
218
|
const timingEntriesKeyValues = [
|
|
198
219
|
...timingEntriesFromArtifacts,
|
|
199
220
|
...timingEntriesFromRunner,
|
|
200
|
-
|
|
201
|
-
|
|
221
|
+
].map(entry => /** @type {[string, PerformanceEntry]} */ ([
|
|
222
|
+
// As entries can share a name and start time, dedupe based on the name, startTime and duration
|
|
223
|
+
`${entry.startTime}-${entry.name}-${entry.duration}`,
|
|
224
|
+
entry,
|
|
225
|
+
]));
|
|
202
226
|
const timingEntries = Array.from(new Map(timingEntriesKeyValues).values())
|
|
203
227
|
// Truncate timestamps to hundredths of a millisecond saves ~4KB. No need for microsecond
|
|
204
228
|
// resolution.
|
|
@@ -212,8 +236,11 @@ class Runner {
|
|
|
212
236
|
entryType: entry.entryType,
|
|
213
237
|
};
|
|
214
238
|
}).sort((a, b) => a.startTime - b.startTime);
|
|
215
|
-
const
|
|
216
|
-
|
|
239
|
+
const gatherEntry = timingEntries.find(e => e.name === 'lh:runner:gather');
|
|
240
|
+
const auditEntry = timingEntries.find(e => e.name === 'lh:runner:audit');
|
|
241
|
+
const gatherTiming = gatherEntry?.duration || 0;
|
|
242
|
+
const auditTiming = auditEntry?.duration || 0;
|
|
243
|
+
return {entries: timingEntries, total: gatherTiming + auditTiming};
|
|
217
244
|
}
|
|
218
245
|
|
|
219
246
|
/**
|
|
@@ -630,6 +630,10 @@ const UIStrings = {
|
|
|
630
630
|
thirdPartyResourcesLabel: 'Show 3rd-party resources',
|
|
631
631
|
/** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */
|
|
632
632
|
viewTreemapLabel: 'View Treemap',
|
|
633
|
+
/** This label is for a button that will show the user a trace of the page. */
|
|
634
|
+
viewTraceLabel: 'View Trace',
|
|
635
|
+
/** This label is for a button that will show the user a trace of the page. */
|
|
636
|
+
viewOriginalTraceLabel: 'View Original Trace',
|
|
633
637
|
|
|
634
638
|
/** Option in a dropdown menu that opens a small, summary report in a print dialog. */
|
|
635
639
|
dropdownPrintSummary: 'Print Summary',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.4.0",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -121,7 +121,6 @@
|
|
|
121
121
|
"@types/lodash.set": "^4.3.6",
|
|
122
122
|
"@types/node": "*",
|
|
123
123
|
"@types/pako": "^1.0.1",
|
|
124
|
-
"@types/raven": "^2.5.1",
|
|
125
124
|
"@types/resize-observer-browser": "^0.1.1",
|
|
126
125
|
"@types/semver": "^5.5.0",
|
|
127
126
|
"@types/tabulator-tables": "^4.9.1",
|
|
@@ -183,6 +182,7 @@
|
|
|
183
182
|
"webtreemap-cdt": "^3.2.1"
|
|
184
183
|
},
|
|
185
184
|
"dependencies": {
|
|
185
|
+
"@sentry/node": "^6.17.4",
|
|
186
186
|
"axe-core": "4.3.5",
|
|
187
187
|
"chrome-launcher": "^0.15.0",
|
|
188
188
|
"configstore": "^5.0.1",
|
|
@@ -204,7 +204,6 @@
|
|
|
204
204
|
"open": "^8.4.0",
|
|
205
205
|
"parse-cache-control": "1.0.1",
|
|
206
206
|
"ps-list": "^8.0.0",
|
|
207
|
-
"raven": "^2.2.1",
|
|
208
207
|
"robots-parser": "^3.0.0",
|
|
209
208
|
"semver": "^5.3.0",
|
|
210
209
|
"speedline-core": "^1.4.3",
|
package/readme.md
CHANGED
|
@@ -313,7 +313,7 @@ yarn type-check
|
|
|
313
313
|
|
|
314
314
|
### Docs
|
|
315
315
|
|
|
316
|
-
Some of our docs have tests that run only in CI by default. If you end up needing to modify our documentation, you'll need to run `yarn test-docs` locally to make sure they pass.
|
|
316
|
+
Some of our docs have tests that run only in CI by default. If you end up needing to modify our documentation, you'll need to run `yarn build-pack && yarn test-docs` locally to make sure they pass.
|
|
317
317
|
|
|
318
318
|
**Additional Dependencies**
|
|
319
319
|
- `brew install jq`
|
|
@@ -210,8 +210,8 @@ limitations under the License.
|
|
|
210
210
|
<div class="lh-scores-wrapper">
|
|
211
211
|
<div class="lh-scores-container">
|
|
212
212
|
<div class="lh-pyro">
|
|
213
|
-
<div class="lh-before"></div>
|
|
214
|
-
<div class="lh-after"></div>
|
|
213
|
+
<div class="lh-pyro-before"></div>
|
|
214
|
+
<div class="lh-pyro-after"></div>
|
|
215
215
|
</div>
|
|
216
216
|
</div>
|
|
217
217
|
</div>
|
|
@@ -463,9 +463,6 @@ limitations under the License.
|
|
|
463
463
|
animation-delay: 2.25s, 2.25s, 2.25s;
|
|
464
464
|
animation-duration: 1.25s, 1.25s, 6.25s;
|
|
465
465
|
}
|
|
466
|
-
.lh-fireworks-paused .lh-pyro > div {
|
|
467
|
-
animation-play-state: paused;
|
|
468
|
-
}
|
|
469
466
|
|
|
470
467
|
@keyframes bang {
|
|
471
468
|
to {
|
|
@@ -349,7 +349,7 @@ function createGaugePwaComponent(dom) {
|
|
|
349
349
|
function createHeadingComponent(dom) {
|
|
350
350
|
const el0 = dom.createFragment();
|
|
351
351
|
const el1 = dom.createElement('style');
|
|
352
|
-
el1.append('\n /* CSS Fireworks. Originally by Eddie Lin\n https://codepen.io/paulirish/pen/yEVMbP\n */\n .lh-pyro {\n display: none;\n z-index: 1;\n pointer-events: none;\n }\n .lh-score100 .lh-pyro {\n display: block;\n }\n .lh-score100 .lh-lighthouse stop:first-child {\n stop-color: hsla(200, 12%, 95%, 0);\n }\n .lh-score100 .lh-lighthouse stop:last-child {\n stop-color: hsla(65, 81%, 76%, 1);\n }\n\n .lh-pyro > .lh-pyro-before, .lh-pyro > .lh-pyro-after {\n position: absolute;\n width: 5px;\n height: 5px;\n border-radius: 2.5px;\n box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff;\n animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards;\n animation-delay: 1s, 1s, 1s;\n }\n\n .lh-pyro > .lh-pyro-after {\n animation-delay: 2.25s, 2.25s, 2.25s;\n animation-duration: 1.25s, 1.25s, 6.25s;\n }\n
|
|
352
|
+
el1.append('\n /* CSS Fireworks. Originally by Eddie Lin\n https://codepen.io/paulirish/pen/yEVMbP\n */\n .lh-pyro {\n display: none;\n z-index: 1;\n pointer-events: none;\n }\n .lh-score100 .lh-pyro {\n display: block;\n }\n .lh-score100 .lh-lighthouse stop:first-child {\n stop-color: hsla(200, 12%, 95%, 0);\n }\n .lh-score100 .lh-lighthouse stop:last-child {\n stop-color: hsla(65, 81%, 76%, 1);\n }\n\n .lh-pyro > .lh-pyro-before, .lh-pyro > .lh-pyro-after {\n position: absolute;\n width: 5px;\n height: 5px;\n border-radius: 2.5px;\n box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff;\n animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards;\n animation-delay: 1s, 1s, 1s;\n }\n\n .lh-pyro > .lh-pyro-after {\n animation-delay: 2.25s, 2.25s, 2.25s;\n animation-duration: 1.25s, 1.25s, 6.25s;\n }\n\n @keyframes bang {\n to {\n box-shadow: -70px -115.67px #47ebbc, -28px -99.67px #eb47a4, 58px -31.67px #7eeb47, 13px -141.67px #eb47c5, -19px 6.33px #7347eb, -2px -74.67px #ebd247, 24px -151.67px #eb47e0, 57px -138.67px #b4eb47, -51px -104.67px #479eeb, 62px 8.33px #ebcf47, -93px 0.33px #d547eb, -16px -118.67px #47bfeb, 53px -84.67px #47eb83, 66px -57.67px #eb47bf, -93px -65.67px #91eb47, 30px -13.67px #86eb47, -2px -59.67px #83eb47, -44px 1.33px #eb47eb, 61px -58.67px #47eb73, 5px -22.67px #47e8eb, -66px -28.67px #ebe247, 42px -123.67px #eb5547, -75px 26.33px #7beb47, 15px -52.67px #a147eb, 36px -51.67px #eb8347, -38px -12.67px #eb5547, -46px -59.67px #47eb81, 78px -114.67px #eb47ba, 15px -156.67px #eb47bf, -36px 1.33px #eb4783, -72px -86.67px #eba147, 31px -46.67px #ebe247, -68px 29.33px #47e2eb, -55px 19.33px #ebe047, -56px 27.33px #4776eb, -13px -91.67px #eb5547, -47px -138.67px #47ebc7, -18px -96.67px #eb47ac, 11px -88.67px #4783eb, -67px -28.67px #47baeb, 53px 10.33px #ba47eb, 11px 19.33px #5247eb, -5px -11.67px #eb4791, -68px -4.67px #47eba7, 95px -37.67px #eb478b, -67px -162.67px #eb5d47, -54px -120.67px #eb6847, 49px -12.67px #ebe047, 88px 8.33px #47ebda, 97px 33.33px #eb8147, 6px -71.67px #ebbc47;\n }\n }\n @keyframes gravity {\n to {\n transform: translateY(80px);\n opacity: 0;\n }\n }\n @keyframes position {\n 0%, 19.9% {\n margin-top: 4%;\n margin-left: 47%;\n }\n 20%, 39.9% {\n margin-top: 7%;\n margin-left: 30%;\n }\n 40%, 59.9% {\n margin-top: 6%;\n margin-left: 70%;\n }\n 60%, 79.9% {\n margin-top: 3%;\n margin-left: 20%;\n }\n 80%, 99.9% {\n margin-top: 3%;\n margin-left: 80%;\n }\n }\n ');
|
|
353
353
|
el0.append(el1);
|
|
354
354
|
const el2 = dom.createElement('div', 'lh-header-container');
|
|
355
355
|
const el3 = dom.createElement('div', 'lh-scores-wrapper-placeholder');
|
|
@@ -450,8 +450,8 @@ function createScoresWrapperComponent(dom) {
|
|
|
450
450
|
const el2 = dom.createElement('div', 'lh-scores-wrapper');
|
|
451
451
|
const el3 = dom.createElement('div', 'lh-scores-container');
|
|
452
452
|
const el4 = dom.createElement('div', 'lh-pyro');
|
|
453
|
-
const el5 = dom.createElement('div', 'lh-before');
|
|
454
|
-
const el6 = dom.createElement('div', 'lh-after');
|
|
453
|
+
const el5 = dom.createElement('div', 'lh-pyro-before');
|
|
454
|
+
const el6 = dom.createElement('div', 'lh-pyro-after');
|
|
455
455
|
el4.append(' ', el5, ' ', el6, ' ');
|
|
456
456
|
el3.append(' ', el4, ' ');
|
|
457
457
|
el2.append(' ', el3, ' ');
|
package/report/renderer/dom.js
CHANGED
|
@@ -293,10 +293,8 @@ export class DOM {
|
|
|
293
293
|
* @param {string} filename
|
|
294
294
|
*/
|
|
295
295
|
saveFile(blob, filename) {
|
|
296
|
-
const ext = blob.type.match('json') ? '.json' : '.html';
|
|
297
|
-
|
|
298
296
|
const a = this.createElement('a');
|
|
299
|
-
a.download =
|
|
297
|
+
a.download = filename;
|
|
300
298
|
this.safelySetBlobHref(a, blob);
|
|
301
299
|
this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM.
|
|
302
300
|
a.click();
|
|
@@ -70,12 +70,13 @@ export class ReportUIFeatures {
|
|
|
70
70
|
this._setupThirdPartyFilter();
|
|
71
71
|
this._setupElementScreenshotOverlay(this._dom.rootEl);
|
|
72
72
|
|
|
73
|
-
let turnOffTheLights = false;
|
|
74
73
|
// Do not query the system preferences for DevTools - DevTools should only apply dark theme
|
|
75
74
|
// if dark is selected in the settings panel.
|
|
76
|
-
|
|
75
|
+
// TODO: set `disableDarkMode` in devtools and delete this special case.
|
|
76
|
+
const disableDarkMode = this._dom.isDevTools() ||
|
|
77
|
+
this._opts.disableDarkMode || this._opts.disableAutoDarkModeAndFireworks;
|
|
77
78
|
if (!disableDarkMode && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
78
|
-
|
|
79
|
+
toggleDarkTheme(this._dom, true);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// Fireworks!
|
|
@@ -85,13 +86,12 @@ export class ReportUIFeatures {
|
|
|
85
86
|
const cat = lhr.categories[id];
|
|
86
87
|
return cat && cat.score === 1;
|
|
87
88
|
});
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
const disableFireworks =
|
|
90
|
+
this._opts.disableFireworks || this._opts.disableAutoDarkModeAndFireworks;
|
|
91
|
+
if (scoresAll100 && !disableFireworks) {
|
|
90
92
|
this._enableFireworks();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (turnOffTheLights) {
|
|
94
|
-
toggleDarkTheme(this._dom, true);
|
|
93
|
+
// If dark mode is allowed, force it on because it looks so much better.
|
|
94
|
+
if (!disableDarkMode) toggleDarkTheme(this._dom, true);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Show the metric descriptions by default when there is an error.
|
|
@@ -112,6 +112,15 @@ export class ReportUIFeatures {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
if (this._opts.onViewTrace) {
|
|
116
|
+
this.addButton({
|
|
117
|
+
text: lhr.configSettings.throttlingMethod === 'simulate' ?
|
|
118
|
+
Util.i18n.strings.viewOriginalTraceLabel :
|
|
119
|
+
Util.i18n.strings.viewTraceLabel,
|
|
120
|
+
onClick: () => this._opts.onViewTrace?.(),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
115
124
|
if (this._opts.getStandaloneReportHTML) {
|
|
116
125
|
this._dom.find('a[data-action="save-html"]', this._dom.rootEl).classList.remove('lh-hidden');
|
|
117
126
|
}
|
|
@@ -180,9 +189,6 @@ export class ReportUIFeatures {
|
|
|
180
189
|
_enableFireworks() {
|
|
181
190
|
const scoresContainer = this._dom.find('.lh-scores-container', this._dom.rootEl);
|
|
182
191
|
scoresContainer.classList.add('lh-score100');
|
|
183
|
-
scoresContainer.addEventListener('click', _ => {
|
|
184
|
-
scoresContainer.classList.toggle('lh-fireworks-paused');
|
|
185
|
-
});
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
_setupMediaQueryListeners() {
|
|
@@ -339,13 +345,15 @@ export class ReportUIFeatures {
|
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
/**
|
|
342
|
-
* DevTools uses its own file manager to download files, so it redefines this function.
|
|
343
|
-
* Wrapper is necessary so DevTools can still override this function.
|
|
344
|
-
*
|
|
345
348
|
* @param {Blob|File} blob
|
|
346
349
|
*/
|
|
347
350
|
_saveFile(blob) {
|
|
348
|
-
const
|
|
349
|
-
this.
|
|
351
|
+
const ext = blob.type.match('json') ? '.json' : '.html';
|
|
352
|
+
const filename = getLhrFilenamePrefix(this.json) + ext;
|
|
353
|
+
if (this._opts.onSaveFileOverride) {
|
|
354
|
+
this._opts.onSaveFileOverride(blob, filename);
|
|
355
|
+
} else {
|
|
356
|
+
this._dom.saveFile(blob, filename);
|
|
357
|
+
}
|
|
350
358
|
}
|
|
351
359
|
}
|
|
@@ -199,7 +199,11 @@ export class TopbarFeatures {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
_print() {
|
|
202
|
-
|
|
202
|
+
if (this._reportUIFeatures._opts.onPrintOverride) {
|
|
203
|
+
this._reportUIFeatures._opts.onPrintOverride(this._dom.rootEl);
|
|
204
|
+
} else {
|
|
205
|
+
self.print();
|
|
206
|
+
}
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
/**
|
package/report/renderer/util.js
CHANGED
|
@@ -627,6 +627,10 @@ const UIStrings = {
|
|
|
627
627
|
thirdPartyResourcesLabel: 'Show 3rd-party resources',
|
|
628
628
|
/** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */
|
|
629
629
|
viewTreemapLabel: 'View Treemap',
|
|
630
|
+
/** This label is for a button that will show the user a trace of the page. */
|
|
631
|
+
viewTraceLabel: 'View Trace',
|
|
632
|
+
/** This label is for a button that will show the user a trace of the page. */
|
|
633
|
+
viewOriginalTraceLabel: 'View Original Trace',
|
|
630
634
|
|
|
631
635
|
/** Option in a dropdown menu that opens a small, summary report in a print dialog. */
|
|
632
636
|
dropdownPrintSummary: 'Print Summary',
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`ReportRendererAxe with aXe renders without axe violations 2`] = `
|
|
4
|
+
Array [
|
|
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.3/duplicate-id?application=axeAPI",
|
|
9
|
+
"id": "duplicate-id",
|
|
10
|
+
"impact": "minor",
|
|
11
|
+
"nodes": Array [
|
|
12
|
+
Object {
|
|
13
|
+
"all": Array [],
|
|
14
|
+
"any": Array [
|
|
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:
|
|
91
|
+
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
|
+
},
|
|
106
|
+
]
|
|
107
|
+
`;
|
|
@@ -53,23 +53,44 @@ describe('ReportRendererAxe', () => {
|
|
|
53
53
|
// eslint-disable-next-line no-undef
|
|
54
54
|
const axeResults = await page.evaluate(config => axe.run(config), config);
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
56
|
+
// Color contrast failure only pops up if this pptr is run headfully.
|
|
57
|
+
// There are currently 27 problematic nodes, primarily audit display text and explanations.
|
|
58
|
+
// TODO: fix these failures, regardless.
|
|
59
|
+
// {
|
|
60
|
+
// id: 'color-contrast',
|
|
61
|
+
// },
|
|
62
|
+
|
|
63
|
+
expect(axeResults.violations.find(v => v.id === 'duplicate-id')).toMatchObject({
|
|
64
|
+
id: 'duplicate-id',
|
|
65
|
+
nodes: [
|
|
66
|
+
// We use these audits in multiple categories. Makes sense.
|
|
67
|
+
{html: '<div class="lh-audit lh-audit--binary lh-audit--pass" id="viewport">'},
|
|
68
|
+
{html: '<div class="lh-audit lh-audit--binary lh-audit--fail" id="image-alt">'},
|
|
69
|
+
{html: '<div class="lh-audit lh-audit--binary lh-audit--pass" id="document-title">'},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const axeSummary = axeResults.violations.map((v) => {
|
|
74
|
+
return {
|
|
75
|
+
id: v.id,
|
|
76
|
+
message: v.nodes.map((n) => n.failureSummary).join('\n'),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
expect(axeSummary).toMatchInlineSnapshot(`
|
|
80
|
+
Array [
|
|
81
|
+
Object {
|
|
82
|
+
"id": "duplicate-id",
|
|
83
|
+
"message": "Fix any of the following:
|
|
84
|
+
Document has multiple static elements with the same id attribute: viewport
|
|
85
|
+
Fix any of the following:
|
|
86
|
+
Document has multiple static elements with the same id attribute: image-alt
|
|
87
|
+
Fix any of the following:
|
|
88
|
+
Document has multiple static elements with the same id attribute: document-title",
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
`);
|
|
92
|
+
|
|
93
|
+
expect(axeResults.violations).toMatchSnapshot();
|
|
73
94
|
},
|
|
74
95
|
// This test takes 10s on fast hardware, but can take longer in CI.
|
|
75
96
|
// https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
|
|
@@ -46,7 +46,8 @@ async function renderLHReport() {
|
|
|
46
46
|
|
|
47
47
|
const reportRootEl = lighthouseRenderer.renderReport(lhr, {
|
|
48
48
|
omitTopbar: true,
|
|
49
|
-
|
|
49
|
+
disableFireworks: true,
|
|
50
|
+
disableDarkMode: true,
|
|
50
51
|
});
|
|
51
52
|
// TODO: display warnings if appropriate.
|
|
52
53
|
for (const el of reportRootEl.querySelectorAll('.lh-warnings--toplevel')) {
|