lighthouse 9.5.0-dev.20220626 → 9.5.0-dev.20220629
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 +7 -6
- package/dist/report/flow.js +24 -24
- package/dist/report/standalone.js +19 -19
- package/flow-report/test/common-test.tsx +5 -4
- package/flow-report/test/flow-report-pptr-test.ts +4 -7
- package/flow-report/test/run-flow-report-tests.sh +20 -0
- package/flow-report/test/setup/env-setup.ts +44 -24
- package/flow-report/test/sidebar/sidebar-test.tsx +0 -1
- package/flow-report/test/topbar-test.tsx +5 -5
- package/flow-report/test/util-test.tsx +3 -3
- package/flow-report/tsconfig.json +4 -1
- package/lighthouse-cli/cli-flags.js +313 -305
- package/lighthouse-cli/test/smokehouse/report-assert-test.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +30 -5
- package/lighthouse-core/audits/network-requests.js +79 -62
- package/lighthouse-core/audits/preload-lcp-image.js +33 -7
- package/lighthouse-core/config/config-plugin.js +26 -0
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +1 -0
- package/lighthouse-core/gather/gather-runner.js +1 -0
- package/lighthouse-core/gather/gatherers/main-document-content.js +0 -2
- package/lighthouse-core/lib/dependency-graph/network-node.js +1 -1
- package/lighthouse-core/lib/lh-env.js +1 -1
- package/lighthouse-core/lib/navigation-error.js +26 -7
- package/lighthouse-core/util-commonjs.js +7 -6
- package/package.json +24 -19
- package/report/renderer/util.js +7 -6
- package/report/test/.eslintrc.cjs +4 -1
- package/report/test/clients/bundle-test.js +4 -4
- package/report/test/generator/report-generator-test.js +3 -1
- package/report/test/renderer/category-renderer-test.js +3 -3
- package/report/test/renderer/components-test.js +36 -34
- package/report/test/renderer/crc-details-renderer-test.js +2 -2
- package/report/test/renderer/details-renderer-test.js +2 -2
- package/report/test/renderer/dom-test.js +4 -4
- package/report/test/renderer/element-screenshot-renderer-test.js +3 -2
- package/report/test/renderer/performance-category-renderer-test.js +4 -4
- package/report/test/renderer/pwa-category-renderer-test.js +3 -3
- package/report/test/renderer/report-renderer-axe-test.js +6 -8
- package/report/test/renderer/report-renderer-test.js +5 -5
- package/report/test/renderer/report-ui-features-test.js +8 -8
- package/report/test/renderer/snippet-renderer-test.js +2 -2
- package/report/test/renderer/text-encoding-test.js +2 -2
- package/report/test/renderer/util-test.js +1 -1
- package/root.js +0 -18
- package/shared/localization/locales/en-US.json +3 -0
- package/shared/localization/locales/en-XL.json +3 -0
- package/shared/test/localization/.eslintrc.cjs +4 -1
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/chromium-synchronization/installability-errors-test.js +1 -3
- package/types/test.d.ts +53 -0
- package/flow-report/jest.config.js +0 -24
- package/flow-report/test/setup/global-setup.ts +0 -11
- package/jest.config.js +0 -30
- package/types/jest.d.ts +0 -25
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
/* eslint-disable no-control-regex */
|
|
8
8
|
|
|
9
|
+
import {readJson} from '../../../lighthouse-core/test/test-utils.js';
|
|
9
10
|
import {findDifferences, getAssertionReport} from './report-assert.js';
|
|
10
|
-
import {readJson} from '../../../root.js';
|
|
11
11
|
|
|
12
12
|
describe('findDiffersences', () => {
|
|
13
13
|
const testCases = {
|
|
@@ -33,6 +33,9 @@ const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
|
33
33
|
|
|
34
34
|
const IGNORE_THRESHOLD_IN_BYTES = 4096;
|
|
35
35
|
|
|
36
|
+
// Ignore up to 12KB of waste if an effort was made with breakpoints.
|
|
37
|
+
const IGNORE_THRESHOLD_IN_BYTES_BREAKPOINTS_PRESENT = 12288;
|
|
38
|
+
|
|
36
39
|
class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
37
40
|
/**
|
|
38
41
|
* @return {LH.Audit.Meta}
|
|
@@ -112,6 +115,18 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
|
112
115
|
};
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {LH.Artifacts.ImageElement} image
|
|
121
|
+
* @return {number};
|
|
122
|
+
*/
|
|
123
|
+
static determineAllowableWaste(image) {
|
|
124
|
+
if (image.srcset || image.isPicture) {
|
|
125
|
+
return IGNORE_THRESHOLD_IN_BYTES_BREAKPOINTS_PRESENT;
|
|
126
|
+
}
|
|
127
|
+
return IGNORE_THRESHOLD_IN_BYTES;
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
/**
|
|
116
131
|
* @param {LH.Artifacts} artifacts
|
|
117
132
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -126,6 +141,8 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
|
126
141
|
const ViewportDimensions = artifacts.ViewportDimensions;
|
|
127
142
|
/** @type {Map<string, LH.Audit.ByteEfficiencyItem>} */
|
|
128
143
|
const resultsMap = new Map();
|
|
144
|
+
/** @type {Array<string>} */
|
|
145
|
+
const passedImageList = [];
|
|
129
146
|
for (const image of images) {
|
|
130
147
|
// Give SVG a free pass because creating a "responsive" SVG is of questionable value.
|
|
131
148
|
// Ignore CSS images because it's difficult to determine what is a spritesheet,
|
|
@@ -147,15 +164,23 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
|
147
164
|
);
|
|
148
165
|
if (!processed) continue;
|
|
149
166
|
|
|
150
|
-
//
|
|
167
|
+
// Verify the image wastes more than the minimum.
|
|
168
|
+
const exceedsAllowableWaste = processed.wastedBytes > this.determineAllowableWaste(image);
|
|
169
|
+
|
|
151
170
|
const existing = resultsMap.get(processed.url);
|
|
152
|
-
|
|
153
|
-
|
|
171
|
+
// Don't warn about an image that was later used appropriately, or wastes a trivial amount of data.
|
|
172
|
+
if (exceedsAllowableWaste && !passedImageList.includes(processed.url)) {
|
|
173
|
+
if ((!existing || existing.wastedBytes > processed.wastedBytes)) {
|
|
174
|
+
resultsMap.set(processed.url, processed);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
// Ensure this url passes for future tests.
|
|
178
|
+
resultsMap.delete(processed.url);
|
|
179
|
+
passedImageList.push(processed.url);
|
|
154
180
|
}
|
|
155
181
|
}
|
|
156
182
|
|
|
157
|
-
const items = Array.from(resultsMap.values())
|
|
158
|
-
.filter(item => item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES);
|
|
183
|
+
const items = Array.from(resultsMap.values());
|
|
159
184
|
|
|
160
185
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
161
186
|
const headings = [
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const Audit = require('./audit.js');
|
|
9
9
|
const URL = require('../lib/url-shim.js');
|
|
10
10
|
const NetworkRecords = require('../computed/network-records.js');
|
|
11
|
+
const MainResource = require('../computed/main-resource.js');
|
|
11
12
|
|
|
12
13
|
class NetworkRequests extends Audit {
|
|
13
14
|
/**
|
|
@@ -19,7 +20,7 @@ class NetworkRequests extends Audit {
|
|
|
19
20
|
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
20
21
|
title: 'Network Requests',
|
|
21
22
|
description: 'Lists the network requests that were made during page load.',
|
|
22
|
-
requiredArtifacts: ['devtoolsLogs'],
|
|
23
|
+
requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -28,75 +29,91 @@ class NetworkRequests extends Audit {
|
|
|
28
29
|
* @param {LH.Audit.Context} context
|
|
29
30
|
* @return {Promise<LH.Audit.Product>}
|
|
30
31
|
*/
|
|
31
|
-
static audit(artifacts, context) {
|
|
32
|
+
static async audit(artifacts, context) {
|
|
32
33
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const records = await NetworkRecords.request(devtoolsLog, context);
|
|
35
|
+
const earliestStartTime = records.reduce(
|
|
36
|
+
(min, record) => Math.min(min, record.startTime),
|
|
37
|
+
Infinity
|
|
38
|
+
);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Optional mainFrameId check because the main resource is only available for
|
|
41
|
+
// navigations. TODO: https://github.com/GoogleChrome/lighthouse/issues/14157
|
|
42
|
+
// for the general solution to this.
|
|
43
|
+
/** @type {string|undefined} */
|
|
44
|
+
let mainFrameId;
|
|
45
|
+
if (artifacts.GatherContext.gatherMode === 'navigation') {
|
|
46
|
+
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
|
|
47
|
+
mainFrameId = mainResource.frameId;
|
|
48
|
+
}
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const requestMs = record.lrStatistics?.requestMs;
|
|
47
|
-
const responseMs = record.lrStatistics?.responseMs;
|
|
50
|
+
/** @param {number} time */
|
|
51
|
+
const timeToMs = time => time < earliestStartTime || !Number.isFinite(time) ?
|
|
52
|
+
undefined : (time - earliestStartTime) * 1000;
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
resourceType: record.resourceType,
|
|
60
|
-
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
61
|
-
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
62
|
-
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
63
|
-
lrResponseMs: responseMs, // Only exists on Lightrider runs
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
|
|
68
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
69
|
-
const headings = [
|
|
70
|
-
{key: 'url', itemType: 'url', text: 'URL'},
|
|
71
|
-
{key: 'protocol', itemType: 'text', text: 'Protocol'},
|
|
72
|
-
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
|
|
73
|
-
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
|
|
74
|
-
{
|
|
75
|
-
key: 'transferSize',
|
|
76
|
-
itemType: 'bytes',
|
|
77
|
-
displayUnit: 'kb',
|
|
78
|
-
granularity: 1,
|
|
79
|
-
text: 'Transfer Size',
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: 'resourceSize',
|
|
83
|
-
itemType: 'bytes',
|
|
84
|
-
displayUnit: 'kb',
|
|
85
|
-
granularity: 1,
|
|
86
|
-
text: 'Resource Size',
|
|
87
|
-
},
|
|
88
|
-
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
|
|
89
|
-
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
|
|
90
|
-
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
const tableDetails = Audit.makeTableDetails(headings, results);
|
|
54
|
+
const results = records.map(record => {
|
|
55
|
+
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
|
|
56
|
+
const TCPMs = record.lrStatistics?.TCPMs;
|
|
57
|
+
const requestMs = record.lrStatistics?.requestMs;
|
|
58
|
+
const responseMs = record.lrStatistics?.responseMs;
|
|
59
|
+
// Default these to undefined so omitted from JSON in the negative case.
|
|
60
|
+
const isLinkPreload = record.isLinkPreload || undefined;
|
|
61
|
+
const experimentalFromMainFrame = mainFrameId ?
|
|
62
|
+
((record.frameId === mainFrameId) || undefined) :
|
|
63
|
+
undefined;
|
|
94
64
|
|
|
95
65
|
return {
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
url: URL.elideDataURI(record.url),
|
|
67
|
+
protocol: record.protocol,
|
|
68
|
+
startTime: timeToMs(record.startTime),
|
|
69
|
+
endTime: timeToMs(record.endTime),
|
|
70
|
+
finished: record.finished,
|
|
71
|
+
transferSize: record.transferSize,
|
|
72
|
+
resourceSize: record.resourceSize,
|
|
73
|
+
statusCode: record.statusCode,
|
|
74
|
+
mimeType: record.mimeType,
|
|
75
|
+
resourceType: record.resourceType,
|
|
76
|
+
isLinkPreload,
|
|
77
|
+
experimentalFromMainFrame,
|
|
78
|
+
lrEndTimeDeltaMs: endTimeDeltaMs, // Only exists on Lightrider runs
|
|
79
|
+
lrTCPMs: TCPMs, // Only exists on Lightrider runs
|
|
80
|
+
lrRequestMs: requestMs, // Only exists on Lightrider runs
|
|
81
|
+
lrResponseMs: responseMs, // Only exists on Lightrider runs
|
|
98
82
|
};
|
|
99
83
|
});
|
|
84
|
+
|
|
85
|
+
// NOTE(i18n): this audit is only for debug info in the LHR and does not appear in the report.
|
|
86
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
87
|
+
const headings = [
|
|
88
|
+
{key: 'url', itemType: 'url', text: 'URL'},
|
|
89
|
+
{key: 'protocol', itemType: 'text', text: 'Protocol'},
|
|
90
|
+
{key: 'startTime', itemType: 'ms', granularity: 1, text: 'Start Time'},
|
|
91
|
+
{key: 'endTime', itemType: 'ms', granularity: 1, text: 'End Time'},
|
|
92
|
+
{
|
|
93
|
+
key: 'transferSize',
|
|
94
|
+
itemType: 'bytes',
|
|
95
|
+
displayUnit: 'kb',
|
|
96
|
+
granularity: 1,
|
|
97
|
+
text: 'Transfer Size',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'resourceSize',
|
|
101
|
+
itemType: 'bytes',
|
|
102
|
+
displayUnit: 'kb',
|
|
103
|
+
granularity: 1,
|
|
104
|
+
text: 'Resource Size',
|
|
105
|
+
},
|
|
106
|
+
{key: 'statusCode', itemType: 'text', text: 'Status Code'},
|
|
107
|
+
{key: 'mimeType', itemType: 'text', text: 'MIME Type'},
|
|
108
|
+
{key: 'resourceType', itemType: 'text', text: 'Resource Type'},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const tableDetails = Audit.makeTableDetails(headings, results);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
score: 1,
|
|
115
|
+
details: tableDetails,
|
|
116
|
+
};
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
|
|
@@ -23,6 +23,10 @@ const UIStrings = {
|
|
|
23
23
|
|
|
24
24
|
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
|
|
28
|
+
*/
|
|
29
|
+
|
|
26
30
|
class PreloadLCPImageAudit extends Audit {
|
|
27
31
|
/**
|
|
28
32
|
* @return {LH.Audit.Meta}
|
|
@@ -86,22 +90,33 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
86
90
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
87
91
|
* @param {LH.Artifacts.TraceElement|undefined} lcpElement
|
|
88
92
|
* @param {Array<LH.Artifacts.ImageElement>} imageElements
|
|
89
|
-
* @return {LH.Gatherer.Simulation.GraphNetworkNode
|
|
93
|
+
* @return {{lcpNodeToPreload?: LH.Gatherer.Simulation.GraphNetworkNode, initiatorPath?: InitiatorPath}}
|
|
90
94
|
*/
|
|
91
95
|
static getLCPNodeToPreload(mainResource, graph, lcpElement, imageElements) {
|
|
92
|
-
if (!lcpElement) return
|
|
96
|
+
if (!lcpElement) return {};
|
|
93
97
|
|
|
94
98
|
const lcpImageElement = imageElements.find(elem => {
|
|
95
99
|
return elem.node.devtoolsNodePath === lcpElement.node.devtoolsNodePath;
|
|
96
100
|
});
|
|
97
101
|
|
|
98
|
-
if (!lcpImageElement) return
|
|
102
|
+
if (!lcpImageElement) return {};
|
|
99
103
|
const lcpUrl = lcpImageElement.src;
|
|
100
104
|
const {lcpNode, path} = PreloadLCPImageAudit.findLCPNode(graph, lcpUrl);
|
|
101
|
-
if (!lcpNode || !path) return
|
|
105
|
+
if (!lcpNode || !path) return {};
|
|
106
|
+
|
|
102
107
|
// eslint-disable-next-line max-len
|
|
103
108
|
const shouldPreload = PreloadLCPImageAudit.shouldPreloadRequest(lcpNode.record, mainResource, path);
|
|
104
|
-
|
|
109
|
+
const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
|
|
110
|
+
|
|
111
|
+
const initiatorPath = [
|
|
112
|
+
{url: lcpNode.record.url, initiatorType: lcpNode.initiatorType},
|
|
113
|
+
...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
lcpNodeToPreload,
|
|
118
|
+
initiatorPath,
|
|
119
|
+
};
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
/**
|
|
@@ -215,10 +230,10 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
215
230
|
|
|
216
231
|
const graph = lanternLCP.pessimisticGraph;
|
|
217
232
|
// eslint-disable-next-line max-len
|
|
218
|
-
const
|
|
233
|
+
const {lcpNodeToPreload, initiatorPath} = PreloadLCPImageAudit.getLCPNodeToPreload(mainResource, graph, lcpElement, artifacts.ImageElements);
|
|
219
234
|
|
|
220
235
|
const {results, wastedMs} =
|
|
221
|
-
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement,
|
|
236
|
+
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
|
|
222
237
|
|
|
223
238
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
224
239
|
const headings = [
|
|
@@ -228,6 +243,17 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
228
243
|
];
|
|
229
244
|
const details = Audit.makeOpportunityDetails(headings, results, wastedMs);
|
|
230
245
|
|
|
246
|
+
// If LCP element was an image and had valid network records (regardless of
|
|
247
|
+
// if it should be preloaded), it will be found first in the `initiatorPath`.
|
|
248
|
+
// Otherwise path and length will be undefined.
|
|
249
|
+
if (initiatorPath) {
|
|
250
|
+
details.debugData = {
|
|
251
|
+
type: 'debugdata',
|
|
252
|
+
initiatorPath,
|
|
253
|
+
pathLength: initiatorPath.length,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
231
257
|
return {
|
|
232
258
|
score: UnusedBytes.scoreForWastedMs(wastedMs),
|
|
233
259
|
numericValue: wastedMs,
|
|
@@ -23,6 +23,24 @@ function isObjectOfUnknownProperties(val) {
|
|
|
23
23
|
return typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {unknown} str
|
|
28
|
+
* @return {str is LH.Gatherer.GatherMode}
|
|
29
|
+
*/
|
|
30
|
+
function objectIsGatherMode(str) {
|
|
31
|
+
if (typeof str !== 'string') return false;
|
|
32
|
+
return str === 'navigation' || str === 'timespan' || str === 'snapshot';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {unknown} arr
|
|
37
|
+
* @return {arr is Array<LH.Gatherer.GatherMode>}
|
|
38
|
+
*/
|
|
39
|
+
function isArrayOfGatherModes(arr) {
|
|
40
|
+
if (!Array.isArray(arr)) return false;
|
|
41
|
+
return arr.every(objectIsGatherMode);
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
/**
|
|
27
45
|
* Asserts that obj has no own properties, throwing a nice error message if it does.
|
|
28
46
|
* Plugin and object name are included for nicer logging.
|
|
@@ -124,6 +142,7 @@ class ConfigPlugin {
|
|
|
124
142
|
description,
|
|
125
143
|
manualDescription,
|
|
126
144
|
auditRefs: auditRefsJson,
|
|
145
|
+
supportedModes,
|
|
127
146
|
...invalidRest
|
|
128
147
|
} = categoryJson;
|
|
129
148
|
|
|
@@ -138,6 +157,12 @@ class ConfigPlugin {
|
|
|
138
157
|
if (!i18n.isStringOrIcuMessage(manualDescription) && manualDescription !== undefined) {
|
|
139
158
|
throw new Error(`${pluginName} has an invalid category manualDescription.`);
|
|
140
159
|
}
|
|
160
|
+
if (!isArrayOfGatherModes(supportedModes) && supportedModes !== undefined) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`${pluginName} supportedModes must be an array, ` +
|
|
163
|
+
`valid array values are "navigation", "timespan", and "snapshot".`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
141
166
|
const auditRefs = ConfigPlugin._parseAuditRefsList(auditRefsJson, pluginName);
|
|
142
167
|
|
|
143
168
|
return {
|
|
@@ -145,6 +170,7 @@ class ConfigPlugin {
|
|
|
145
170
|
auditRefs,
|
|
146
171
|
description: description,
|
|
147
172
|
manualDescription: manualDescription,
|
|
173
|
+
supportedModes,
|
|
148
174
|
};
|
|
149
175
|
}
|
|
150
176
|
|
|
@@ -604,6 +604,7 @@ class GatherRunner {
|
|
|
604
604
|
url: passContext.url,
|
|
605
605
|
loadFailureMode: passConfig.loadFailureMode,
|
|
606
606
|
networkRecords: loadData.networkRecords,
|
|
607
|
+
warnings: passContext.LighthouseRunWarnings,
|
|
607
608
|
});
|
|
608
609
|
if (pageLoadError) {
|
|
609
610
|
const localizedMessage = format.getFormatted(pageLoadError.friendlyMessage,
|
|
@@ -21,7 +21,6 @@ class MainDocumentContent extends FRGatherer {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
24
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
26
25
|
* @param {LH.Artifacts['DevtoolsLog']} devtoolsLog
|
|
27
26
|
* @return {Promise<LH.Artifacts['MainDocumentContent']>}
|
|
@@ -34,7 +33,6 @@ class MainDocumentContent extends FRGatherer {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
*
|
|
38
36
|
* @param {LH.Gatherer.FRTransitionalContext<'DevtoolsLog'>} context
|
|
39
37
|
* @return {Promise<LH.Artifacts['MainDocumentContent']>}
|
|
40
38
|
*/
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const process = require('process');
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
// NODE_ENV is set to test by
|
|
11
|
+
// NODE_ENV is set to test by mocha-setup.js and the smokehouse CLI runner
|
|
12
12
|
// CI as a catchall for everything we do in GitHub Actions
|
|
13
13
|
isUnderTest: !!process.env.CI || process.env.NODE_ENV === 'test',
|
|
14
14
|
};
|
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
const LHError = require('./lh-error.js');
|
|
9
9
|
const NetworkAnalyzer = require('./dependency-graph/simulator/network-analyzer.js');
|
|
10
10
|
const NetworkRequest = require('./network-request.js');
|
|
11
|
+
const i18n = require('./i18n/i18n.js');
|
|
12
|
+
|
|
13
|
+
const UIStrings = {
|
|
14
|
+
/**
|
|
15
|
+
* Warning shown in report when the page under test is an XHTML document, which Lighthouse does not directly support
|
|
16
|
+
* so we display a warning.
|
|
17
|
+
*/
|
|
18
|
+
warningXhtml:
|
|
19
|
+
'The page MIME type is XHTML: Lighthouse does not explicitly support this document type',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
23
|
+
|
|
24
|
+
// MIME types are case-insensitive but Chrome normalizes MIME types to be lowercase.
|
|
25
|
+
const HTML_MIME_TYPE = 'text/html';
|
|
26
|
+
const XHTML_MIME_TYPE = 'application/xhtml+xml';
|
|
11
27
|
|
|
12
28
|
/**
|
|
13
29
|
* Returns an error if the original network request failed or wasn't found.
|
|
@@ -76,16 +92,15 @@ function getInterstitialError(mainRecord, networkRecords) {
|
|
|
76
92
|
* @return {LH.LighthouseError|undefined}
|
|
77
93
|
*/
|
|
78
94
|
function getNonHtmlError(finalRecord) {
|
|
79
|
-
// MIME types are case-insenstive but Chrome normalizes MIME types to be lowercase.
|
|
80
|
-
const HTML_MIME_TYPE = 'text/html';
|
|
81
|
-
|
|
82
95
|
// If we never requested a document, there's no doctype error, let other cases handle it.
|
|
83
96
|
if (!finalRecord) return undefined;
|
|
84
97
|
|
|
85
98
|
// mimeType is determined by the browser, we assume Chrome is determining mimeType correctly,
|
|
86
99
|
// independently of 'Content-Type' response headers, and always sending mimeType if well-formed.
|
|
87
|
-
if (
|
|
88
|
-
return new LHError(LHError.errors.NOT_HTML, {
|
|
100
|
+
if (finalRecord.mimeType !== HTML_MIME_TYPE && finalRecord.mimeType !== XHTML_MIME_TYPE) {
|
|
101
|
+
return new LHError(LHError.errors.NOT_HTML, {
|
|
102
|
+
mimeType: finalRecord.mimeType,
|
|
103
|
+
});
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
return undefined;
|
|
@@ -95,7 +110,7 @@ function getNonHtmlError(finalRecord) {
|
|
|
95
110
|
* Returns an error if the page load should be considered failed, e.g. from a
|
|
96
111
|
* main document request failure, a security issue, etc.
|
|
97
112
|
* @param {LH.LighthouseError|undefined} navigationError
|
|
98
|
-
* @param {{url: string, loadFailureMode: LH.Gatherer.PassContext['passConfig']['loadFailureMode'], networkRecords: Array<LH.Artifacts.NetworkRequest>}} context
|
|
113
|
+
* @param {{url: string, loadFailureMode: LH.Gatherer.PassContext['passConfig']['loadFailureMode'], networkRecords: Array<LH.Artifacts.NetworkRequest>, warnings: Array<string | LH.IcuMessage>}} context
|
|
99
114
|
* @return {LH.LighthouseError|undefined}
|
|
100
115
|
*/
|
|
101
116
|
function getPageLoadError(navigationError, context) {
|
|
@@ -120,6 +135,10 @@ function getPageLoadError(navigationError, context) {
|
|
|
120
135
|
finalRecord = NetworkAnalyzer.resolveRedirects(mainRecord);
|
|
121
136
|
}
|
|
122
137
|
|
|
138
|
+
if (finalRecord?.mimeType === XHTML_MIME_TYPE) {
|
|
139
|
+
context.warnings.push(str_(UIStrings.warningXhtml));
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
const networkError = getNetworkError(mainRecord);
|
|
124
143
|
const interstitialError = getInterstitialError(mainRecord, networkRecords);
|
|
125
144
|
const nonHtmlError = getNonHtmlError(finalRecord);
|
|
@@ -145,10 +164,10 @@ function getPageLoadError(navigationError, context) {
|
|
|
145
164
|
return navigationError;
|
|
146
165
|
}
|
|
147
166
|
|
|
148
|
-
|
|
149
167
|
module.exports = {
|
|
150
168
|
getNetworkError,
|
|
151
169
|
getInterstitialError,
|
|
152
170
|
getPageLoadError,
|
|
153
171
|
getNonHtmlError,
|
|
172
|
+
UIStrings,
|
|
154
173
|
};
|
|
@@ -572,15 +572,16 @@ class Util {
|
|
|
572
572
|
*/
|
|
573
573
|
Util.reportJson = null;
|
|
574
574
|
|
|
575
|
+
let svgSuffix = 0;
|
|
575
576
|
/**
|
|
576
577
|
* An always-increasing counter for making unique SVG ID suffixes.
|
|
577
578
|
*/
|
|
578
|
-
Util.getUniqueSuffix = (
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
579
|
+
Util.getUniqueSuffix = () => {
|
|
580
|
+
return svgSuffix++;
|
|
581
|
+
};
|
|
582
|
+
Util.resetUniqueSuffix = () => {
|
|
583
|
+
svgSuffix = 0;
|
|
584
|
+
};
|
|
584
585
|
|
|
585
586
|
/**
|
|
586
587
|
* Report-renderer-specific strings.
|
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.20220629",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,24 +35,24 @@
|
|
|
35
35
|
"smoke": "node lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js",
|
|
36
36
|
"debug": "node --inspect-brk ./lighthouse-cli/index.js",
|
|
37
37
|
"start": "yarn build-report --standalone && node ./lighthouse-cli/index.js",
|
|
38
|
-
"
|
|
38
|
+
"mocha": "node lighthouse-core/test/scripts/run-mocha-tests.js",
|
|
39
39
|
"test": "yarn diff:sample-json && yarn lint --quiet && yarn unit && yarn type-check",
|
|
40
40
|
"test-bundle": "yarn smoke --runner bundle --retries=2",
|
|
41
|
-
"test-clients": "yarn
|
|
42
|
-
"test-viewer": "yarn unit-viewer && yarn
|
|
43
|
-
"test-treemap": "yarn unit-treemap && yarn
|
|
41
|
+
"test-clients": "yarn mocha --testMatch clients/**/*-test.js && yarn mocha --testMatch clients/**/*-test-pptr.js",
|
|
42
|
+
"test-viewer": "yarn unit-viewer && yarn mocha --testMatch viewer/**/*-test-pptr.js --timeout 35000",
|
|
43
|
+
"test-treemap": "yarn unit-treemap && yarn mocha --testMatch treemap/**/*-test-pptr.js --timeout 35000",
|
|
44
44
|
"test-lantern": "bash lighthouse-core/scripts/test-lantern.sh",
|
|
45
45
|
"test-legacy-javascript": "bash lighthouse-core/scripts/test-legacy-javascript.sh",
|
|
46
46
|
"test-docs": "yarn --cwd docs/recipes/ test",
|
|
47
47
|
"test-proto": "yarn compile-proto && yarn build-proto-roundtrip",
|
|
48
|
-
"unit-core": "yarn
|
|
49
|
-
"unit-cli": "yarn
|
|
50
|
-
"unit-report": "yarn
|
|
51
|
-
"unit-treemap": "yarn
|
|
52
|
-
"unit-viewer": "yarn
|
|
53
|
-
"unit-flow": "
|
|
54
|
-
"unit": "yarn
|
|
55
|
-
"unit:ci": "NODE_OPTIONS=--max-old-space-size=8192 npm run
|
|
48
|
+
"unit-core": "yarn mocha lighthouse-core",
|
|
49
|
+
"unit-cli": "yarn mocha --testMatch lighthouse-cli/**/*-test.js",
|
|
50
|
+
"unit-report": "yarn mocha --testMatch report/**/*-test.js",
|
|
51
|
+
"unit-treemap": "yarn mocha --testMatch treemap/**/*-test.js",
|
|
52
|
+
"unit-viewer": "yarn mocha --testMatch viewer/**/*-test.js",
|
|
53
|
+
"unit-flow": "bash flow-report/test/run-flow-report-tests.sh",
|
|
54
|
+
"unit": "yarn unit-flow && yarn mocha",
|
|
55
|
+
"unit:ci": "NODE_OPTIONS=--max-old-space-size=8192 npm run unit --forbid-only",
|
|
56
56
|
"core-unit": "yarn unit-core",
|
|
57
57
|
"cli-unit": "yarn unit-cli",
|
|
58
58
|
"viewer-unit": "yarn unit-viewer",
|
|
@@ -96,6 +96,8 @@
|
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"@build-tracker/cli": "^1.0.0-beta.15",
|
|
99
|
+
"@esbuild-kit/esm-loader": "^2.1.1",
|
|
100
|
+
"@jest/fake-timers": "^28.1.0",
|
|
99
101
|
"@rollup/plugin-alias": "^3.1.2",
|
|
100
102
|
"@rollup/plugin-commonjs": "^20.0.0",
|
|
101
103
|
"@rollup/plugin-dynamic-import-vars": "^1.1.1",
|
|
@@ -103,7 +105,7 @@
|
|
|
103
105
|
"@rollup/plugin-node-resolve": "^13.0.4",
|
|
104
106
|
"@rollup/plugin-typescript": "^8.2.5",
|
|
105
107
|
"@stadtlandnetz/rollup-plugin-postprocess": "^1.1.0",
|
|
106
|
-
"@testing-library/preact": "^
|
|
108
|
+
"@testing-library/preact": "^3.1.1",
|
|
107
109
|
"@testing-library/preact-hooks": "^1.1.0",
|
|
108
110
|
"@types/archiver": "^2.1.2",
|
|
109
111
|
"@types/chrome": "^0.0.154",
|
|
@@ -113,10 +115,10 @@
|
|
|
113
115
|
"@types/estree": "^0.0.50",
|
|
114
116
|
"@types/gh-pages": "^2.0.0",
|
|
115
117
|
"@types/google.analytics": "0.0.39",
|
|
116
|
-
"@types/jest": "^27.0.3",
|
|
117
118
|
"@types/jpeg-js": "^0.3.7",
|
|
118
119
|
"@types/jsdom": "^16.2.13",
|
|
119
120
|
"@types/lodash": "^4.14.178",
|
|
121
|
+
"@types/mocha": "^9.0.0",
|
|
120
122
|
"@types/node": "*",
|
|
121
123
|
"@types/pako": "^1.0.1",
|
|
122
124
|
"@types/resize-observer-browser": "^0.1.1",
|
|
@@ -130,7 +132,7 @@
|
|
|
130
132
|
"acorn": "^8.5.0",
|
|
131
133
|
"angular": "^1.7.4",
|
|
132
134
|
"archiver": "^3.0.0",
|
|
133
|
-
"c8": "^7.
|
|
135
|
+
"c8": "^7.11.3",
|
|
134
136
|
"chalk": "^2.4.1",
|
|
135
137
|
"chrome-devtools-frontend": "1.0.1012379",
|
|
136
138
|
"concurrently": "^6.4.0",
|
|
@@ -146,22 +148,25 @@
|
|
|
146
148
|
"eslint-plugin-import": "^2.25.3",
|
|
147
149
|
"eslint-plugin-local-rules": "1.1.0",
|
|
148
150
|
"event-target-shim": "^6.0.2",
|
|
151
|
+
"expect": "^28.1.0",
|
|
149
152
|
"firebase": "^9.0.2",
|
|
150
153
|
"gh-pages": "^2.0.1",
|
|
151
154
|
"glob": "^7.1.3",
|
|
152
155
|
"idb-keyval": "2.2.0",
|
|
153
156
|
"intl-messageformat-parser": "^1.8.1",
|
|
154
|
-
"jest": "27.
|
|
157
|
+
"jest-mock": "^27.3.0",
|
|
158
|
+
"jest-snapshot": "^28.1.0",
|
|
155
159
|
"jsdom": "^12.2.0",
|
|
156
160
|
"jsonld": "^5.2.0",
|
|
157
161
|
"jsonlint-mod": "^1.7.6",
|
|
158
162
|
"lighthouse-plugin-publisher-ads": "^1.5.4",
|
|
159
163
|
"magic-string": "^0.25.7",
|
|
160
164
|
"mime-types": "^2.1.30",
|
|
165
|
+
"mocha": "^10.0.0",
|
|
161
166
|
"node-fetch": "^2.6.1",
|
|
162
167
|
"npm-run-posix-or-windows": "^2.0.2",
|
|
163
168
|
"pako": "^2.0.3",
|
|
164
|
-
"preact": "^10.
|
|
169
|
+
"preact": "^10.7.2",
|
|
165
170
|
"pretty-json-stringify": "^0.0.2",
|
|
166
171
|
"puppeteer": "13.7.0",
|
|
167
172
|
"resolve": "^1.20.0",
|
|
@@ -173,7 +178,7 @@
|
|
|
173
178
|
"rollup-plugin-terser": "^7.0.2",
|
|
174
179
|
"tabulator-tables": "^4.9.3",
|
|
175
180
|
"terser": "^5.3.8",
|
|
176
|
-
"
|
|
181
|
+
"testdouble": "^3.16.5",
|
|
177
182
|
"typed-query-selector": "^2.6.1",
|
|
178
183
|
"typescript": "^4.7.3",
|
|
179
184
|
"wait-for-expect": "^3.0.2",
|