lighthouse 9.5.0-dev.20230111 → 9.5.0-dev.20230113
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/cli/bin.js +6 -6
- package/cli/cli-flags.js +5 -1
- package/cli/run.js +1 -1
- package/cli/test/smokehouse/core-tests.js +2 -0
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +9 -9
- package/cli/test/smokehouse/lighthouse-runners/cli.js +7 -7
- package/cli/test/smokehouse/lighthouse-runners/devtools.js +3 -3
- package/cli/test/smokehouse/readme.md +1 -1
- package/cli/test/smokehouse/report-assert.js +11 -7
- package/cli/test/smokehouse/smokehouse.js +13 -13
- package/core/audits/seo/is-crawlable.js +114 -42
- package/core/config/config-helpers.js +11 -11
- package/core/config/config-plugin.js +2 -2
- package/core/config/config.js +15 -15
- package/core/config/constants.js +1 -0
- package/core/config/default-config.js +2 -3
- package/core/config/desktop-config.js +1 -1
- package/core/config/experimental-config.js +1 -1
- package/core/config/filters.js +8 -4
- package/core/config/full-config.js +1 -1
- package/core/config/lr-desktop-config.js +1 -1
- package/core/config/lr-mobile-config.js +1 -1
- package/core/config/perf-config.js +1 -1
- package/core/config/validation.js +4 -4
- package/core/gather/gatherers/accessibility.js +15 -1
- package/core/gather/gatherers/full-page-screenshot.js +51 -40
- package/core/gather/gatherers/seo/tap-targets.js +39 -19
- package/core/gather/navigation-runner.js +1 -1
- package/core/gather/snapshot-runner.js +1 -1
- package/core/gather/timespan-runner.js +1 -1
- package/core/index.js +10 -10
- package/core/legacy/config/config.js +31 -33
- package/core/legacy/config/legacy-default-config.js +1 -1
- package/core/legacy/gather/driver.js +0 -17
- package/core/legacy/gather/gather-runner.js +0 -6
- package/core/runner.js +2 -0
- package/core/user-flow.js +3 -3
- package/core/util.cjs +43 -3
- package/dist/report/bundle.esm.js +53 -22
- package/dist/report/flow.js +5 -5
- package/dist/report/standalone.js +4 -4
- package/flow-report/src/common.tsx +3 -3
- package/flow-report/src/util.ts +0 -11
- package/flow-report/test/common-test.tsx +4 -7
- package/flow-report/test/summary/summary-test.tsx +1 -1
- package/package.json +1 -1
- package/readme.md +1 -0
- package/report/renderer/details-renderer.js +1 -2
- package/report/renderer/element-screenshot-renderer.js +4 -4
- package/report/renderer/report-renderer.js +3 -7
- package/report/renderer/report-ui-features.js +3 -7
- package/report/renderer/util.js +43 -3
- package/report/test/generator/report-generator-test.js +1 -1
- package/report/test/renderer/util-test.js +22 -0
- package/report/test-assets/faux-psi.js +3 -3
- package/types/artifacts.d.ts +1 -11
- package/types/config.d.ts +18 -18
- package/types/lhr/audit-details.d.ts +0 -17
- package/types/lhr/lhr.d.ts +13 -0
- package/types/lhr/settings.d.ts +2 -0
- package/types/smokehouse.d.ts +4 -3
- package/types/user-flow.d.ts +1 -1
- package/core/audits/full-page-screenshot.js +0 -42
package/core/config/config.js
CHANGED
|
@@ -38,19 +38,19 @@ const defaultConfigPath = path.join(
|
|
|
38
38
|
);
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* @param {LH.Config
|
|
41
|
+
* @param {LH.Config|undefined} config
|
|
42
42
|
* @param {{configPath?: string}} context
|
|
43
|
-
* @return {{configWorkingCopy: LH.Config
|
|
43
|
+
* @return {{configWorkingCopy: LH.Config, configDir?: string, configPath?: string}}
|
|
44
44
|
*/
|
|
45
|
-
function resolveWorkingCopy(
|
|
45
|
+
function resolveWorkingCopy(config, context) {
|
|
46
46
|
let {configPath} = context;
|
|
47
47
|
|
|
48
48
|
if (configPath && !path.isAbsolute(configPath)) {
|
|
49
49
|
throw new Error('configPath must be an absolute path');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (!
|
|
53
|
-
|
|
52
|
+
if (!config) {
|
|
53
|
+
config = defaultConfig;
|
|
54
54
|
configPath = defaultConfigPath;
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -58,24 +58,24 @@ function resolveWorkingCopy(configJSON, context) {
|
|
|
58
58
|
const configDir = configPath ? path.dirname(configPath) : undefined;
|
|
59
59
|
|
|
60
60
|
return {
|
|
61
|
-
configWorkingCopy: deepCloneConfigJson(
|
|
61
|
+
configWorkingCopy: deepCloneConfigJson(config),
|
|
62
62
|
configPath,
|
|
63
63
|
configDir,
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* @param {LH.Config
|
|
69
|
-
* @return {LH.Config
|
|
68
|
+
* @param {LH.Config} config
|
|
69
|
+
* @return {LH.Config}
|
|
70
70
|
*/
|
|
71
|
-
function resolveExtensions(
|
|
72
|
-
if (!
|
|
71
|
+
function resolveExtensions(config) {
|
|
72
|
+
if (!config.extends) return config;
|
|
73
73
|
|
|
74
|
-
if (
|
|
74
|
+
if (config.extends !== 'lighthouse:default') {
|
|
75
75
|
throw new Error('`lighthouse:default` is the only valid extension method.');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const {artifacts, ...extensionJSON} =
|
|
78
|
+
const {artifacts, ...extensionJSON} = config;
|
|
79
79
|
const defaultClone = deepCloneConfigJson(defaultConfig);
|
|
80
80
|
const mergedConfig = mergeConfigFragment(defaultClone, extensionJSON);
|
|
81
81
|
|
|
@@ -236,15 +236,15 @@ function resolveFakeNavigations(artifactDefns, settings) {
|
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
238
|
* @param {LH.Gatherer.GatherMode} gatherMode
|
|
239
|
-
* @param {LH.Config
|
|
239
|
+
* @param {LH.Config=} config
|
|
240
240
|
* @param {LH.Flags=} flags
|
|
241
241
|
* @return {Promise<{resolvedConfig: LH.Config.ResolvedConfig, warnings: string[]}>}
|
|
242
242
|
*/
|
|
243
|
-
async function initializeConfig(gatherMode,
|
|
243
|
+
async function initializeConfig(gatherMode, config, flags = {}) {
|
|
244
244
|
const status = {msg: 'Initialize config', id: 'lh:config'};
|
|
245
245
|
log.time(status, 'verbose');
|
|
246
246
|
|
|
247
|
-
let {configWorkingCopy, configDir} = resolveWorkingCopy(
|
|
247
|
+
let {configWorkingCopy, configDir} = resolveWorkingCopy(config, flags);
|
|
248
248
|
|
|
249
249
|
configWorkingCopy = resolveExtensions(configWorkingCopy);
|
|
250
250
|
configWorkingCopy = await mergePlugins(configWorkingCopy, configDir, flags);
|
package/core/config/constants.js
CHANGED
|
@@ -170,7 +170,7 @@ for (const key of Object.keys(artifacts)) {
|
|
|
170
170
|
artifacts[/** @type {keyof typeof artifacts} */ (key)] = key;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
/** @type {LH.Config
|
|
173
|
+
/** @type {LH.Config} */
|
|
174
174
|
const defaultConfig = {
|
|
175
175
|
settings: constants.defaultSettings,
|
|
176
176
|
artifacts: [
|
|
@@ -278,7 +278,6 @@ const defaultConfig = {
|
|
|
278
278
|
'valid-source-maps',
|
|
279
279
|
'preload-lcp-image',
|
|
280
280
|
'csp-xss',
|
|
281
|
-
'full-page-screenshot',
|
|
282
281
|
'script-treemap-data',
|
|
283
282
|
'manual/pwa-cross-browser',
|
|
284
283
|
'manual/pwa-page-transitions',
|
|
@@ -517,7 +516,6 @@ const defaultConfig = {
|
|
|
517
516
|
{id: 'non-composited-animations', weight: 0},
|
|
518
517
|
{id: 'unsized-images', weight: 0},
|
|
519
518
|
{id: 'viewport', weight: 0},
|
|
520
|
-
{id: 'no-unload-listeners', weight: 0},
|
|
521
519
|
{id: 'uses-responsive-images-snapshot', weight: 0},
|
|
522
520
|
{id: 'work-during-interaction', weight: 0},
|
|
523
521
|
{id: 'bf-cache', weight: 0},
|
|
@@ -623,6 +621,7 @@ const defaultConfig = {
|
|
|
623
621
|
{id: 'doctype', weight: 1, group: 'best-practices-browser-compat'},
|
|
624
622
|
{id: 'charset', weight: 1, group: 'best-practices-browser-compat'},
|
|
625
623
|
// General Group
|
|
624
|
+
{id: 'no-unload-listeners', weight: 1, group: 'best-practices-general'},
|
|
626
625
|
{id: 'js-libraries', weight: 0, group: 'best-practices-general'},
|
|
627
626
|
{id: 'deprecations', weight: 1, group: 'best-practices-general'},
|
|
628
627
|
{id: 'errors-in-console', weight: 1, group: 'best-practices-general'},
|
package/core/config/filters.js
CHANGED
|
@@ -27,12 +27,13 @@ const baseArtifactKeys = Object.keys(baseArtifactKeySource);
|
|
|
27
27
|
|
|
28
28
|
// Some audits are used by the report for additional information.
|
|
29
29
|
// Keep these audits unless they are *directly* skipped with `skipAudits`.
|
|
30
|
-
|
|
30
|
+
/** @type {string[]} */
|
|
31
|
+
const filterResistantAuditIds = [];
|
|
31
32
|
|
|
32
33
|
// Some artifacts are used by the report for additional information.
|
|
33
34
|
// Always run these artifacts even if audits do not request them.
|
|
34
35
|
// These are similar to base artifacts but they cannot be run in all 3 modes.
|
|
35
|
-
const filterResistantArtifactIds = ['Stacks', 'NetworkUserAgent'];
|
|
36
|
+
const filterResistantArtifactIds = ['Stacks', 'NetworkUserAgent', 'FullPageScreenshot'];
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Returns the set of audit IDs used in the list of categories.
|
|
@@ -302,7 +303,7 @@ function filterConfigByExplicitFilters(resolvedConfig, filters) {
|
|
|
302
303
|
[
|
|
303
304
|
...baseAuditIds, // Start with our base audits.
|
|
304
305
|
...(onlyAudits || []), // Additionally include the opt-in audits from `onlyAudits`.
|
|
305
|
-
...filterResistantAuditIds, // Always include
|
|
306
|
+
...filterResistantAuditIds, // Always include any filter-resistant audits.
|
|
306
307
|
].filter(auditId => !skipAudits || !skipAudits.includes(auditId))
|
|
307
308
|
);
|
|
308
309
|
|
|
@@ -313,7 +314,10 @@ function filterConfigByExplicitFilters(resolvedConfig, filters) {
|
|
|
313
314
|
const availableCategories =
|
|
314
315
|
filterCategoriesByAvailableAudits(resolvedConfig.categories, audits || []);
|
|
315
316
|
const categories = filterCategoriesByExplicitFilters(availableCategories, onlyCategories);
|
|
316
|
-
|
|
317
|
+
let artifacts = filterArtifactsByAvailableAudits(resolvedConfig.artifacts, audits);
|
|
318
|
+
if (artifacts && resolvedConfig.settings.disableFullPageScreenshot) {
|
|
319
|
+
artifacts = artifacts.filter(({id}) => id !== 'FullPageScreenshot');
|
|
320
|
+
}
|
|
317
321
|
const navigations =
|
|
318
322
|
filterNavigationsByAvailableArtifacts(resolvedConfig.navigations, artifacts || []);
|
|
319
323
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
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
5
|
*/
|
|
6
6
|
|
|
7
|
-
/** @type {LH.Config
|
|
7
|
+
/** @type {LH.Config} */
|
|
8
8
|
const fullConfig = {
|
|
9
9
|
extends: 'lighthouse:default',
|
|
10
10
|
settings: {},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
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
5
|
*/
|
|
6
6
|
|
|
7
|
-
/** @type {LH.Config
|
|
7
|
+
/** @type {LH.Config} */
|
|
8
8
|
const config = {
|
|
9
9
|
extends: 'lighthouse:default',
|
|
10
10
|
settings: {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
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
5
|
*/
|
|
6
6
|
|
|
7
|
-
/** @type {LH.Config
|
|
7
|
+
/** @type {LH.Config} */
|
|
8
8
|
const perfConfig = {
|
|
9
9
|
extends: 'lighthouse:default',
|
|
10
10
|
settings: {
|
|
@@ -39,16 +39,16 @@ function isValidArtifactDependency(dependent, dependency) {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Throws if pluginName is invalid or (somehow) collides with a category in the
|
|
42
|
-
*
|
|
43
|
-
* @param {LH.Config
|
|
42
|
+
* config being added to.
|
|
43
|
+
* @param {LH.Config} config
|
|
44
44
|
* @param {string} pluginName
|
|
45
45
|
*/
|
|
46
|
-
function assertValidPluginName(
|
|
46
|
+
function assertValidPluginName(config, pluginName) {
|
|
47
47
|
if (!pluginName.startsWith('lighthouse-plugin-')) {
|
|
48
48
|
throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
if (
|
|
51
|
+
if (config.categories?.[pluginName]) {
|
|
52
52
|
throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -81,6 +81,19 @@ async function runA11yChecks() {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
async function runA11yChecksAndResetScroll() {
|
|
85
|
+
const originalScrollPosition = {
|
|
86
|
+
x: window.scrollX,
|
|
87
|
+
y: window.scrollY,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
return await runA11yChecks();
|
|
92
|
+
} finally {
|
|
93
|
+
window.scrollTo(originalScrollPosition.x, originalScrollPosition.y);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
/**
|
|
85
98
|
* @param {import('axe-core/axe').Result} result
|
|
86
99
|
* @return {LH.Artifacts.AxeRuleResult}
|
|
@@ -168,13 +181,14 @@ class Accessibility extends FRGatherer {
|
|
|
168
181
|
getArtifact(passContext) {
|
|
169
182
|
const driver = passContext.driver;
|
|
170
183
|
|
|
171
|
-
return driver.executionContext.evaluate(
|
|
184
|
+
return driver.executionContext.evaluate(runA11yChecksAndResetScroll, {
|
|
172
185
|
args: [],
|
|
173
186
|
useIsolation: true,
|
|
174
187
|
deps: [
|
|
175
188
|
axeSource,
|
|
176
189
|
pageFunctions.getNodeDetails,
|
|
177
190
|
createAxeRuleResultArtifact,
|
|
191
|
+
runA11yChecks,
|
|
178
192
|
],
|
|
179
193
|
});
|
|
180
194
|
}
|
|
@@ -47,7 +47,7 @@ function getObservedDeviceMetrics() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* The screenshot dimensions are sized to `window.outerHeight` / `window.
|
|
50
|
+
* The screenshot dimensions are sized to `window.outerHeight` / `window.outerWidth`,
|
|
51
51
|
* however the bounding boxes of the elements are relative to `window.innerHeight` / `window.innerWidth`.
|
|
52
52
|
*/
|
|
53
53
|
function getScreenshotAreaSize() {
|
|
@@ -74,9 +74,8 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
74
74
|
/**
|
|
75
75
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
76
76
|
* @param {{height: number, width: number, mobile: boolean}} deviceMetrics
|
|
77
|
-
* @return {Promise<LH.Artifacts.FullPageScreenshot['screenshot']>}
|
|
78
77
|
*/
|
|
79
|
-
async
|
|
78
|
+
async _resizeViewport(context, deviceMetrics) {
|
|
80
79
|
const session = context.driver.defaultSession;
|
|
81
80
|
const metrics = await session.sendCommand('Page.getLayoutMetrics');
|
|
82
81
|
|
|
@@ -118,8 +117,14 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
118
117
|
|
|
119
118
|
// Now that new resources are (probably) fetched, wait long enough for a layout.
|
|
120
119
|
await context.driver.executionContext.evaluate(waitForDoubleRaf, {args: []});
|
|
120
|
+
}
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
/**
|
|
123
|
+
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
124
|
+
* @return {Promise<LH.Result.FullPageScreenshot['screenshot']>}
|
|
125
|
+
*/
|
|
126
|
+
async _takeScreenshot(context) {
|
|
127
|
+
const result = await context.driver.defaultSession.sendCommand('Page.captureScreenshot', {
|
|
123
128
|
format: 'webp',
|
|
124
129
|
quality: FULL_PAGE_SCREENSHOT_QUALITY,
|
|
125
130
|
});
|
|
@@ -129,8 +134,8 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
129
134
|
await context.driver.executionContext.evaluate(getScreenshotAreaSize, {
|
|
130
135
|
args: [],
|
|
131
136
|
useIsolation: true,
|
|
132
|
-
deps: [kebabCaseToCamelCase],
|
|
133
137
|
});
|
|
138
|
+
|
|
134
139
|
return {
|
|
135
140
|
data,
|
|
136
141
|
width: screenshotAreaSize.width,
|
|
@@ -146,11 +151,11 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
146
151
|
* to re-collect the bounding client rectangle.
|
|
147
152
|
* @see pageFunctions.getNodeDetails
|
|
148
153
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
149
|
-
* @return {Promise<LH.
|
|
154
|
+
* @return {Promise<LH.Result.FullPageScreenshot['nodes']>}
|
|
150
155
|
*/
|
|
151
156
|
async _resolveNodes(context) {
|
|
152
157
|
function resolveNodes() {
|
|
153
|
-
/** @type {LH.
|
|
158
|
+
/** @type {LH.Result.FullPageScreenshot['nodes']} */
|
|
154
159
|
const nodes = {};
|
|
155
160
|
if (!window.__lighthouseNodesDontTouchOrAllVarianceGoesAway) return nodes;
|
|
156
161
|
|
|
@@ -197,44 +202,50 @@ class FullPageScreenshot extends FRGatherer {
|
|
|
197
202
|
/** @type {{width: number, height: number, deviceScaleFactor: number, mobile: boolean}} */
|
|
198
203
|
const deviceMetrics = {...settings.screenEmulation};
|
|
199
204
|
|
|
200
|
-
// In case some other program is controlling emulation, remember what the device looks like now and reset after gatherer is done.
|
|
201
|
-
// If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
|
|
202
|
-
if (!lighthouseControlsEmulation) {
|
|
203
|
-
const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
|
|
204
|
-
args: [],
|
|
205
|
-
useIsolation: true,
|
|
206
|
-
deps: [kebabCaseToCamelCase],
|
|
207
|
-
});
|
|
208
|
-
deviceMetrics.height = observedDeviceMetrics.height;
|
|
209
|
-
deviceMetrics.width = observedDeviceMetrics.width;
|
|
210
|
-
deviceMetrics.deviceScaleFactor = observedDeviceMetrics.deviceScaleFactor;
|
|
211
|
-
// If screen emulation is disabled, use formFactor to determine if we are on mobile.
|
|
212
|
-
deviceMetrics.mobile = settings.formFactor === 'mobile';
|
|
213
|
-
}
|
|
214
|
-
|
|
215
205
|
try {
|
|
206
|
+
if (!settings.usePassiveGathering) {
|
|
207
|
+
// In case some other program is controlling emulation, remember what the device looks like now and reset after gatherer is done.
|
|
208
|
+
// If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
|
|
209
|
+
if (!lighthouseControlsEmulation) {
|
|
210
|
+
const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
|
|
211
|
+
args: [],
|
|
212
|
+
useIsolation: true,
|
|
213
|
+
deps: [kebabCaseToCamelCase],
|
|
214
|
+
});
|
|
215
|
+
deviceMetrics.height = observedDeviceMetrics.height;
|
|
216
|
+
deviceMetrics.width = observedDeviceMetrics.width;
|
|
217
|
+
deviceMetrics.deviceScaleFactor = observedDeviceMetrics.deviceScaleFactor;
|
|
218
|
+
// If screen emulation is disabled, use formFactor to determine if we are on mobile.
|
|
219
|
+
deviceMetrics.mobile = settings.formFactor === 'mobile';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await this._resizeViewport(context, deviceMetrics);
|
|
223
|
+
}
|
|
224
|
+
|
|
216
225
|
return {
|
|
217
|
-
screenshot: await this._takeScreenshot(context
|
|
226
|
+
screenshot: await this._takeScreenshot(context),
|
|
218
227
|
nodes: await this._resolveNodes(context),
|
|
219
228
|
};
|
|
220
229
|
} finally {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
230
|
+
if (!settings.usePassiveGathering) {
|
|
231
|
+
// Revert resized page.
|
|
232
|
+
if (lighthouseControlsEmulation) {
|
|
233
|
+
await emulation.emulate(session, settings);
|
|
234
|
+
} else {
|
|
235
|
+
// Best effort to reset emulation to what it was.
|
|
236
|
+
// https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
|
|
237
|
+
// TODO: seems like this would be brittle. Should at least work for devtools, but what
|
|
238
|
+
// about scripted puppeteer usages? Better to introduce a "setEmulation" callback
|
|
239
|
+
// in the LH runner api, which for ex. puppeteer consumers would setup puppeteer emulation,
|
|
240
|
+
// and then just call that to reset?
|
|
241
|
+
// https://github.com/GoogleChrome/lighthouse/issues/11122
|
|
242
|
+
await session.sendCommand('Emulation.setDeviceMetricsOverride', {
|
|
243
|
+
mobile: deviceMetrics.mobile,
|
|
244
|
+
deviceScaleFactor: deviceMetrics.deviceScaleFactor,
|
|
245
|
+
height: deviceMetrics.height,
|
|
246
|
+
width: 0, // Leave width unchanged
|
|
247
|
+
});
|
|
248
|
+
}
|
|
238
249
|
}
|
|
239
250
|
}
|
|
240
251
|
}
|
|
@@ -281,6 +281,24 @@ function gatherTapTargets(tapTargetsSelector, className) {
|
|
|
281
281
|
|
|
282
282
|
return targets;
|
|
283
283
|
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @param {string} tapTargetsSelector
|
|
287
|
+
* @param {string} className
|
|
288
|
+
* @return {LH.Artifacts.TapTarget[]}
|
|
289
|
+
*/
|
|
290
|
+
function gatherTapTargetsAndResetScroll(tapTargetsSelector, className) {
|
|
291
|
+
const originalScrollPosition = {
|
|
292
|
+
x: window.scrollX,
|
|
293
|
+
y: window.scrollY,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
return gatherTapTargets(tapTargetsSelector, className);
|
|
298
|
+
} finally {
|
|
299
|
+
window.scrollTo(originalScrollPosition.x, originalScrollPosition.y);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
284
302
|
/* c8 ignore stop */
|
|
285
303
|
|
|
286
304
|
class TapTargets extends FRGatherer {
|
|
@@ -337,25 +355,27 @@ class TapTargets extends FRGatherer {
|
|
|
337
355
|
const className = 'lighthouse-disable-pointer-events';
|
|
338
356
|
const styleSheetId = await this.addStyleRule(session, className);
|
|
339
357
|
|
|
340
|
-
const tapTargets =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
const tapTargets =
|
|
359
|
+
await passContext.driver.executionContext.evaluate(gatherTapTargetsAndResetScroll, {
|
|
360
|
+
args: [tapTargetsSelector, className],
|
|
361
|
+
useIsolation: true,
|
|
362
|
+
deps: [
|
|
363
|
+
pageFunctions.getNodeDetails,
|
|
364
|
+
pageFunctions.getElementsInDocument,
|
|
365
|
+
disableFixedAndStickyElementPointerEvents,
|
|
366
|
+
elementIsVisible,
|
|
367
|
+
elementHasAncestorTapTarget,
|
|
368
|
+
elementCenterIsAtZAxisTop,
|
|
369
|
+
getClientRects,
|
|
370
|
+
hasTextNodeSiblingsFormingTextBlock,
|
|
371
|
+
elementIsInTextBlock,
|
|
372
|
+
RectHelpers.getRectCenterPoint,
|
|
373
|
+
pageFunctions.getNodePath,
|
|
374
|
+
pageFunctions.getNodeSelector,
|
|
375
|
+
pageFunctions.getNodeLabel,
|
|
376
|
+
gatherTapTargets,
|
|
377
|
+
],
|
|
378
|
+
});
|
|
359
379
|
|
|
360
380
|
await this.removeStyleRule(session, styleSheetId);
|
|
361
381
|
|
|
@@ -311,7 +311,7 @@ async function _cleanup({requestedUrl, driver, resolvedConfig}) {
|
|
|
311
311
|
/**
|
|
312
312
|
* @param {LH.Puppeteer.Page|undefined} page
|
|
313
313
|
* @param {LH.NavigationRequestor|undefined} requestor
|
|
314
|
-
* @param {{config?: LH.Config
|
|
314
|
+
* @param {{config?: LH.Config, flags?: LH.Flags}} [options]
|
|
315
315
|
* @return {Promise<LH.Gatherer.FRGatherResult>}
|
|
316
316
|
*/
|
|
317
317
|
async function navigationGather(page, requestor, options = {}) {
|
|
@@ -14,7 +14,7 @@ import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {LH.Puppeteer.Page} page
|
|
17
|
-
* @param {{config?: LH.Config
|
|
17
|
+
* @param {{config?: LH.Config, flags?: LH.Flags}} [options]
|
|
18
18
|
* @return {Promise<LH.Gatherer.FRGatherResult>}
|
|
19
19
|
*/
|
|
20
20
|
async function snapshotGather(page, options = {}) {
|
|
@@ -15,7 +15,7 @@ import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @param {LH.Puppeteer.Page} page
|
|
18
|
-
* @param {{config?: LH.Config
|
|
18
|
+
* @param {{config?: LH.Config, flags?: LH.Flags}} [options]
|
|
19
19
|
* @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
|
|
20
20
|
*/
|
|
21
21
|
async function startTimespanGather(page, options = {}) {
|
package/core/index.js
CHANGED
|
@@ -37,13 +37,13 @@ import {navigationGather} from './gather/navigation-runner.js';
|
|
|
37
37
|
* @param {string=} url The URL to test. Optional if running in auditMode.
|
|
38
38
|
* @param {LH.Flags=} flags Optional settings for the Lighthouse run. If present,
|
|
39
39
|
* they will override any settings in the config.
|
|
40
|
-
* @param {LH.Config
|
|
40
|
+
* @param {LH.Config=} config Configuration for the Lighthouse run. If
|
|
41
41
|
* not present, the default config is used.
|
|
42
42
|
* @param {LH.Puppeteer.Page=} page
|
|
43
43
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
44
44
|
*/
|
|
45
|
-
async function lighthouse(url, flags = {},
|
|
46
|
-
return navigation(page, url, {config
|
|
45
|
+
async function lighthouse(url, flags = {}, config, page) {
|
|
46
|
+
return navigation(page, url, {config, flags});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -53,17 +53,17 @@ async function lighthouse(url, flags = {}, configJSON, page) {
|
|
|
53
53
|
* @param {string=} url The URL to test. Optional if running in auditMode.
|
|
54
54
|
* @param {LH.Flags=} flags Optional settings for the Lighthouse run. If present,
|
|
55
55
|
* they will override any settings in the config.
|
|
56
|
-
* @param {LH.Config
|
|
56
|
+
* @param {LH.Config=} config Configuration for the Lighthouse run. If
|
|
57
57
|
* not present, the default config is used.
|
|
58
58
|
* @param {Connection=} userConnection
|
|
59
59
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
60
60
|
*/
|
|
61
|
-
async function legacyNavigation(url, flags = {},
|
|
61
|
+
async function legacyNavigation(url, flags = {}, config, userConnection) {
|
|
62
62
|
// set logging preferences, assume quiet
|
|
63
63
|
flags.logLevel = flags.logLevel || 'error';
|
|
64
64
|
log.setLevel(flags.logLevel);
|
|
65
65
|
|
|
66
|
-
const resolvedConfig = await LegacyResolvedConfig.fromJson(
|
|
66
|
+
const resolvedConfig = await LegacyResolvedConfig.fromJson(config, flags);
|
|
67
67
|
const computedCache = new Map();
|
|
68
68
|
const options = {resolvedConfig, computedCache};
|
|
69
69
|
const connection = userConnection || new CriConnection(flags.port, flags.hostname);
|
|
@@ -87,7 +87,7 @@ async function startFlow(page, options) {
|
|
|
87
87
|
/**
|
|
88
88
|
* @param {LH.Puppeteer.Page|undefined} page
|
|
89
89
|
* @param {LH.NavigationRequestor|undefined} requestor
|
|
90
|
-
* @param {{config?: LH.Config
|
|
90
|
+
* @param {{config?: LH.Config, flags?: LH.Flags}} [options]
|
|
91
91
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
92
92
|
*/
|
|
93
93
|
async function navigation(page, requestor, options) {
|
|
@@ -97,7 +97,7 @@ async function navigation(page, requestor, options) {
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* @param {LH.Puppeteer.Page} page
|
|
100
|
-
* @param {{config?: LH.Config
|
|
100
|
+
* @param {{config?: LH.Config, flags?: LH.Flags}} [options]
|
|
101
101
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
102
102
|
*/
|
|
103
103
|
async function snapshot(page, options) {
|
|
@@ -107,7 +107,7 @@ async function snapshot(page, options) {
|
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* @param {LH.Puppeteer.Page} page
|
|
110
|
-
* @param {{config?: LH.Config
|
|
110
|
+
* @param {{config?: LH.Config, flags?: LH.Flags}} [options]
|
|
111
111
|
* @return {Promise<{endTimespan: () => Promise<LH.RunnerResult|undefined>}>}
|
|
112
112
|
*/
|
|
113
113
|
async function startTimespan(page, options) {
|
|
@@ -138,7 +138,7 @@ function generateReport(result, format = 'html') {
|
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
140
|
* @param {LH.UserFlow.FlowArtifacts} flowArtifacts
|
|
141
|
-
* @param {LH.Config
|
|
141
|
+
* @param {LH.Config} [config]
|
|
142
142
|
*/
|
|
143
143
|
async function auditFlowArtifacts(flowArtifacts, config) {
|
|
144
144
|
const {gatherSteps, name} = flowArtifacts;
|