lighthouse 9.4.0 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +5902 -0
- package/dist/report/bundle.esm.js +3 -2
- package/dist/report/flow.js +2 -2
- package/dist/report/standalone.js +2 -2
- package/lighthouse-cli/run.js +5 -1
- package/lighthouse-cli/test/smokehouse/core-tests.js +4 -0
- package/lighthouse-cli/test/smokehouse/frontends/lib.js +3 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +4 -1
- package/lighthouse-cli/test/smokehouse/readme.md +14 -0
- package/lighthouse-cli/test/smokehouse/report-assert.js +64 -28
- package/lighthouse-cli/test/smokehouse/smokehouse.js +5 -2
- package/lighthouse-core/audits/dobetterweb/inspector-issues.js +6 -6
- package/lighthouse-core/audits/script-treemap-data.js +1 -1
- package/lighthouse-core/config/config-helpers.js +1 -1
- package/lighthouse-core/fraggle-rock/api.js +56 -5
- package/lighthouse-core/fraggle-rock/config/config.js +1 -1
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +1 -1
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +8 -4
- package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +3 -1
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +12 -5
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +13 -7
- package/lighthouse-core/fraggle-rock/user-flow.js +114 -55
- package/lighthouse-core/gather/gatherers/full-page-screenshot.js +33 -25
- package/lighthouse-core/gather/gatherers/inspector-issues.js +1 -1
- package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
- package/lighthouse-core/lib/asset-saver.js +15 -0
- package/lighthouse-core/runner.js +1 -1
- package/lighthouse-core/scripts/package.json +4 -0
- package/package.json +7 -12
- package/report/assets/templates.html +2 -1
- package/report/renderer/components.js +1 -1
- package/report/renderer/topbar-features.js +2 -1
- package/shared/localization/swap-locale.js +1 -2
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/snyk/snapshot.json +4 -3
- package/types/artifacts.d.ts +1 -1
- package/types/config.d.ts +1 -1
- package/types/gatherer.d.ts +8 -0
- package/types/global-lh.d.ts +3 -0
- package/types/smokehouse.d.ts +1 -1
- package/types/user-flow.d.ts +19 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const log = require('lighthouse-logger');
|
|
8
9
|
const Driver = require('./driver.js');
|
|
9
10
|
const Runner = require('../../runner.js');
|
|
10
11
|
const {
|
|
@@ -18,10 +19,12 @@ const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
|
|
21
|
-
* @return {Promise<{
|
|
22
|
+
* @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
|
|
22
23
|
*/
|
|
23
|
-
async function
|
|
24
|
+
async function startTimespanGather(options) {
|
|
24
25
|
const {configContext = {}} = options;
|
|
26
|
+
log.setLevel(configContext.logLevel || 'error');
|
|
27
|
+
|
|
25
28
|
const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'timespan'});
|
|
26
29
|
const driver = new Driver(options.page);
|
|
27
30
|
await driver.connect();
|
|
@@ -29,11 +32,12 @@ async function startTimespan(options) {
|
|
|
29
32
|
/** @type {Map<string, LH.ArbitraryEqualityMap>} */
|
|
30
33
|
const computedCache = new Map();
|
|
31
34
|
const artifactDefinitions = config.artifacts || [];
|
|
32
|
-
const requestedUrl = await
|
|
35
|
+
const requestedUrl = await driver.url();
|
|
33
36
|
const baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode: 'timespan'});
|
|
34
37
|
const artifactState = getEmptyArtifactState();
|
|
35
38
|
/** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
|
|
36
39
|
const phaseOptions = {
|
|
40
|
+
url: requestedUrl,
|
|
37
41
|
driver,
|
|
38
42
|
artifactDefinitions,
|
|
39
43
|
artifactState,
|
|
@@ -48,8 +52,10 @@ async function startTimespan(options) {
|
|
|
48
52
|
await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
|
|
49
53
|
|
|
50
54
|
return {
|
|
51
|
-
async
|
|
52
|
-
const finalUrl = await
|
|
55
|
+
async endTimespanGather() {
|
|
56
|
+
const finalUrl = await driver.url();
|
|
57
|
+
phaseOptions.url = finalUrl;
|
|
58
|
+
|
|
53
59
|
const runnerOptions = {config, computedCache};
|
|
54
60
|
const artifacts = await Runner.gather(
|
|
55
61
|
async () => {
|
|
@@ -66,11 +72,11 @@ async function startTimespan(options) {
|
|
|
66
72
|
},
|
|
67
73
|
runnerOptions
|
|
68
74
|
);
|
|
69
|
-
return
|
|
75
|
+
return {artifacts, runnerOptions};
|
|
70
76
|
},
|
|
71
77
|
};
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
module.exports = {
|
|
75
|
-
|
|
81
|
+
startTimespanGather,
|
|
76
82
|
};
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
9
|
+
const {snapshotGather} = require('./gather/snapshot-runner.js');
|
|
10
|
+
const {startTimespanGather} = require('./gather/timespan-runner.js');
|
|
11
|
+
const {navigationGather} = require('./gather/navigation-runner.js');
|
|
12
|
+
const Runner = require('../runner.js');
|
|
13
|
+
const {initializeConfig} = require('./config/config.js');
|
|
12
14
|
|
|
13
|
-
/** @typedef {Parameters<
|
|
15
|
+
/** @typedef {Parameters<snapshotGather>[0]} FrOptions */
|
|
14
16
|
/** @typedef {Omit<FrOptions, 'page'> & {name?: string}} UserFlowOptions */
|
|
15
17
|
/** @typedef {Omit<FrOptions, 'page'> & {stepName?: string}} StepOptions */
|
|
18
|
+
/** @typedef {WeakMap<LH.UserFlow.GatherStep, LH.Gatherer.FRGatherResult['runnerOptions']>} GatherStepRunnerOptions */
|
|
16
19
|
|
|
17
20
|
class UserFlow {
|
|
18
21
|
/**
|
|
@@ -24,8 +27,10 @@ class UserFlow {
|
|
|
24
27
|
this.options = {page, ...options};
|
|
25
28
|
/** @type {string|undefined} */
|
|
26
29
|
this.name = options?.name;
|
|
27
|
-
/** @type {LH.
|
|
28
|
-
this.
|
|
30
|
+
/** @type {LH.UserFlow.GatherStep[]} */
|
|
31
|
+
this._gatherSteps = [];
|
|
32
|
+
/** @type {GatherStepRunnerOptions} */
|
|
33
|
+
this._gatherStepRunnerOptions = new WeakMap();
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
@@ -38,12 +43,12 @@ class UserFlow {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
41
|
-
* @param {LH.
|
|
46
|
+
* @param {LH.Artifacts} artifacts
|
|
42
47
|
* @return {string}
|
|
43
48
|
*/
|
|
44
|
-
_getDefaultStepName(
|
|
45
|
-
const shortUrl = this._shortenUrl(
|
|
46
|
-
switch (
|
|
49
|
+
_getDefaultStepName(artifacts) {
|
|
50
|
+
const shortUrl = this._shortenUrl(artifacts.URL.finalUrl);
|
|
51
|
+
switch (artifacts.GatherContext.gatherMode) {
|
|
47
52
|
case 'navigation':
|
|
48
53
|
return `Navigation report (${shortUrl})`;
|
|
49
54
|
case 'timespan':
|
|
@@ -66,7 +71,8 @@ class UserFlow {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
// On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
|
|
69
|
-
const isSubsequentNavigation = this.
|
|
74
|
+
const isSubsequentNavigation = this._gatherSteps
|
|
75
|
+
.some(step => step.artifacts.GatherContext.gatherMode === 'navigation');
|
|
70
76
|
if (isSubsequentNavigation) {
|
|
71
77
|
if (settingsOverrides.disableStorageReset === undefined) {
|
|
72
78
|
settingsOverrides.disableStorageReset = true;
|
|
@@ -79,92 +85,145 @@ class UserFlow {
|
|
|
79
85
|
return options;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
* @param {LH.Gatherer.FRGatherResult} gatherResult
|
|
91
|
+
* @param {StepOptions} options
|
|
92
|
+
*/
|
|
93
|
+
_addGatherStep(gatherResult, options) {
|
|
94
|
+
const providedName = options?.stepName;
|
|
95
|
+
const gatherStep = {
|
|
96
|
+
artifacts: gatherResult.artifacts,
|
|
97
|
+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
|
|
98
|
+
config: options.config,
|
|
99
|
+
configContext: options.configContext,
|
|
100
|
+
};
|
|
101
|
+
this._gatherSteps.push(gatherStep);
|
|
102
|
+
this._gatherStepRunnerOptions.set(gatherStep, gatherResult.runnerOptions);
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
/**
|
|
83
106
|
* @param {LH.NavigationRequestor} requestor
|
|
84
107
|
* @param {StepOptions=} stepOptions
|
|
85
108
|
*/
|
|
86
109
|
async navigate(requestor, stepOptions) {
|
|
87
|
-
if (this.currentTimespan) throw Error('Timespan already in progress');
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
this._getNextNavigationOptions(stepOptions)
|
|
92
|
-
);
|
|
93
|
-
if (!result) throw Error('Navigation returned undefined');
|
|
94
|
-
|
|
95
|
-
const providedName = stepOptions?.stepName;
|
|
96
|
-
this.steps.push({
|
|
97
|
-
lhr: result.lhr,
|
|
98
|
-
name: providedName || this._getDefaultStepName(result.lhr),
|
|
99
|
-
});
|
|
110
|
+
if (this.currentTimespan) throw new Error('Timespan already in progress');
|
|
111
|
+
|
|
112
|
+
const options = this._getNextNavigationOptions(stepOptions);
|
|
113
|
+
const gatherResult = await navigationGather(requestor, options);
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
this._addGatherStep(gatherResult, options);
|
|
116
|
+
|
|
117
|
+
return gatherResult;
|
|
102
118
|
}
|
|
103
119
|
|
|
104
120
|
/**
|
|
105
121
|
* @param {StepOptions=} stepOptions
|
|
106
122
|
*/
|
|
107
123
|
async startTimespan(stepOptions) {
|
|
108
|
-
if (this.currentTimespan) throw Error('Timespan already in progress');
|
|
124
|
+
if (this.currentTimespan) throw new Error('Timespan already in progress');
|
|
109
125
|
|
|
110
126
|
const options = {...this.options, ...stepOptions};
|
|
111
|
-
const timespan = await
|
|
127
|
+
const timespan = await startTimespanGather(options);
|
|
112
128
|
this.currentTimespan = {timespan, options};
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
async endTimespan() {
|
|
116
|
-
if (!this.currentTimespan) throw Error('No timespan in progress');
|
|
132
|
+
if (!this.currentTimespan) throw new Error('No timespan in progress');
|
|
117
133
|
|
|
118
134
|
const {timespan, options} = this.currentTimespan;
|
|
119
|
-
const
|
|
135
|
+
const gatherResult = await timespan.endTimespanGather();
|
|
120
136
|
this.currentTimespan = undefined;
|
|
121
|
-
if (!result) throw Error('Timespan returned undefined');
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
this.steps.push({
|
|
125
|
-
lhr: result.lhr,
|
|
126
|
-
name: providedName || this._getDefaultStepName(result.lhr),
|
|
127
|
-
});
|
|
138
|
+
this._addGatherStep(gatherResult, options);
|
|
128
139
|
|
|
129
|
-
return
|
|
140
|
+
return gatherResult;
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
/**
|
|
133
144
|
* @param {StepOptions=} stepOptions
|
|
134
145
|
*/
|
|
135
146
|
async snapshot(stepOptions) {
|
|
136
|
-
if (this.currentTimespan) throw Error('Timespan already in progress');
|
|
147
|
+
if (this.currentTimespan) throw new Error('Timespan already in progress');
|
|
137
148
|
|
|
138
149
|
const options = {...this.options, ...stepOptions};
|
|
139
|
-
const
|
|
140
|
-
if (!result) throw Error('Snapshot returned undefined');
|
|
150
|
+
const gatherResult = await snapshotGather(options);
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
this.steps.push({
|
|
144
|
-
lhr: result.lhr,
|
|
145
|
-
name: providedName || this._getDefaultStepName(result.lhr),
|
|
146
|
-
});
|
|
152
|
+
this._addGatherStep(gatherResult, options);
|
|
147
153
|
|
|
148
|
-
return
|
|
154
|
+
return gatherResult;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
/**
|
|
152
|
-
* @
|
|
158
|
+
* @returns {Promise<LH.FlowResult>}
|
|
153
159
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
async createFlowResult() {
|
|
161
|
+
return auditGatherSteps(this._gatherSteps, {
|
|
162
|
+
name: this.name,
|
|
163
|
+
config: this.options.config,
|
|
164
|
+
gatherStepRunnerOptions: this._gatherStepRunnerOptions,
|
|
165
|
+
});
|
|
159
166
|
}
|
|
160
167
|
|
|
161
168
|
/**
|
|
162
|
-
* @return {string}
|
|
169
|
+
* @return {Promise<string>}
|
|
163
170
|
*/
|
|
164
|
-
generateReport() {
|
|
165
|
-
const flowResult = this.
|
|
171
|
+
async generateReport() {
|
|
172
|
+
const flowResult = await this.createFlowResult();
|
|
166
173
|
return generateFlowReportHtml(flowResult);
|
|
167
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @return {LH.UserFlow.FlowArtifacts}
|
|
178
|
+
*/
|
|
179
|
+
createArtifactsJson() {
|
|
180
|
+
return {
|
|
181
|
+
gatherSteps: this._gatherSteps,
|
|
182
|
+
name: this.name,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {Array<LH.UserFlow.GatherStep>} gatherSteps
|
|
189
|
+
* @param {{name?: string, config?: LH.Config.Json, gatherStepRunnerOptions?: GatherStepRunnerOptions}} options
|
|
190
|
+
*/
|
|
191
|
+
async function auditGatherSteps(gatherSteps, options) {
|
|
192
|
+
if (!gatherSteps.length) {
|
|
193
|
+
throw new Error('Need at least one step before getting the result');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @type {LH.FlowResult['steps']} */
|
|
197
|
+
const steps = [];
|
|
198
|
+
for (const gatherStep of gatherSteps) {
|
|
199
|
+
const {artifacts, name, configContext} = gatherStep;
|
|
200
|
+
|
|
201
|
+
let runnerOptions = options.gatherStepRunnerOptions?.get(gatherStep);
|
|
202
|
+
|
|
203
|
+
// If the gather step is not active, we must recreate the runner options.
|
|
204
|
+
if (!runnerOptions) {
|
|
205
|
+
// Step specific configs take precedence over a config for the entire flow.
|
|
206
|
+
const configJson = gatherStep.config || options.config;
|
|
207
|
+
const {gatherMode} = artifacts.GatherContext;
|
|
208
|
+
const {config} = initializeConfig(configJson, {...configContext, gatherMode});
|
|
209
|
+
runnerOptions = {
|
|
210
|
+
config,
|
|
211
|
+
computedCache: new Map(),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const result = await Runner.audit(artifacts, runnerOptions);
|
|
216
|
+
if (!result) throw new Error(`Step "${name}" did not return a result`);
|
|
217
|
+
steps.push({lhr: result.lhr, name});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const url = new URL(gatherSteps[0].artifacts.URL.finalUrl);
|
|
221
|
+
const flowName = options.name || `User flow (${url.hostname})`;
|
|
222
|
+
return {steps, name: flowName};
|
|
168
223
|
}
|
|
169
224
|
|
|
170
|
-
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
UserFlow,
|
|
228
|
+
auditGatherSteps,
|
|
229
|
+
};
|
|
@@ -70,21 +70,22 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
73
|
+
* @param {{height: number, width: number, mobile: boolean}} deviceMetrics
|
|
73
74
|
* @return {Promise<LH.Artifacts.FullPageScreenshot['screenshot']>}
|
|
74
75
|
*/
|
|
75
|
-
async _takeScreenshot(context) {
|
|
76
|
+
async _takeScreenshot(context, deviceMetrics) {
|
|
76
77
|
const session = context.driver.defaultSession;
|
|
77
78
|
const maxTextureSize = await this.getMaxTextureSize(context);
|
|
78
79
|
const metrics = await session.sendCommand('Page.getLayoutMetrics');
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const height = Math.min(
|
|
81
|
+
// Height should be as tall as the content.
|
|
82
|
+
// Scale the emulated height to reach the content height.
|
|
83
|
+
const fullHeight = Math.round(
|
|
84
|
+
deviceMetrics.height *
|
|
85
|
+
metrics.contentSize.height /
|
|
86
|
+
metrics.layoutViewport.clientHeight
|
|
87
|
+
);
|
|
88
|
+
const height = Math.min(fullHeight, maxTextureSize);
|
|
88
89
|
|
|
89
90
|
// Setup network monitor before we change the viewport.
|
|
90
91
|
const networkMonitor = new NetworkMonitor(session);
|
|
@@ -98,13 +99,10 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
98
99
|
await networkMonitor.enable();
|
|
99
100
|
|
|
100
101
|
await session.sendCommand('Emulation.setDeviceMetricsOverride', {
|
|
101
|
-
|
|
102
|
-
mobile: context.settings.screenEmulation.mobile,
|
|
103
|
-
height,
|
|
104
|
-
width,
|
|
102
|
+
mobile: deviceMetrics.mobile,
|
|
105
103
|
deviceScaleFactor: 1,
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
height,
|
|
105
|
+
width: 0, // Leave width unchanged
|
|
108
106
|
});
|
|
109
107
|
|
|
110
108
|
// Now that the viewport is taller, give the page some time to fetch new resources that
|
|
@@ -127,7 +125,7 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
127
125
|
|
|
128
126
|
return {
|
|
129
127
|
data,
|
|
130
|
-
width,
|
|
128
|
+
width: deviceMetrics.width,
|
|
131
129
|
height,
|
|
132
130
|
};
|
|
133
131
|
}
|
|
@@ -185,29 +183,37 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
185
183
|
const session = context.driver.defaultSession;
|
|
186
184
|
const executionContext = context.driver.executionContext;
|
|
187
185
|
const settings = context.settings;
|
|
188
|
-
|
|
189
|
-
// In case some other program is controlling emulation, remember what the device looks
|
|
190
|
-
// like now and reset after gatherer is done.
|
|
191
|
-
let observedDeviceMetrics;
|
|
192
186
|
const lighthouseControlsEmulation = !settings.screenEmulation.disabled;
|
|
187
|
+
|
|
188
|
+
// Make a copy so we don't modify the config settings.
|
|
189
|
+
/** @type {{width: number, height: number, deviceScaleFactor: number, mobile: boolean}} */
|
|
190
|
+
const deviceMetrics = {...settings.screenEmulation};
|
|
191
|
+
|
|
192
|
+
// In case some other program is controlling emulation, remember what the device looks like now and reset after gatherer is done.
|
|
193
|
+
// If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
|
|
193
194
|
if (!lighthouseControlsEmulation) {
|
|
194
|
-
observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
|
|
195
|
+
const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
|
|
195
196
|
args: [],
|
|
196
197
|
useIsolation: true,
|
|
197
198
|
deps: [kebabCaseToCamelCase],
|
|
198
199
|
});
|
|
200
|
+
deviceMetrics.height = observedDeviceMetrics.height;
|
|
201
|
+
deviceMetrics.width = observedDeviceMetrics.width;
|
|
202
|
+
deviceMetrics.deviceScaleFactor = observedDeviceMetrics.deviceScaleFactor;
|
|
203
|
+
// If screen emulation is disabled, use formFactor to determine if we are on mobile.
|
|
204
|
+
deviceMetrics.mobile = settings.formFactor === 'mobile';
|
|
199
205
|
}
|
|
200
206
|
|
|
201
207
|
try {
|
|
202
208
|
return {
|
|
203
|
-
screenshot: await this._takeScreenshot(context),
|
|
209
|
+
screenshot: await this._takeScreenshot(context, deviceMetrics),
|
|
204
210
|
nodes: await this._resolveNodes(context),
|
|
205
211
|
};
|
|
206
212
|
} finally {
|
|
207
213
|
// Revert resized page.
|
|
208
214
|
if (lighthouseControlsEmulation) {
|
|
209
215
|
await emulation.emulate(session, settings);
|
|
210
|
-
} else
|
|
216
|
+
} else {
|
|
211
217
|
// Best effort to reset emulation to what it was.
|
|
212
218
|
// https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
|
|
213
219
|
// TODO: seems like this would be brittle. Should at least work for devtools, but what
|
|
@@ -216,8 +222,10 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
216
222
|
// and then just call that to reset?
|
|
217
223
|
// https://github.com/GoogleChrome/lighthouse/issues/11122
|
|
218
224
|
await session.sendCommand('Emulation.setDeviceMetricsOverride', {
|
|
219
|
-
mobile:
|
|
220
|
-
|
|
225
|
+
mobile: deviceMetrics.mobile,
|
|
226
|
+
deviceScaleFactor: deviceMetrics.deviceScaleFactor,
|
|
227
|
+
height: deviceMetrics.height,
|
|
228
|
+
width: 0, // Leave width unchanged
|
|
221
229
|
});
|
|
222
230
|
}
|
|
223
231
|
}
|
|
@@ -313,6 +313,20 @@ async function saveLanternNetworkData(devtoolsLog, outputPath) {
|
|
|
313
313
|
fs.writeFileSync(outputPath, JSON.stringify(lanternData));
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Normalize timing data so it doesn't change every update.
|
|
318
|
+
* @param {LH.Result.MeasureEntry[]} timings
|
|
319
|
+
*/
|
|
320
|
+
function normalizeTimingEntries(timings) {
|
|
321
|
+
let baseTime = 0;
|
|
322
|
+
for (const timing of timings) {
|
|
323
|
+
// @ts-expect-error: Value actually is writeable at this point.
|
|
324
|
+
timing.startTime = baseTime++;
|
|
325
|
+
// @ts-expect-error: Value actually is writeable at this point.
|
|
326
|
+
timing.duration = 1;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
316
330
|
module.exports = {
|
|
317
331
|
saveArtifacts,
|
|
318
332
|
saveLhr,
|
|
@@ -323,4 +337,5 @@ module.exports = {
|
|
|
323
337
|
saveDevtoolsLog,
|
|
324
338
|
saveLanternNetworkData,
|
|
325
339
|
stringifyReplacer,
|
|
340
|
+
normalizeTimingEntries,
|
|
326
341
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const isDeepEqual = require('lodash
|
|
8
|
+
const {isEqual: isDeepEqual} = require('lodash');
|
|
9
9
|
const Driver = require('./gather/driver.js');
|
|
10
10
|
const GatherRunner = require('./gather/gather-runner.js');
|
|
11
11
|
const ReportScoring = require('./scoring.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.5.0",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"build-extension": "yarn build-extension-chrome && yarn build-extension-firefox",
|
|
21
21
|
"build-extension-chrome": "node ./build/build-extension.js chrome",
|
|
22
22
|
"build-extension-firefox": "node ./build/build-extension.js firefox",
|
|
23
|
-
"build-devtools": "yarn reset-link && node ./build/build-bundle.js clients/devtools-entry.js dist/lighthouse-dt-bundle.js && node ./build/build-dt-report-resources.js",
|
|
23
|
+
"build-devtools": "yarn reset-link && node ./build/build-bundle.js clients/devtools/devtools-entry.js dist/lighthouse-dt-bundle.js && node ./build/build-dt-report-resources.js",
|
|
24
24
|
"build-smokehouse-bundle": "node ./build/build-smokehouse-bundle.js",
|
|
25
25
|
"build-lr": "yarn reset-link && node ./build/build-lightrider-bundles.js",
|
|
26
26
|
"build-pack": "bash build/build-pack.sh",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"update:sample-artifacts": "node lighthouse-core/scripts/update-report-fixtures.js",
|
|
77
77
|
"update:sample-json": "yarn i18n:collect-strings && node ./lighthouse-cli -A=./lighthouse-core/test/results/artifacts --config-path=./lighthouse-core/test/results/sample-config.js --output=json --output-path=./lighthouse-core/test/results/sample_v2.json && node lighthouse-core/scripts/cleanup-LHR-for-diff.js ./lighthouse-core/test/results/sample_v2.json --only-remove-timing",
|
|
78
78
|
"update:flow-sample-json": "node ./lighthouse-core/scripts/update-flow-fixtures.js",
|
|
79
|
+
"update:snapshot-sample-json": "node ./lighthouse-core/scripts/update-snapshot-sample.js",
|
|
79
80
|
"update:test-devtools": "bash lighthouse-core/test/chromium-web-tests/test-locally.sh --reset-results",
|
|
80
81
|
"test-devtools": "bash lighthouse-core/test/chromium-web-tests/test-locally.sh",
|
|
81
82
|
"open-devtools": "bash lighthouse-core/scripts/open-devtools.sh",
|
|
@@ -115,10 +116,7 @@
|
|
|
115
116
|
"@types/jest": "^27.0.3",
|
|
116
117
|
"@types/jpeg-js": "^0.3.7",
|
|
117
118
|
"@types/jsdom": "^16.2.13",
|
|
118
|
-
"@types/lodash
|
|
119
|
-
"@types/lodash.get": "^4.4.6",
|
|
120
|
-
"@types/lodash.isequal": "^4.5.2",
|
|
121
|
-
"@types/lodash.set": "^4.3.6",
|
|
119
|
+
"@types/lodash": "^4.14.178",
|
|
122
120
|
"@types/node": "*",
|
|
123
121
|
"@types/pako": "^1.0.1",
|
|
124
122
|
"@types/resize-observer-browser": "^0.1.1",
|
|
@@ -140,7 +138,7 @@
|
|
|
140
138
|
"cpy": "^8.1.2",
|
|
141
139
|
"cross-env": "^7.0.2",
|
|
142
140
|
"csv-validator": "^0.0.3",
|
|
143
|
-
"devtools-protocol": "0.0.
|
|
141
|
+
"devtools-protocol": "0.0.975298",
|
|
144
142
|
"es-main": "^1.0.2",
|
|
145
143
|
"eslint": "^8.4.1",
|
|
146
144
|
"eslint-config-google": "^0.14.0",
|
|
@@ -195,10 +193,7 @@
|
|
|
195
193
|
"js-library-detector": "^6.4.0",
|
|
196
194
|
"lighthouse-logger": "^1.3.0",
|
|
197
195
|
"lighthouse-stack-packs": "^1.7.0",
|
|
198
|
-
"lodash
|
|
199
|
-
"lodash.get": "^4.4.2",
|
|
200
|
-
"lodash.isequal": "^4.5.0",
|
|
201
|
-
"lodash.set": "^4.3.2",
|
|
196
|
+
"lodash": "^4.17.21",
|
|
202
197
|
"lookup-closest-locale": "6.2.0",
|
|
203
198
|
"metaviewport-parser": "0.2.0",
|
|
204
199
|
"open": "^8.4.0",
|
|
@@ -213,7 +208,7 @@
|
|
|
213
208
|
"yargs-parser": "^21.0.0"
|
|
214
209
|
},
|
|
215
210
|
"resolutions": {
|
|
216
|
-
"puppeteer/**/devtools-protocol": "0.0.
|
|
211
|
+
"puppeteer/**/devtools-protocol": "0.0.975298"
|
|
217
212
|
},
|
|
218
213
|
"repository": "GoogleChrome/lighthouse",
|
|
219
214
|
"keywords": [
|
|
@@ -445,7 +445,7 @@ function createScorescaleComponent(dom) {
|
|
|
445
445
|
function createScoresWrapperComponent(dom) {
|
|
446
446
|
const el0 = dom.createFragment();
|
|
447
447
|
const el1 = dom.createElement('style');
|
|
448
|
-
el1.append('\n .lh-scores-container {\n display: flex;\n flex-direction: column;\n padding: var(--default-padding) 0;\n position: relative;\n width: 100%;\n }\n\n .lh-sticky-header {\n --gauge-circle-size: var(--gauge-circle-size-sm);\n --plugin-badge-size: 16px;\n --plugin-icon-size: 75%;\n --gauge-wrapper-width: 60px;\n --gauge-percentage-font-size: 13px;\n position: fixed;\n left: 0;\n right: 0;\n top: var(--topbar-height);\n font-weight: 500;\n display: none;\n justify-content: center;\n background-color: var(--sticky-header-background-color);\n border-bottom: 1px solid var(--color-gray-200);\n padding-top: var(--score-container-padding);\n padding-bottom: 4px;\n z-index: 1;\n pointer-events: none;\n }\n\n .lh-devtools .lh-sticky-header {\n /* The report within DevTools is placed in a container with overflow, which changes the placement of this header unless we change `position` to `sticky.` */\n position: sticky;\n }\n\n .lh-sticky-header--visible {\n display: grid;\n grid-auto-flow: column;\n pointer-events: auto;\n }\n\n /* Disable the gauge arc animation for the sticky header, so toggling display: none\n does not play the animation. */\n .lh-sticky-header .lh-gauge-arc {\n animation: none;\n }\n\n .lh-sticky-header .lh-gauge__label {\n display: none;\n }\n\n .lh-highlighter {\n width: var(--gauge-wrapper-width);\n height: 1px;\n background-color: var(--highlighter-background-color);\n /* Position at bottom of first gauge in sticky header. */\n position: absolute;\n grid-column: 1;\n bottom: -1px;\n }\n\n .lh-gauge__wrapper:first-of-type {\n contain: none;\n }\n ');
|
|
448
|
+
el1.append('\n .lh-scores-container {\n display: flex;\n flex-direction: column;\n padding: var(--default-padding) 0;\n position: relative;\n width: 100%;\n }\n\n .lh-sticky-header {\n --gauge-circle-size: var(--gauge-circle-size-sm);\n --plugin-badge-size: 16px;\n --plugin-icon-size: 75%;\n --gauge-wrapper-width: 60px;\n --gauge-percentage-font-size: 13px;\n position: fixed;\n left: 0;\n right: 0;\n top: var(--topbar-height);\n font-weight: 500;\n display: none;\n justify-content: center;\n background-color: var(--sticky-header-background-color);\n border-bottom: 1px solid var(--color-gray-200);\n padding-top: var(--score-container-padding);\n padding-bottom: 4px;\n z-index: 1;\n pointer-events: none;\n }\n\n .lh-devtools .lh-sticky-header {\n /* The report within DevTools is placed in a container with overflow, which changes the placement of this header unless we change `position` to `sticky.` */\n position: sticky;\n }\n\n .lh-sticky-header--visible {\n display: grid;\n grid-auto-flow: column;\n pointer-events: auto;\n }\n\n /* Disable the gauge arc animation for the sticky header, so toggling display: none\n does not play the animation. */\n .lh-sticky-header .lh-gauge-arc {\n animation: none;\n }\n\n .lh-sticky-header .lh-gauge__label,\n .lh-sticky-header .lh-fraction__label {\n display: none;\n }\n\n .lh-highlighter {\n width: var(--gauge-wrapper-width);\n height: 1px;\n background-color: var(--highlighter-background-color);\n /* Position at bottom of first gauge in sticky header. */\n position: absolute;\n grid-column: 1;\n bottom: -1px;\n }\n\n .lh-gauge__wrapper:first-of-type {\n contain: none;\n }\n ');
|
|
449
449
|
el0.append(el1);
|
|
450
450
|
const el2 = dom.createElement('div', 'lh-scores-wrapper');
|
|
451
451
|
const el3 = dom.createElement('div', 'lh-scores-container');
|
|
@@ -306,7 +306,8 @@ export class TopbarFeatures {
|
|
|
306
306
|
categoriesAboveTheMiddle.length > 0 ? categoriesAboveTheMiddle.length - 1 : 0;
|
|
307
307
|
|
|
308
308
|
// Category order matches gauge order in sticky header.
|
|
309
|
-
const gaugeWrapperEls =
|
|
309
|
+
const gaugeWrapperEls =
|
|
310
|
+
this.stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper, .lh-fraction__wrapper');
|
|
310
311
|
const gaugeToHighlight = gaugeWrapperEls[highlightIndex];
|
|
311
312
|
const origin = gaugeWrapperEls[0].getBoundingClientRect().left;
|
|
312
313
|
const offset = gaugeToHighlight.getBoundingClientRect().left - origin;
|
|
@@ -38,6 +38,7 @@ Array [
|
|
|
38
38
|
"blockedByResponseIssueDetails",
|
|
39
39
|
"clientHintIssueDetails",
|
|
40
40
|
"contentSecurityPolicyIssueDetails",
|
|
41
|
+
"cookieIssueDetails",
|
|
41
42
|
"corsIssueDetails",
|
|
42
43
|
"deprecationIssueDetails",
|
|
43
44
|
"federatedAuthRequestIssueDetails",
|
|
@@ -47,7 +48,6 @@ Array [
|
|
|
47
48
|
"mixedContentIssueDetails",
|
|
48
49
|
"navigatorUserAgentIssueDetails",
|
|
49
50
|
"quirksModeIssueDetails",
|
|
50
|
-
"sameSiteCookieIssueDetails",
|
|
51
51
|
"sharedArrayBufferIssueDetails",
|
|
52
52
|
"twaQualityEnforcementDetails",
|
|
53
53
|
]
|