lighthouse 10.0.0-dev.20230213 → 10.0.1
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/core/audits/dobetterweb/doctype.d.ts +3 -2
- package/core/audits/dobetterweb/doctype.js +15 -4
- package/core/audits/dobetterweb/paste-preventing-inputs.js +2 -2
- package/core/gather/driver/network-monitor.js +1 -3
- package/core/gather/gatherers/bf-cache-failures.js +7 -8
- package/package.json +1 -1
- package/shared/localization/locales/en-US.json +1 -1
- package/shared/localization/locales/en-XL.json +1 -1
- package/.codecov.yml +0 -22
- package/.cz-config.js +0 -24
- package/.gitattributes +0 -5
- package/.mailmap +0 -6
- package/CODE_OF_CONDUCT.md +0 -93
- package/eslint-local-rules.cjs +0 -106
- package/flow-report/test/app-test.d.ts +0 -7
- package/flow-report/test/app-test.tsx +0 -52
- package/flow-report/test/common-test.d.ts +0 -7
- package/flow-report/test/common-test.tsx +0 -103
- package/flow-report/test/flow-report-pptr-test.d.ts +0 -7
- package/flow-report/test/flow-report-pptr-test.ts +0 -59
- package/flow-report/test/header-test.d.ts +0 -7
- package/flow-report/test/header-test.tsx +0 -55
- package/flow-report/test/run-flow-report-tests.sh +0 -20
- package/flow-report/test/sample-flow.d.ts +0 -7
- package/flow-report/test/sample-flow.ts +0 -17
- package/flow-report/test/setup/env-setup.d.ts +0 -11
- package/flow-report/test/setup/env-setup.ts +0 -55
- package/flow-report/test/sidebar/flow-test.d.ts +0 -7
- package/flow-report/test/sidebar/flow-test.tsx +0 -65
- package/flow-report/test/sidebar/sidebar-test.d.ts +0 -7
- package/flow-report/test/sidebar/sidebar-test.tsx +0 -116
- package/flow-report/test/summary/category-test.d.ts +0 -7
- package/flow-report/test/summary/category-test.tsx +0 -206
- package/flow-report/test/summary/summary-test.d.ts +0 -7
- package/flow-report/test/summary/summary-test.tsx +0 -124
- package/flow-report/test/topbar-test.d.ts +0 -7
- package/flow-report/test/topbar-test.tsx +0 -84
- package/flow-report/test/util-test.d.ts +0 -7
- package/flow-report/test/util-test.tsx +0 -133
- package/flow-report/test/wrappers/category-score-test.d.ts +0 -7
- package/flow-report/test/wrappers/category-score-test.tsx +0 -84
- package/flow-report/test/wrappers/markdown-test.d.ts +0 -7
- package/flow-report/test/wrappers/markdown-test.tsx +0 -17
- package/report/test/clients/bundle-test.js +0 -65
- package/report/test/generator/file-namer-test.js +0 -27
- package/report/test/generator/report-generator-test.js +0 -160
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +0 -15
- package/report/test/renderer/category-renderer-test.js +0 -575
- package/report/test/renderer/components-test.js +0 -124
- package/report/test/renderer/crc-details-renderer-test.js +0 -116
- package/report/test/renderer/details-renderer-test.js +0 -882
- package/report/test/renderer/dom-test.js +0 -257
- package/report/test/renderer/element-screenshot-renderer-test.js +0 -183
- package/report/test/renderer/i18n-formatter-test.js +0 -179
- package/report/test/renderer/performance-category-renderer-test.js +0 -417
- package/report/test/renderer/pwa-category-renderer-test.js +0 -301
- package/report/test/renderer/report-renderer-axe-test.js +0 -85
- package/report/test/renderer/report-renderer-test.js +0 -268
- package/report/test/renderer/report-ui-features-test.js +0 -773
- package/report/test/renderer/report-utils-test.js +0 -219
- package/report/test/renderer/snippet-renderer-test.js +0 -291
- package/report/test/renderer/text-encoding-test.js +0 -43
- package/report/test-assets/faux-psi-template.html +0 -177
- package/report/test-assets/faux-psi.d.ts +0 -34
- package/report/test-assets/faux-psi.js +0 -168
- package/report/test-assets/lhr-3.0.0.json +0 -2332
- package/report/test-assets/lhr-4.3.0.json +0 -4754
- package/report/test-assets/lhr-5.0.0.json +0 -5994
- package/report/test-assets/lhr-6.0.0.json +0 -7222
- package/report/test-assets/lhr-8.5.0.json +0 -8679
- package/shared/test/localization/format-test.js +0 -430
- package/shared/test/localization/locales-test.js +0 -45
- package/shared/test/localization/swap-locale-test.js +0 -127
- package/shared/test/util-test.js +0 -214
- package/vercel.json +0 -6
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2018 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
|
-
|
|
7
|
-
import assert from 'assert/strict';
|
|
8
|
-
|
|
9
|
-
import jsdom from 'jsdom';
|
|
10
|
-
|
|
11
|
-
import {ReportUtils, UIStrings} from '../../renderer/report-utils.js';
|
|
12
|
-
import {I18nFormatter} from '../../renderer/i18n-formatter.js';
|
|
13
|
-
import {DOM} from '../../renderer/dom.js';
|
|
14
|
-
import {DetailsRenderer} from '../../renderer/details-renderer.js';
|
|
15
|
-
import {PwaCategoryRenderer} from '../../renderer/pwa-category-renderer.js';
|
|
16
|
-
import {readJson} from '../../../core/test/test-utils.js';
|
|
17
|
-
import {Globals} from '../../renderer/report-globals.js';
|
|
18
|
-
|
|
19
|
-
const sampleResultsOrig = readJson('../../../core/test/results/sample_v2.json', import.meta);
|
|
20
|
-
|
|
21
|
-
describe('PwaCategoryRenderer', () => {
|
|
22
|
-
let category;
|
|
23
|
-
let pwaRenderer;
|
|
24
|
-
let sampleResults;
|
|
25
|
-
|
|
26
|
-
before(() => {
|
|
27
|
-
Globals.apply({
|
|
28
|
-
providedStrings: {},
|
|
29
|
-
i18n: new I18nFormatter('en'),
|
|
30
|
-
reportJson: null,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const {document} = new jsdom.JSDOM().window;
|
|
34
|
-
const dom = new DOM(document);
|
|
35
|
-
const detailsRenderer = new DetailsRenderer(dom);
|
|
36
|
-
pwaRenderer = new PwaCategoryRenderer(dom, detailsRenderer);
|
|
37
|
-
|
|
38
|
-
sampleResults = ReportUtils.prepareReportResult(sampleResultsOrig);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
// Clone category to allow modifications.
|
|
43
|
-
const pwaCategory = sampleResults.categories.pwa;
|
|
44
|
-
category = JSON.parse(JSON.stringify(pwaCategory));
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
after(() => {
|
|
48
|
-
Globals.i18n = undefined;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('renders the regular audits', () => {
|
|
52
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
53
|
-
const allAuditElements = Array.from(categoryElem.querySelectorAll('.lh-audit'));
|
|
54
|
-
const manualElements = Array.from(categoryElem.querySelectorAll('.lh-clump--manual .lh-audit'));
|
|
55
|
-
const regularAuditElements = allAuditElements.filter(el => !manualElements.includes(el));
|
|
56
|
-
|
|
57
|
-
const nonManualAudits = category.auditRefs
|
|
58
|
-
.filter(audit => audit.result.scoreDisplayMode !== 'manual');
|
|
59
|
-
assert.strictEqual(regularAuditElements.length, nonManualAudits.length);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('renders the manual audits', () => {
|
|
63
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
64
|
-
const manualElements = categoryElem.querySelectorAll('.lh-clump--manual .lh-audit');
|
|
65
|
-
|
|
66
|
-
const manualAudits = category.auditRefs
|
|
67
|
-
.filter(audit => audit.result.scoreDisplayMode === 'manual');
|
|
68
|
-
|
|
69
|
-
assert.strictEqual(manualElements.length, manualAudits.length);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('manual audits are the only clump', () => {
|
|
73
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
74
|
-
const clumpElems = categoryElem.querySelectorAll('.lh-clump');
|
|
75
|
-
assert.strictEqual(clumpElems.length, 1);
|
|
76
|
-
assert.ok(clumpElems[0].classList.contains('lh-clump--manual'));
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('renders the audit groups', () => {
|
|
80
|
-
const categoryGroupIds = new Set(category.auditRefs.filter(a => a.group).map(a => a.group));
|
|
81
|
-
assert.strictEqual(categoryGroupIds.size, 2); // Ensure there's something to test.
|
|
82
|
-
|
|
83
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
84
|
-
|
|
85
|
-
categoryGroupIds.forEach(groupId => {
|
|
86
|
-
const selector = `.lh-audit-group--${groupId}`;
|
|
87
|
-
// Expected that only the non-manual audits will be grouped.
|
|
88
|
-
assert.strictEqual(categoryElem.querySelectorAll(selector).length, 1,
|
|
89
|
-
`trouble with selector '${selector}'`);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('badging groups', () => {
|
|
94
|
-
let auditRefs;
|
|
95
|
-
let groupIds;
|
|
96
|
-
|
|
97
|
-
beforeEach(() => {
|
|
98
|
-
auditRefs = category.auditRefs
|
|
99
|
-
.filter(audit => audit.result.scoreDisplayMode !== 'manual');
|
|
100
|
-
|
|
101
|
-
// Expect results to all be scorable or n/a
|
|
102
|
-
for (const auditRef of auditRefs) {
|
|
103
|
-
const matcher = expect.stringMatching(/(binary)|(notApplicable)/);
|
|
104
|
-
expect(auditRef.result.scoreDisplayMode).toEqual(matcher);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
groupIds = [...new Set(auditRefs.map(ref => ref.group))];
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('gives passing even if an audit is notApplicable', () => {
|
|
111
|
-
const clone = JSON.parse(JSON.stringify(sampleResults));
|
|
112
|
-
const category = clone.categories.pwa;
|
|
113
|
-
|
|
114
|
-
// Set everything to passing, except for one. (themed-omnibox chosen randomly)
|
|
115
|
-
for (const auditRef of category.auditRefs) {
|
|
116
|
-
auditRef.result.score = 1;
|
|
117
|
-
auditRef.result.scoreDisplayMode = 'binary';
|
|
118
|
-
}
|
|
119
|
-
const audit = category.auditRefs.find(ref => ref.id === 'themed-omnibox');
|
|
120
|
-
audit.result.scoreDisplayMode = 'notApplicable';
|
|
121
|
-
audit.result.score = null;
|
|
122
|
-
|
|
123
|
-
const categoryElem = pwaRenderer.render(category, clone.categoryGroups);
|
|
124
|
-
const badgedElems = categoryElem.querySelectorAll(`.lh-audit-group--pwa-optimized.lh-badged`);
|
|
125
|
-
expect(badgedElems.length).toEqual(1);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('only gives a group a badge when all the group\'s audits are passing', () => {
|
|
129
|
-
for (const auditRef of auditRefs) {
|
|
130
|
-
auditRef.result.score = 0;
|
|
131
|
-
auditRef.result.scoreDisplayMode = 'binary';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const targetGroupId = 'pwa-optimized';
|
|
135
|
-
const targetGroupTitle = sampleResults.categoryGroups[targetGroupId].title;
|
|
136
|
-
const targetAuditRefs = auditRefs.filter(ref => ref.group === targetGroupId);
|
|
137
|
-
|
|
138
|
-
// Try every permutation of audit scoring.
|
|
139
|
-
const totalPermutations = Math.pow(2, targetAuditRefs.length);
|
|
140
|
-
for (let i = 0; i < totalPermutations; i++) {
|
|
141
|
-
for (let j = 0; j < targetAuditRefs.length; j++) {
|
|
142
|
-
// Set as passing if jth bit in i is set.
|
|
143
|
-
targetAuditRefs[j].result.score = i >> j & 1;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
147
|
-
const badgedElems = categoryElem.querySelectorAll(`.lh-badged`);
|
|
148
|
-
const badgedScoreGauge =
|
|
149
|
-
categoryElem.querySelector('.lh-gauge--pwa__wrapper[class*="lh-badged--"]');
|
|
150
|
-
|
|
151
|
-
const tooltip = categoryElem.querySelector('.lh-gauge--pwa__wrapper').title;
|
|
152
|
-
const targetGroupTip = tooltip.split(', ').find(tip => tip.startsWith(targetGroupTitle));
|
|
153
|
-
assert.ok(targetGroupTip);
|
|
154
|
-
|
|
155
|
-
// Only expect a badge (and badged gauge) on last permutation (all bits are set).
|
|
156
|
-
if (i !== totalPermutations - 1) {
|
|
157
|
-
assert.strictEqual(badgedElems.length, 0);
|
|
158
|
-
assert.strictEqual(badgedScoreGauge, null);
|
|
159
|
-
|
|
160
|
-
// Tooltip ends with passing/total.
|
|
161
|
-
const passingCount = categoryElem.querySelectorAll(
|
|
162
|
-
`.lh-audit-group--${targetGroupId} .lh-audit--pass`).length;
|
|
163
|
-
assert.ok(targetGroupTip.endsWith(`${passingCount}/${targetAuditRefs.length}`));
|
|
164
|
-
} else {
|
|
165
|
-
assert.strictEqual(badgedElems.length, 1);
|
|
166
|
-
assert.ok(badgedScoreGauge.classList.contains(`lh-badged--${targetGroupId}`));
|
|
167
|
-
|
|
168
|
-
// Tooltip ends with total/total.
|
|
169
|
-
assert.ok(targetGroupTip.endsWith(`${targetAuditRefs.length}/${targetAuditRefs.length}`));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('renders all badges when all audits are passing', () => {
|
|
175
|
-
for (const auditRef of auditRefs) {
|
|
176
|
-
auditRef.result.score = 1;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
180
|
-
assert.strictEqual(categoryElem.querySelectorAll('.lh-badged').length, groupIds.length);
|
|
181
|
-
|
|
182
|
-
// Score gauge.
|
|
183
|
-
const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
|
|
184
|
-
assert.ok(gaugeElem.classList.contains('lh-badged--all'));
|
|
185
|
-
|
|
186
|
-
// All tooltips should have x/x audits passed.
|
|
187
|
-
const tips = gaugeElem.title.split(', ');
|
|
188
|
-
assert.strictEqual(tips.length, groupIds.length);
|
|
189
|
-
for (const tip of tips) {
|
|
190
|
-
assert.ok(/(\d+)\/\1$/.test(tip));
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('renders no badges when no audit groups are passing', () => {
|
|
195
|
-
for (const auditRef of auditRefs) {
|
|
196
|
-
auditRef.result.score = 0;
|
|
197
|
-
auditRef.result.scoreDisplayMode = 'binary';
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
201
|
-
assert.strictEqual(categoryElem.querySelectorAll('.lh-badged').length, 0);
|
|
202
|
-
|
|
203
|
-
// Score gauge.
|
|
204
|
-
const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
|
|
205
|
-
assert.ok(!gaugeElem.matches('.lh-gauge--pwa__wrapper[class*="lh-badged-"]'));
|
|
206
|
-
|
|
207
|
-
// All tooltips should have 0/x audits passed.
|
|
208
|
-
const tips = gaugeElem.title.split(', ');
|
|
209
|
-
assert.strictEqual(tips.length, groupIds.length);
|
|
210
|
-
for (const tip of tips) {
|
|
211
|
-
assert.ok(/0\/\d+$/.test(tip));
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('renders all but one badge when all groups but one are passing', () => {
|
|
216
|
-
for (const auditRef of auditRefs) {
|
|
217
|
-
auditRef.result.score = 1;
|
|
218
|
-
}
|
|
219
|
-
auditRefs[0].result.score = 0;
|
|
220
|
-
const failingGroupId = auditRefs[0].group;
|
|
221
|
-
|
|
222
|
-
const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
|
|
223
|
-
const gaugeElem = categoryElem.querySelector('.lh-gauge--pwa__wrapper');
|
|
224
|
-
|
|
225
|
-
const tips = gaugeElem.title.split(', ');
|
|
226
|
-
assert.strictEqual(tips.length, groupIds.length);
|
|
227
|
-
|
|
228
|
-
for (const groupId of groupIds) {
|
|
229
|
-
const expectedCount = groupId === failingGroupId ? 0 : 1;
|
|
230
|
-
|
|
231
|
-
// Individual group badges.
|
|
232
|
-
const groupElems = categoryElem.querySelectorAll(`.lh-audit-group--${groupId}.lh-badged`);
|
|
233
|
-
assert.strictEqual(groupElems.length, expectedCount);
|
|
234
|
-
|
|
235
|
-
// Score gauge.
|
|
236
|
-
if (groupId !== failingGroupId) {
|
|
237
|
-
assert.ok(gaugeElem.classList.contains(`lh-badged--${groupId}`));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Map back from groupId to groupTitle (used in tooltip).
|
|
241
|
-
const groupTitle = sampleResults.categoryGroups[groupId].title;
|
|
242
|
-
const groupTip = tips.find(tip => tip.startsWith(groupTitle));
|
|
243
|
-
assert.ok(groupTip);
|
|
244
|
-
|
|
245
|
-
// All tooltips should be x/x except for failingGroup, which should be (x-1)/x.
|
|
246
|
-
if (groupId !== failingGroupId) {
|
|
247
|
-
assert.ok(/(\d+)\/\1$/.test(groupTip));
|
|
248
|
-
} else {
|
|
249
|
-
const [, passingCount, totalCount] = /(\d+)\/(\d+)$/.exec(groupTip);
|
|
250
|
-
assert.strictEqual(Number(passingCount) + 1, Number(totalCount));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
describe('#renderCategoryScore', () => {
|
|
257
|
-
it('renders an error score gauge in case of category error', () => {
|
|
258
|
-
category.score = null;
|
|
259
|
-
const badgeGauge = pwaRenderer.renderCategoryScore(category, sampleResults.categoryGroups);
|
|
260
|
-
|
|
261
|
-
// Not a PWA gauge.
|
|
262
|
-
assert.strictEqual(badgeGauge.querySelector('.lh-gauge--pwa__wrapper'), null);
|
|
263
|
-
|
|
264
|
-
const percentageElem = badgeGauge.querySelector('.lh-gauge__percentage');
|
|
265
|
-
assert.strictEqual(percentageElem.textContent, '?');
|
|
266
|
-
assert.strictEqual(percentageElem.title, UIStrings.errorLabel);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('renders score gauges with unique ids for items in <defs>', () => {
|
|
270
|
-
const gauge1 = pwaRenderer.renderCategoryScore(category, sampleResults.categoryGroups);
|
|
271
|
-
const gauge1Ids = [...gauge1.querySelectorAll('defs [id]')].map(el => el.id);
|
|
272
|
-
assert.ok(gauge1Ids.length > 2);
|
|
273
|
-
|
|
274
|
-
const gauge2 = pwaRenderer.renderCategoryScore(category, sampleResults.categoryGroups);
|
|
275
|
-
const gauge2Ids = [...gauge2.querySelectorAll('defs [id]')].map(el => el.id);
|
|
276
|
-
assert.ok(gauge2Ids.length === gauge1Ids.length);
|
|
277
|
-
|
|
278
|
-
/** Returns whether id is used by at least one <use> or fill under `rootEl`. */
|
|
279
|
-
function idInUseElOrFillAttr(rootEl, id) {
|
|
280
|
-
const isUse = rootEl.querySelector(`use[href="#${id}"]`);
|
|
281
|
-
const isFill = rootEl.querySelector(`[fill="url(#${id})"]`);
|
|
282
|
-
|
|
283
|
-
return !!(isUse || isFill);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Check that each gauge1 ID is actually used in gauge1 and isn't used in gauge2.
|
|
287
|
-
for (const gauge1Id of gauge1Ids) {
|
|
288
|
-
assert.equal(idInUseElOrFillAttr(gauge1, gauge1Id), true);
|
|
289
|
-
assert.ok(!gauge2Ids.includes(gauge1Id));
|
|
290
|
-
assert.equal(idInUseElOrFillAttr(gauge2, gauge1Id), false);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// And that the reverse is true for gauge2 IDs.
|
|
294
|
-
for (const gauge2Id of gauge2Ids) {
|
|
295
|
-
assert.equal(idInUseElOrFillAttr(gauge1, gauge2Id), false);
|
|
296
|
-
assert.equal(idInUseElOrFillAttr(gauge2, gauge2Id), true);
|
|
297
|
-
assert.ok(!gauge1Ids.includes(gauge2Id));
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
import puppeteer from 'puppeteer';
|
|
8
|
-
|
|
9
|
-
import {ReportGenerator} from '../../generator/report-generator.js';
|
|
10
|
-
import {axeSource} from '../../../core/lib/axe.js';
|
|
11
|
-
import {readJson} from '../../../core/test/test-utils.js';
|
|
12
|
-
|
|
13
|
-
const sampleResults = readJson('../../../core/test/results/sample_v2.json', import.meta);
|
|
14
|
-
|
|
15
|
-
describe('ReportRendererAxe', () => {
|
|
16
|
-
describe('with aXe', () => {
|
|
17
|
-
let browser;
|
|
18
|
-
|
|
19
|
-
before(async () => {
|
|
20
|
-
browser = await puppeteer.launch();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
after(async () => {
|
|
24
|
-
await browser.close();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// This test takes 10s on fast hardware, but can take longer in CI.
|
|
28
|
-
// https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
|
|
29
|
-
it('renders without axe violations', async () => {
|
|
30
|
-
const page = await browser.newPage();
|
|
31
|
-
const htmlReport = ReportGenerator.generateReportHtml(sampleResults);
|
|
32
|
-
await page.setContent(htmlReport);
|
|
33
|
-
|
|
34
|
-
// Superset of Lighthouse's aXe config
|
|
35
|
-
const config = {
|
|
36
|
-
runOnly: {
|
|
37
|
-
type: 'tag',
|
|
38
|
-
values: [
|
|
39
|
-
'wcag2a',
|
|
40
|
-
'wcag2aa',
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
resultTypes: ['violations', 'inapplicable'],
|
|
44
|
-
rules: {
|
|
45
|
-
'tabindex': {enabled: true},
|
|
46
|
-
'accesskeys': {enabled: true},
|
|
47
|
-
'heading-order': {enabled: true},
|
|
48
|
-
'meta-viewport': {enabled: true},
|
|
49
|
-
'aria-treeitem-name': {enabled: true},
|
|
50
|
-
// TODO: re-enable. https://github.com/GoogleChrome/lighthouse/issues/13918
|
|
51
|
-
'color-contrast': {enabled: false},
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
await page.evaluate(axeSource);
|
|
56
|
-
// eslint-disable-next-line no-undef
|
|
57
|
-
const axeResults = await page.evaluate(config => axe.run(config), config);
|
|
58
|
-
|
|
59
|
-
// Color contrast failure only pops up if this pptr is run headfully.
|
|
60
|
-
// There are currently 27 problematic nodes, primarily audit display text and explanations.
|
|
61
|
-
// TODO: fix these failures, regardless.
|
|
62
|
-
// {
|
|
63
|
-
// id: 'color-contrast',
|
|
64
|
-
// },
|
|
65
|
-
|
|
66
|
-
expect(axeResults.violations.find(v => v.id === 'duplicate-id')).toMatchObject({
|
|
67
|
-
id: 'duplicate-id',
|
|
68
|
-
nodes: [
|
|
69
|
-
// We use these audits in multiple categories. Makes sense.
|
|
70
|
-
{html: '<div class="lh-audit lh-audit--binary lh-audit--pass" id="viewport">'},
|
|
71
|
-
{html: '<div class="lh-audit lh-audit--binary lh-audit--fail" id="image-alt">'},
|
|
72
|
-
{html: '<div class="lh-audit lh-audit--binary lh-audit--pass" id="document-title">'},
|
|
73
|
-
],
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const axeSummary = axeResults.violations.map((v) => {
|
|
77
|
-
return {
|
|
78
|
-
id: v.id,
|
|
79
|
-
message: v.nodes.map((n) => n.failureSummary).join('\n'),
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
|
-
expect(axeSummary).toMatchSnapshot();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2017 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
|
-
|
|
7
|
-
import assert from 'assert/strict';
|
|
8
|
-
|
|
9
|
-
import jsdom from 'jsdom';
|
|
10
|
-
import jestMock from 'jest-mock';
|
|
11
|
-
|
|
12
|
-
import {ReportUtils} from '../../renderer/report-utils.js';
|
|
13
|
-
import {DOM} from '../../renderer/dom.js';
|
|
14
|
-
import {DetailsRenderer} from '../../renderer/details-renderer.js';
|
|
15
|
-
import {CategoryRenderer} from '../../renderer/category-renderer.js';
|
|
16
|
-
import {ReportRenderer} from '../../renderer/report-renderer.js';
|
|
17
|
-
import {readJson} from '../../../core/test/test-utils.js';
|
|
18
|
-
|
|
19
|
-
const sampleResultsOrig = readJson('../../../core/test/results/sample_v2.json', import.meta);
|
|
20
|
-
|
|
21
|
-
const TIMESTAMP_REGEX = /\d+, \d{4}.*\d+:\d+/;
|
|
22
|
-
|
|
23
|
-
describe('ReportRenderer', () => {
|
|
24
|
-
let renderer;
|
|
25
|
-
let sampleResults;
|
|
26
|
-
|
|
27
|
-
before(() => {
|
|
28
|
-
global.console.warn = jestMock.fn();
|
|
29
|
-
|
|
30
|
-
// Stub out matchMedia for Node.
|
|
31
|
-
global.matchMedia = function() {
|
|
32
|
-
return {
|
|
33
|
-
addListener: function() {},
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const {window} = new jsdom.JSDOM();
|
|
38
|
-
global.self = window;
|
|
39
|
-
|
|
40
|
-
const dom = new DOM(window.document);
|
|
41
|
-
const detailsRenderer = new DetailsRenderer(dom);
|
|
42
|
-
const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
|
|
43
|
-
renderer = new ReportRenderer(dom, categoryRenderer);
|
|
44
|
-
sampleResults = ReportUtils.prepareReportResult(sampleResultsOrig);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
after(() => {
|
|
48
|
-
global.self = undefined;
|
|
49
|
-
global.matchMedia = undefined;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('renderReport', () => {
|
|
53
|
-
it('should render a report', () => {
|
|
54
|
-
const container = renderer._dom.document().body;
|
|
55
|
-
const output = renderer.renderReport(sampleResults, container);
|
|
56
|
-
assert.ok(output.querySelector('.lh-header-container'), 'has a header');
|
|
57
|
-
assert.ok(output.querySelector('.lh-report'), 'has report body');
|
|
58
|
-
// 3 sets of gauges - one in sticky header, one in scores header, and one in each section.
|
|
59
|
-
assert.equal(output.querySelectorAll('.lh-gauge__wrapper, .lh-gauge--pwa__wrapper').length,
|
|
60
|
-
Object.keys(sampleResults.categories).length * 3, 'renders category gauges');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('renders additional reports by replacing the existing one', () => {
|
|
64
|
-
const container = renderer._dom.document().body;
|
|
65
|
-
const oldReport = Array.from(renderer.renderReport(sampleResults, container).children);
|
|
66
|
-
const newReport = Array.from(renderer.renderReport(sampleResults, container).children);
|
|
67
|
-
assert.ok(!oldReport.find(node => container.contains(node)), 'old report was removed');
|
|
68
|
-
assert.ok(newReport.find(node => container.contains(node)),
|
|
69
|
-
'new report appended to container');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('renders a topbar', () => {
|
|
73
|
-
const topbar = renderer._renderReportTopbar(sampleResults);
|
|
74
|
-
assert.equal(
|
|
75
|
-
topbar.querySelector('.lh-topbar__url').textContent,
|
|
76
|
-
sampleResults.finalDisplayedUrl
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('renders a header', () => {
|
|
81
|
-
const header = renderer._renderReportHeader();
|
|
82
|
-
assert.ok(header.querySelector('.lh-scores-container'), 'contains score container');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('renders score gauges in this order: default, pwa, plugins', () => {
|
|
86
|
-
const sampleResultsCopy = JSON.parse(JSON.stringify(sampleResults));
|
|
87
|
-
sampleResultsCopy.categories['lighthouse-plugin-someplugin'] = {
|
|
88
|
-
id: 'lighthouse-plugin-someplugin',
|
|
89
|
-
title: 'Some Plugin',
|
|
90
|
-
auditRefs: [],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const container = renderer._dom.document().body;
|
|
94
|
-
const output = renderer.renderReport(sampleResultsCopy, container);
|
|
95
|
-
|
|
96
|
-
function isPWAGauge(el) {
|
|
97
|
-
return el.querySelector('.lh-gauge__label').textContent === 'PWA';
|
|
98
|
-
}
|
|
99
|
-
function isPluginGauge(el) {
|
|
100
|
-
return el.querySelector('.lh-gauge__label').textContent === 'Some Plugin';
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const indexOfPwaGauge = Array.from(output
|
|
104
|
-
.querySelectorAll('.lh-scores-header > a[class*="lh-gauge"]')).findIndex(isPWAGauge);
|
|
105
|
-
|
|
106
|
-
const indexOfPluginGauge = Array.from(output
|
|
107
|
-
.querySelectorAll('.lh-scores-header > a[class*="lh-gauge"]')).findIndex(isPluginGauge);
|
|
108
|
-
|
|
109
|
-
const scoresHeaderElem = output.querySelector('.lh-scores-header');
|
|
110
|
-
assert.equal(scoresHeaderElem.children.length - 2, indexOfPwaGauge);
|
|
111
|
-
assert.equal(scoresHeaderElem.children.length - 1, indexOfPluginGauge);
|
|
112
|
-
assert(indexOfPluginGauge > indexOfPwaGauge);
|
|
113
|
-
|
|
114
|
-
for (let i = 0; i < scoresHeaderElem.children.length; i++) {
|
|
115
|
-
const gauge = scoresHeaderElem.children[i];
|
|
116
|
-
|
|
117
|
-
assert.ok(gauge.classList.contains('lh-gauge__wrapper'));
|
|
118
|
-
if (i >= indexOfPluginGauge) {
|
|
119
|
-
assert.ok(isPluginGauge(gauge));
|
|
120
|
-
} else if (i >= indexOfPwaGauge) {
|
|
121
|
-
assert.ok(isPWAGauge(gauge));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('renders score gauges with custom callback', () => {
|
|
127
|
-
const sampleResultsCopy = JSON.parse(JSON.stringify(sampleResults));
|
|
128
|
-
|
|
129
|
-
const opts = {
|
|
130
|
-
onPageAnchorRendered: link => {
|
|
131
|
-
const id = link.hash.substring(1);
|
|
132
|
-
link.hash = `#index=0&anchor=${id}`;
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
const container = renderer._dom.document().body;
|
|
136
|
-
const output = renderer.renderReport(sampleResultsCopy, container, opts);
|
|
137
|
-
const anchors = output.querySelectorAll('a.lh-gauge__wrapper, a.lh-fraction__wrapper');
|
|
138
|
-
const hashes = Array.from(anchors).map(anchor => anchor.hash).filter(hash => hash);
|
|
139
|
-
|
|
140
|
-
// One set for the sticky header, on set for the gauges at the top.
|
|
141
|
-
assert.deepStrictEqual(hashes, [
|
|
142
|
-
'#index=0&anchor=performance',
|
|
143
|
-
'#index=0&anchor=accessibility',
|
|
144
|
-
'#index=0&anchor=best-practices',
|
|
145
|
-
'#index=0&anchor=seo',
|
|
146
|
-
'#index=0&anchor=pwa',
|
|
147
|
-
'#index=0&anchor=performance',
|
|
148
|
-
'#index=0&anchor=accessibility',
|
|
149
|
-
'#index=0&anchor=best-practices',
|
|
150
|
-
'#index=0&anchor=seo',
|
|
151
|
-
'#index=0&anchor=pwa',
|
|
152
|
-
]);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('renders plugin score gauge', () => {
|
|
156
|
-
const sampleResultsCopy = JSON.parse(JSON.stringify(sampleResults));
|
|
157
|
-
sampleResultsCopy.categories['lighthouse-plugin-someplugin'] = {
|
|
158
|
-
id: 'lighthouse-plugin-someplugin',
|
|
159
|
-
title: 'Some Plugin',
|
|
160
|
-
auditRefs: [],
|
|
161
|
-
};
|
|
162
|
-
const container = renderer._dom.document().body;
|
|
163
|
-
const output = renderer.renderReport(sampleResultsCopy, container);
|
|
164
|
-
const scoresHeaderElem = output.querySelector('.lh-scores-header');
|
|
165
|
-
|
|
166
|
-
const gaugeCount = scoresHeaderElem.querySelectorAll('.lh-gauge').length;
|
|
167
|
-
const pluginGaugeCount =
|
|
168
|
-
scoresHeaderElem.querySelectorAll('.lh-gauge__wrapper--plugin').length;
|
|
169
|
-
|
|
170
|
-
// 5 core categories + the 1 plugin.
|
|
171
|
-
assert.equal(6, gaugeCount);
|
|
172
|
-
assert.equal(1, pluginGaugeCount);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('should not mutate a report object', () => {
|
|
176
|
-
const container = renderer._dom.document().body;
|
|
177
|
-
const originalResults = JSON.parse(JSON.stringify(sampleResults));
|
|
178
|
-
renderer.renderReport(sampleResults, container);
|
|
179
|
-
assert.deepStrictEqual(sampleResults, originalResults);
|
|
180
|
-
}, 2000);
|
|
181
|
-
|
|
182
|
-
it('renders no warning section when no lighthouseRunWarnings occur', () => {
|
|
183
|
-
const warningResults = Object.assign({}, sampleResults, {runWarnings: []});
|
|
184
|
-
const container = renderer._dom.document().body;
|
|
185
|
-
const output = renderer.renderReport(warningResults, container);
|
|
186
|
-
assert.strictEqual(output.querySelector('.lh-warnings--toplevel'), null);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('renders a warning section', () => {
|
|
190
|
-
const container = renderer._dom.document().body;
|
|
191
|
-
const output = renderer.renderReport(sampleResults, container);
|
|
192
|
-
|
|
193
|
-
const warningEls = output.querySelectorAll('.lh-warnings--toplevel > ul > li');
|
|
194
|
-
assert.strictEqual(warningEls.length, sampleResults.runWarnings.length);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('renders links in the warning section', () => {
|
|
198
|
-
const warningResults = Object.assign({}, sampleResults, {
|
|
199
|
-
runWarnings: ['[I am a link](https://example.com/)'],
|
|
200
|
-
});
|
|
201
|
-
const container = renderer._dom.document().body;
|
|
202
|
-
const output = renderer.renderReport(warningResults, container);
|
|
203
|
-
|
|
204
|
-
const warningEls = output.querySelectorAll('.lh-warnings--toplevel ul li a');
|
|
205
|
-
expect(warningEls).toHaveLength(1);
|
|
206
|
-
expect(warningEls[0].href).toEqual('https://example.com/');
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('renders a footer', () => {
|
|
210
|
-
const footer = renderer._renderReportFooter(sampleResults);
|
|
211
|
-
const footerContent = footer.querySelector('.lh-footer').textContent;
|
|
212
|
-
assert.ok(/Generated by Lighthouse \d/.test(footerContent), 'includes lh version');
|
|
213
|
-
assert.ok(footerContent.match(TIMESTAMP_REGEX), 'includes timestamp');
|
|
214
|
-
|
|
215
|
-
// Check env items were populated.
|
|
216
|
-
const items = Array.from(footer.querySelectorAll('.lh-meta__item'));
|
|
217
|
-
expect(items.length).toBeGreaterThanOrEqual(6);
|
|
218
|
-
|
|
219
|
-
const itemsTxt = items.map(el => `${el.textContent} ${el.title}`).join('\n');
|
|
220
|
-
expect(itemsTxt).toContain('Moto G Power');
|
|
221
|
-
expect(itemsTxt).toContain('RTT');
|
|
222
|
-
expect(itemsTxt).toMatch(/\dx/);
|
|
223
|
-
expect(itemsTxt).toContain(sampleResults.environment.networkUserAgent);
|
|
224
|
-
expect(itemsTxt).toMatch('412x823, DPR 1.75');
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('should add LHR channel to doc link parameters', () => {
|
|
229
|
-
const lhrChannel = sampleResults.configSettings.channel;
|
|
230
|
-
// Make sure we have a channel in the LHR.
|
|
231
|
-
assert.ok(lhrChannel.length > 2);
|
|
232
|
-
|
|
233
|
-
const container = renderer._dom.document().body;
|
|
234
|
-
const output = renderer.renderReport(sampleResults, container);
|
|
235
|
-
|
|
236
|
-
const DOCS_ORIGINS = ['https://developers.google.com', 'https://web.dev', 'https://developer.chrome.com'];
|
|
237
|
-
const utmChannels = [...output.querySelectorAll('a[href*="utm_source=lighthouse"')]
|
|
238
|
-
.map(a => new URL(a.href))
|
|
239
|
-
.filter(url => DOCS_ORIGINS.includes(url.origin))
|
|
240
|
-
.map(url => url.searchParams.get('utm_medium'));
|
|
241
|
-
|
|
242
|
-
assert.ok(utmChannels.length > 100);
|
|
243
|
-
for (const utmChannel of utmChannels) {
|
|
244
|
-
assert.strictEqual(utmChannel, lhrChannel);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('renders `not_applicable` audits as `notApplicable`', () => {
|
|
249
|
-
const clonedSampleResult = JSON.parse(JSON.stringify(sampleResultsOrig));
|
|
250
|
-
|
|
251
|
-
let notApplicableCount = 0;
|
|
252
|
-
Object.values(clonedSampleResult.audits).forEach(audit => {
|
|
253
|
-
// The performance-budget audit is omitted from the DOM when it is not applicable
|
|
254
|
-
if (audit.scoreDisplayMode === 'notApplicable' && audit.id !== 'performance-budget') {
|
|
255
|
-
notApplicableCount++;
|
|
256
|
-
audit.scoreDisplayMode = 'not_applicable';
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
assert.ok(notApplicableCount > 20); // Make sure something's being tested.
|
|
261
|
-
|
|
262
|
-
const container = renderer._dom.document().body;
|
|
263
|
-
const reportElement = renderer.renderReport(sampleResults, container);
|
|
264
|
-
const notApplicableElementCount = reportElement
|
|
265
|
-
.querySelectorAll('.lh-audit--notapplicable').length;
|
|
266
|
-
assert.strictEqual(notApplicableCount, notApplicableElementCount);
|
|
267
|
-
});
|
|
268
|
-
});
|