diff --git a/cli/test/cli/__snapshots__/index-test.js.snap b/cli/test/cli/__snapshots__/index-test.js.snap index 9ec56e1989d7..5b77c9ef87c5 100644 --- a/cli/test/cli/__snapshots__/index-test.js.snap +++ b/cli/test/cli/__snapshots__/index-test.js.snap @@ -160,9 +160,11 @@ Object { "settings": Object { "additionalTraceCategories": null, "auditMode": true, + "blankPage": "about:blank", "blockedUrlPatterns": null, "budgets": null, "channel": "cli", + "cpuQuietThresholdMs": 1000, "debugNavigation": false, "disableStorageReset": false, "emulatedUserAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4695.0 Mobile Safari/537.36 Chrome-Lighthouse", @@ -172,6 +174,7 @@ Object { "locale": "en-US", "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "networkQuietThresholdMs": 1000, "onlyAudits": Array [ "metrics", ], @@ -179,6 +182,8 @@ Object { "output": Array [ "json", ], + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, "precomputedLanternData": null, "screenEmulation": Object { "deviceScaleFactor": 2.625, @@ -333,9 +338,11 @@ Object { "settings": Object { "additionalTraceCategories": null, "auditMode": true, + "blankPage": "about:blank", "blockedUrlPatterns": null, "budgets": null, "channel": "cli", + "cpuQuietThresholdMs": 1000, "debugNavigation": false, "disableStorageReset": false, "emulatedUserAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4695.0 Mobile Safari/537.36 Chrome-Lighthouse", @@ -345,6 +352,7 @@ Object { "locale": "en-US", "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "networkQuietThresholdMs": 1000, "onlyAudits": Array [ "charset", ], @@ -352,6 +360,8 @@ Object { "output": Array [ "json", ], + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, "precomputedLanternData": null, "screenEmulation": Object { "deviceScaleFactor": 2.625, diff --git a/cli/test/smokehouse/smokehouse.js b/cli/test/smokehouse/smokehouse.js index cf4543c329df..39abca3aae8c 100644 --- a/cli/test/smokehouse/smokehouse.js +++ b/cli/test/smokehouse/smokehouse.js @@ -139,11 +139,17 @@ function purpleify(str) { */ function convertToLegacyConfig(configJson) { if (!configJson) return configJson; - if (!configJson.navigations) return configJson; return { ...configJson, - passes: configJson.navigations.map(nav => ({...nav, passName: nav.id.concat('Pass')})), + passes: [{ + passName: 'defaultPass', + pauseAfterFcpMs: configJson.settings?.pauseAfterFcpMs, + pauseAfterLoadMs: configJson.settings?.pauseAfterLoadMs, + networkQuietThresholdMs: configJson.settings?.networkQuietThresholdMs, + cpuQuietThresholdMs: configJson.settings?.cpuQuietThresholdMs, + blankPage: configJson.settings?.blankPage, + }], }; } diff --git a/cli/test/smokehouse/test-definitions/oopif-requests.js b/cli/test/smokehouse/test-definitions/oopif-requests.js index 08be666c87ea..f1d19a1733d2 100644 --- a/cli/test/smokehouse/test-definitions/oopif-requests.js +++ b/cli/test/smokehouse/test-definitions/oopif-requests.js @@ -26,13 +26,10 @@ const config = { // Be a little more forgiving on how long it takes all network requests of several nested iframes // to complete. maxWaitForLoad: 180000, - }, - navigations: [{ - id: 'default', // CI machines are pretty weak which lead to many more long tasks than normal. // Reduce our requirement for CPU quiet. cpuQuietThresholdMs: 500, - }], + }, }; /** diff --git a/cli/test/smokehouse/test-definitions/oopif-scripts.js b/cli/test/smokehouse/test-definitions/oopif-scripts.js index 717314370437..39d544e4895a 100644 --- a/cli/test/smokehouse/test-definitions/oopif-scripts.js +++ b/cli/test/smokehouse/test-definitions/oopif-scripts.js @@ -26,15 +26,10 @@ const config = { // Be a little more forgiving on how long it takes all network requests of several nested iframes // to complete. maxWaitForLoad: 180000, - }, - navigations: [ // CI machines are pretty weak which lead to many more long tasks than normal. // Reduce our requirement for CPU quiet. - { - id: 'default', - cpuQuietThresholdMs: 500, - }, - ], + cpuQuietThresholdMs: 500, + }, }; /** diff --git a/core/config/constants.js b/core/config/constants.js index 35c3ae89afc0..488ce725e0f2 100644 --- a/core/config/constants.js +++ b/core/config/constants.js @@ -95,6 +95,10 @@ const defaultSettings = { output: 'json', maxWaitForFcp: 30 * 1000, maxWaitForLoad: 45 * 1000, + pauseAfterFcpMs: 1000, + pauseAfterLoadMs: 1000, + networkQuietThresholdMs: 1000, + cpuQuietThresholdMs: 1000, formFactor: 'mobile', throttling: throttling.mobileSlow4G, @@ -107,6 +111,8 @@ const defaultSettings = { disableStorageReset: false, debugNavigation: false, channel: 'node', + skipAboutBlank: false, + blankPage: 'about:blank', // the following settings have no defaults but we still want ensure that `key in settings` // in config will work in a typechecked way @@ -119,7 +125,6 @@ const defaultSettings = { onlyAudits: null, onlyCategories: null, skipAudits: null, - skipAboutBlank: false, }; /** @type {LH.Config.Pass} */ diff --git a/core/config/default-config.js b/core/config/default-config.js index 7518abdc3e56..aae8ee4ec363 100644 --- a/core/config/default-config.js +++ b/core/config/default-config.js @@ -221,62 +221,6 @@ const defaultConfig = { // FullPageScreenshot comes at the very end so all other node analysis is captured. {id: artifacts.FullPageScreenshot, gatherer: 'full-page-screenshot'}, ], - navigations: [ - { - id: 'default', - pauseAfterFcpMs: 1000, - pauseAfterLoadMs: 1000, - networkQuietThresholdMs: 1000, - cpuQuietThresholdMs: 1000, - artifacts: [ - // Artifacts which can be depended on come first. - artifacts.DevtoolsLog, - artifacts.Trace, - - artifacts.Accessibility, - artifacts.AnchorElements, - artifacts.CacheContents, - artifacts.ConsoleMessages, - artifacts.CSSUsage, - artifacts.Doctype, - artifacts.DOMStats, - artifacts.EmbeddedContent, - artifacts.FontSize, - artifacts.Inputs, - artifacts.GlobalListeners, - artifacts.IFrameElements, - artifacts.ImageElements, - artifacts.InstallabilityErrors, - artifacts.InspectorIssues, - artifacts.JsUsage, - artifacts.LinkElements, - artifacts.MainDocumentContent, - artifacts.MetaElements, - artifacts.NetworkUserAgent, - artifacts.OptimizedImages, - artifacts.PasswordInputsWithPreventedPaste, - artifacts.ResponseCompression, - artifacts.RobotsTxt, - artifacts.ServiceWorker, - artifacts.ScriptElements, - artifacts.Scripts, - artifacts.SourceMaps, - artifacts.Stacks, - artifacts.TagsBlockingFirstPaint, - artifacts.TapTargets, - artifacts.TraceElements, - artifacts.ViewportDimensions, - artifacts.WebAppManifest, - - // Compat artifacts come last. - artifacts.devtoolsLogs, - artifacts.traces, - - // FullPageScreenshot comes at the very end so all other node analysis is captured. - artifacts.FullPageScreenshot, - ], - }, - ], audits: [ 'is-on-https', 'service-worker', diff --git a/core/config/legacy-default-config.js b/core/config/legacy-default-config.js index 55d4b98cef19..81d084fb08d0 100644 --- a/core/config/legacy-default-config.js +++ b/core/config/legacy-default-config.js @@ -19,7 +19,6 @@ if (!legacyDefaultConfig.categories) { // These properties are ignored in Legacy navigations. delete legacyDefaultConfig.artifacts; -delete legacyDefaultConfig.navigations; // These audits don't work in Legacy navigation mode so we remove them. const unsupportedAuditIds = [ diff --git a/core/fraggle-rock/config/config.js b/core/fraggle-rock/config/config.js index 79079eb304f0..bf644e67842c 100644 --- a/core/fraggle-rock/config/config.js +++ b/core/fraggle-rock/config/config.js @@ -78,7 +78,7 @@ function resolveExtensions(configJSON) { throw new Error('`lighthouse:default` is the only valid extension method.'); } - const {artifacts, navigations, ...extensionJSON} = configJSON; + const {artifacts, ...extensionJSON} = configJSON; const defaultClone = deepCloneConfigJson(defaultConfig); const mergedConfig = mergeConfigFragment(defaultClone, extensionJSON); @@ -87,11 +87,6 @@ function resolveExtensions(configJSON) { artifacts, artifact => artifact.id ); - mergedConfig.navigations = mergeConfigFragmentArrayByKey( - defaultClone.navigations, - navigations, - navigation => navigation.id - ); return mergedConfig; } @@ -213,39 +208,33 @@ function overrideNavigationThrottlingWindows(navigation, settings) { } /** - * - * @param {LH.Config.NavigationJson[]|null|undefined} navigations * @param {LH.Config.AnyArtifactDefn[]|null|undefined} artifactDefns * @param {LH.Config.Settings} settings * @return {LH.Config.NavigationDefn[] | null} */ -function resolveNavigationsToDefns(navigations, artifactDefns, settings) { - if (!navigations) return null; - if (!artifactDefns) throw new Error('Cannot use navigations without defining artifacts'); +function resolveFakeNavigations(artifactDefns, settings) { + if (!artifactDefns) return null; const status = {msg: 'Resolve navigation definitions', id: 'lh:config:resolveNavigationsToDefns'}; log.time(status, 'verbose'); - const artifactsById = new Map(artifactDefns.map(defn => [defn.id, defn])); - - const navigationDefns = navigations.map(navigation => { - const navigationWithDefaults = {...defaultNavigationConfig, ...navigation}; - const navId = navigationWithDefaults.id; - const artifacts = navigationWithDefaults.artifacts.map(id => { - const artifact = artifactsById.get(id); - if (!artifact) throw new Error(`Unrecognized artifact "${id}" in navigation "${navId}"`); - return artifact; - }); + const resolvedNavigation = { + ...defaultNavigationConfig, + artifacts: artifactDefns, + pauseAfterFcpMs: settings.pauseAfterFcpMs, + pauseAfterLoadMs: settings.pauseAfterLoadMs, + networkQuietThresholdMs: settings.networkQuietThresholdMs, + cpuQuietThresholdMs: settings.cpuQuietThresholdMs, + blankPage: settings.blankPage, + }; - const resolvedNavigation = {...navigationWithDefaults, artifacts}; - overrideNavigationThrottlingWindows(resolvedNavigation, settings); - return resolvedNavigation; - }); + overrideNavigationThrottlingWindows(resolvedNavigation, settings); - assertArtifactTopologicalOrder(navigationDefns); + const navigations = [resolvedNavigation]; + assertArtifactTopologicalOrder(navigations); log.timeEnd(status); - return navigationDefns; + return navigations; } /** @@ -267,7 +256,8 @@ async function initializeConfig(gatherMode, configJSON, flags = {}) { overrideSettingsForGatherMode(settings, gatherMode); const artifacts = await resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir); - const navigations = resolveNavigationsToDefns(configWorkingCopy.navigations, artifacts, settings); + + const navigations = resolveFakeNavigations(artifacts, settings); /** @type {LH.Config.FRConfig} */ let config = { diff --git a/core/scripts/update-flow-fixtures.js b/core/scripts/update-flow-fixtures.js index 8c8f15bf365e..13bcf63125d5 100644 --- a/core/scripts/update-flow-fixtures.js +++ b/core/scripts/update-flow-fixtures.js @@ -120,6 +120,12 @@ async function rebaselineArtifacts(artifactKeys) { for (let i = 0; i < flowArtifacts.gatherSteps.length; ++i) { const gatherStep = flowArtifacts.gatherSteps[i]; const newGatherStep = newFlowArtifacts.gatherSteps[i]; + + // Always update these three values + gatherStep.config = newGatherStep.config; + gatherStep.flags = newGatherStep.flags; + gatherStep.name = newGatherStep.name; + for (const key of artifactKeys) { // @ts-expect-error gatherStep.artifacts[key] = newGatherStep.artifacts[key]; diff --git a/core/test/fixtures/fraggle-rock/artifacts/sample-flow-artifacts.json b/core/test/fixtures/fraggle-rock/artifacts/sample-flow-artifacts.json index 0dc97b4a6731..76df8f66e120 100644 --- a/core/test/fixtures/fraggle-rock/artifacts/sample-flow-artifacts.json +++ b/core/test/fixtures/fraggle-rock/artifacts/sample-flow-artifacts.json @@ -164,7 +164,12 @@ "skipAudits": [ "uses-http2" ], - "skipAboutBlank": true + "skipAboutBlank": true, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "blankPage": "about:blank" }, "BenchmarkIndex": 2094, "HostUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4945.0 Safari/537.36", @@ -313240,6 +313245,10 @@ ] } }, + "configContext": { + "skipAboutBlank": true, + "settingsOverrides": {} + }, "flags": { "skipAboutBlank": true } @@ -313360,7 +313369,12 @@ "skipAudits": [ "uses-http2" ], - "skipAboutBlank": false + "skipAboutBlank": false, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "blankPage": "about:blank" }, "BenchmarkIndex": 2096.5, "HostUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4945.0 Safari/537.36", @@ -672708,7 +672722,12 @@ "skipAudits": [ "uses-http2" ], - "skipAboutBlank": false + "skipAboutBlank": false, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "blankPage": "about:blank" }, "BenchmarkIndex": 2206.5, "HostUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4945.0 Safari/537.36", @@ -675591,7 +675610,12 @@ "skipAudits": [ "uses-http2" ], - "skipAboutBlank": true + "skipAboutBlank": true, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "blankPage": "about:blank" }, "BenchmarkIndex": 2210.5, "HostUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4945.0 Safari/537.36", @@ -890328,6 +890352,12 @@ ] } }, + "configContext": { + "skipAboutBlank": true, + "settingsOverrides": { + "disableStorageReset": true + } + }, "flags": { "skipAboutBlank": true, "disableStorageReset": true diff --git a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index f2ccc772f68e..0b43427eed8f 100644 --- a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -3661,6 +3661,10 @@ "output": "json", "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, "formFactor": "mobile", "throttling": { "rttMs": 150, @@ -3684,6 +3688,8 @@ "disableStorageReset": false, "debugNavigation": false, "channel": "node", + "skipAboutBlank": true, + "blankPage": "about:blank", "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -3694,8 +3700,7 @@ "onlyCategories": null, "skipAudits": [ "uses-http2" - ], - "skipAboutBlank": true + ] }, "categories": { "performance": { @@ -9064,6 +9069,10 @@ "output": "json", "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, "formFactor": "mobile", "throttling": { "rttMs": 150, @@ -9087,6 +9096,8 @@ "disableStorageReset": false, "debugNavigation": false, "channel": "node", + "skipAboutBlank": false, + "blankPage": "about:blank", "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -9097,8 +9108,7 @@ "onlyCategories": null, "skipAudits": [ "uses-http2" - ], - "skipAboutBlank": false + ] }, "categories": { "performance": { @@ -12288,6 +12298,10 @@ "output": "json", "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, "formFactor": "mobile", "throttling": { "rttMs": 150, @@ -12311,6 +12325,8 @@ "disableStorageReset": false, "debugNavigation": false, "channel": "node", + "skipAboutBlank": false, + "blankPage": "about:blank", "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -12321,8 +12337,7 @@ "onlyCategories": null, "skipAudits": [ "uses-http2" - ], - "skipAboutBlank": false + ] }, "categories": { "performance": { @@ -17755,6 +17770,10 @@ "output": "json", "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, "formFactor": "mobile", "throttling": { "rttMs": 150, @@ -17778,6 +17797,8 @@ "disableStorageReset": true, "debugNavigation": false, "channel": "node", + "skipAboutBlank": true, + "blankPage": "about:blank", "budgets": null, "locale": "en-US", "blockedUrlPatterns": null, @@ -17788,8 +17809,7 @@ "onlyCategories": null, "skipAudits": [ "uses-http2" - ], - "skipAboutBlank": true + ] }, "categories": { "performance": { diff --git a/core/test/fraggle-rock/config/config-test.js b/core/test/fraggle-rock/config/config-test.js index be157ca622a3..4ae95389604b 100644 --- a/core/test/fraggle-rock/config/config-test.js +++ b/core/test/fraggle-rock/config/config-test.js @@ -46,9 +46,14 @@ describe('Fraggle Rock Config', () => { }); it('should resolve settings with defaults', async () => { + /** @type {LH.Config.Json} */ + const configJson = { + extends: 'lighthouse:default', + settings: {output: 'csv', maxWaitForFcp: 1234}, + }; const {config} = await initializeConfig( gatherMode, - {settings: {output: 'csv', maxWaitForFcp: 1234}}, + configJson, {maxWaitForFcp: 12345} ); @@ -161,10 +166,6 @@ describe('Fraggle Rock Config', () => { {id: 'Dependency', gatherer: {instance: dependencyGatherer}}, {id: 'Dependent', gatherer: {instance: dependentGatherer}}, ], - navigations: [ - {id: 'default', artifacts: ['Dependency']}, - {id: 'second', artifacts: ['Dependent']}, - ], }; }); @@ -190,9 +191,9 @@ describe('Fraggle Rock Config', () => { const {config} = await initializeConfig('snapshot', configJson); expect(config).toMatchObject({ navigations: [ - {artifacts: [{id: 'Dependency'}]}, { artifacts: [ + {id: 'Dependency'}, { id: 'Dependent', dependencies: { @@ -212,22 +213,6 @@ describe('Fraggle Rock Config', () => { .rejects.toThrow(/Failed to find dependency/); }); - it('should throw when dependencies are out of order within a navigation', () => { - if (!configJson.navigations) throw new Error('Failed to run beforeEach'); - const invalidNavigation = {id: 'default', artifacts: ['Dependent', 'Dependency']}; - configJson.navigations = [invalidNavigation]; - expect(initializeConfig('snapshot', configJson)) - .rejects.toThrow(/Failed to find dependency/); - }); - - it('should throw when dependencies are out of order between navigations', () => { - if (!configJson.navigations) throw new Error('Failed to run beforeEach'); - const invalidNavigation = {id: 'default', artifacts: ['Dependent']}; - configJson.navigations = [invalidNavigation]; - expect(initializeConfig('snapshot', configJson)) - .rejects.toThrow(/Failed to find dependency/); - }); - it('should throw when timespan needs snapshot', () => { dependentGatherer.meta.supportedModes = ['timespan']; dependencyGatherer.meta.supportedModes = ['snapshot']; @@ -243,11 +228,10 @@ describe('Fraggle Rock Config', () => { }); }); - describe('.resolveNavigationsToDefns', () => { - it('should resolve navigation definitions', async () => { + describe('.resolveFakeNavigations', () => { + it('should resolve a single fake navigation definitions', async () => { const configJson = { artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}], - navigations: [{id: 'default', artifacts: ['Accessibility']}], }; const {config} = await initializeConfig('navigation', configJson); @@ -259,29 +243,10 @@ describe('Fraggle Rock Config', () => { }); }); - it('should throw when navigations are defined without artifacts', () => { - const configJson = { - navigations: [{id: 'default', artifacts: ['Accessibility']}], - }; - - expect(initializeConfig(gatherMode, configJson)) - .rejects.toThrow(/Cannot use navigations/); - }); - - it('should throw when navigations use unrecognized artifacts', () => { - const configJson = { - artifacts: [], - navigations: [{id: 'default', artifacts: ['Accessibility']}], - }; - - expect(initializeConfig(gatherMode, configJson)).rejects.toThrow(/Unrecognized artifact/); - }); - it('should set default properties on navigations', async () => { gatherMode = 'navigation'; const configJson = { artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}], - navigations: [{id: 'default', artifacts: ['Accessibility']}], }; const {config} = await initializeConfig(gatherMode, configJson); @@ -293,8 +258,8 @@ describe('Fraggle Rock Config', () => { artifacts: [{id: 'Accessibility', gatherer: {path: 'accessibility'}}], loadFailureMode: 'fatal', disableThrottling: false, - networkQuietThresholdMs: 0, - cpuQuietThresholdMs: 0, + networkQuietThresholdMs: 1000, + cpuQuietThresholdMs: 1000, }, ], }); @@ -303,12 +268,10 @@ describe('Fraggle Rock Config', () => { it('should ensure minimum quiet thresholds when throttlingMethod is devtools', async () => { gatherMode = 'navigation'; const configJson = { + settings: { + cpuQuietThresholdMs: 10_000, + }, artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}], - navigations: [ - {id: 'default', artifacts: ['Accessibility']}, - {id: 'noThrottling', artifacts: ['Accessibility'], disableThrottling: true}, - {id: 'alreadyHigh', artifacts: ['Accessibility'], cpuQuietThresholdMs: 10_000}, - ], }; const {config} = await initializeConfig(gatherMode, configJson, { @@ -318,13 +281,11 @@ describe('Fraggle Rock Config', () => { expect(config).toMatchObject({ navigations: [ { + cpuQuietThresholdMs: 10_000, pauseAfterFcpMs: nonSimulatedPassConfigOverrides.pauseAfterFcpMs, pauseAfterLoadMs: nonSimulatedPassConfigOverrides.pauseAfterLoadMs, networkQuietThresholdMs: nonSimulatedPassConfigOverrides.networkQuietThresholdMs, - cpuQuietThresholdMs: nonSimulatedPassConfigOverrides.cpuQuietThresholdMs, }, - {networkQuietThresholdMs: 0, cpuQuietThresholdMs: 0}, - {cpuQuietThresholdMs: 10_000}, ], }); }); @@ -361,9 +322,6 @@ describe('Fraggle Rock Config', () => { artifacts: [ {id: 'ExtraArtifact', gatherer: {instance: gatherer}}, ], - navigations: [ - {id: 'default', artifacts: ['ExtraArtifact']}, - ], audits: [ {implementation: ExtraAudit}, ], @@ -383,9 +341,6 @@ describe('Fraggle Rock Config', () => { artifacts: [ {id: 'Accessibility', gatherer: 'accessibility'}, ], - navigations: [ - {id: 'default', artifacts: ['Accessibility']}, - ], }); expect(config).toMatchObject({ @@ -456,17 +411,16 @@ describe('Fraggle Rock Config', () => { }); }); - it('should validate the config with warnings', async () => { + it('should use failure mode fatal for the fake navigation', async () => { /** @type {LH.Config.Json} */ const extensionConfig = { extends: 'lighthouse:default', - navigations: [{id: 'default', loadFailureMode: 'warn'}], }; const {config, warnings} = await initializeConfig('navigation', extensionConfig); const navigations = config.navigations; if (!navigations) throw new Error(`Failed to initialize navigations`); - expect(warnings).toHaveLength(1); + expect(warnings).toHaveLength(0); expect(navigations[0].loadFailureMode).toEqual('fatal'); }); diff --git a/core/test/fraggle-rock/gather/navigation-runner-test.js b/core/test/fraggle-rock/gather/navigation-runner-test.js index b2d04ea78f13..6aefb510bedb 100644 --- a/core/test/fraggle-rock/gather/navigation-runner-test.js +++ b/core/test/fraggle-rock/gather/navigation-runner-test.js @@ -194,18 +194,24 @@ describe('NavigationRunner', () => { }); it('should navigate as many times as there are navigations', async () => { - config = (await initializeConfig( - 'navigation', - { - ...config, - navigations: [ - {id: 'default', artifacts: ['FontSize']}, - {id: 'second', artifacts: ['ConsoleMessages']}, - {id: 'third', artifacts: ['ViewportDimensions']}, - {id: 'fourth', artifacts: ['AnchorElements']}, - ], - } - )).config; + // initializeConfig always produces a single config navigation. + // Artificially construct multiple navigations to test on the navigation runner. + const originalNavigation = config.navigations?.[0]; + if (!originalNavigation) throw new Error('Should always have navigations'); + const artifactDefns = originalNavigation.artifacts.filter(a => + ['FontSize', 'ConsoleMessages', 'ViewportDimensions', 'AnchorElements'].includes(a.id) + ); + const newNavigations = []; + for (let i = 0; i < artifactDefns.length; ++i) { + const artifactDefn = artifactDefns[i]; + newNavigations.push({ + ...originalNavigation, + id: i ? String(i) : 'default', + artifacts: [artifactDefn], + }); + } + + config.navigations = newNavigations; await run(); const navigations = mocks.navigationMock.gotoURL.mock.calls; @@ -220,8 +226,9 @@ describe('NavigationRunner', () => { 'navigation', { ...config, - navigations: [ - {id: 'default', artifacts: ['FontSize']}, + artifacts: [ + {id: 'FontSize', gatherer: 'seo/font-size'}, + {id: 'MetaElements', gatherer: 'meta-elements'}, ], } )).config; @@ -242,16 +249,17 @@ describe('NavigationRunner', () => { }); it('should merge artifacts between navigations', async () => { - config = (await initializeConfig( - 'navigation', - { - ...config, - navigations: [ - {id: 'default', artifacts: ['FontSize']}, - {id: 'second', artifacts: ['ConsoleMessages']}, - ], - } - )).config; + // initializeConfig always produces a single config navigation. + // Artificially construct multiple navigations to test on the navigation runner. + if (!config.navigations) throw new Error('Should always have navigations'); + const firstNavigation = config.navigations[0]; + const secondNavigation = {...firstNavigation, id: 'second'}; + const fontSizeDef = firstNavigation.artifacts.find(a => a.id === 'FontSize'); + const consoleMsgDef = firstNavigation.artifacts.find(a => a.id === 'ConsoleMessages'); + if (!fontSizeDef || !consoleMsgDef) throw new Error('Artifact definitions not found'); + secondNavigation.artifacts = [fontSizeDef]; + firstNavigation.artifacts = [consoleMsgDef]; + config.navigations.push(secondNavigation); // Both gatherers will error in these test conditions, but artifact errors // will be merged into single `artifacts` object. @@ -266,9 +274,9 @@ describe('NavigationRunner', () => { 'navigation', { ...config, - navigations: [ - {id: 'default', loadFailureMode: 'fatal', artifacts: ['FontSize']}, - {id: 'second', artifacts: ['ConsoleMessages']}, + artifacts: [ + {id: 'FontSize', gatherer: 'seo/font-size'}, + {id: 'MetaElements', gatherer: 'meta-elements'}, ], } )).config; diff --git a/core/test/results/artifacts/artifacts.json b/core/test/results/artifacts/artifacts.json index 0997e6c00cdc..c6c26ac49676 100644 --- a/core/test/results/artifacts/artifacts.json +++ b/core/test/results/artifacts/artifacts.json @@ -170,7 +170,12 @@ "onlyAudits": null, "onlyCategories": null, "skipAudits": null, - "skipAboutBlank": false + "skipAboutBlank": false, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "blankPage": "about:blank" }, "URL": { "initialUrl": "about:blank", diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index 82d2cb749c9d..de730ca91a5a 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -5813,6 +5813,10 @@ ], "maxWaitForFcp": 30000, "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, "formFactor": "mobile", "throttling": { "rttMs": 150, @@ -5836,6 +5840,8 @@ "disableStorageReset": false, "debugNavigation": false, "channel": "cli", + "skipAboutBlank": false, + "blankPage": "about:blank", "budgets": [ { "path": "/", @@ -5942,8 +5948,7 @@ "precomputedLanternData": null, "onlyAudits": null, "onlyCategories": null, - "skipAudits": null, - "skipAboutBlank": false + "skipAudits": null }, "categories": { "performance": { diff --git a/types/config.d.ts b/types/config.d.ts index d1e792fe8e04..79b0cf1d9075 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -28,7 +28,6 @@ declare module Config { // Fraggle Rock Only artifacts?: ArtifactJson[] | null; - navigations?: NavigationJson[] | null; // Legacy Only passes?: PassJson[] | null; diff --git a/types/lhr/settings.d.ts b/types/lhr/settings.d.ts index 64c4587aee74..1376aa3b8ca2 100644 --- a/types/lhr/settings.d.ts +++ b/types/lhr/settings.d.ts @@ -99,6 +99,17 @@ export type ScreenEmulationSettings = { precomputedLanternData?: PrecomputedLanternData | null; /** The budget.json object for LightWallet. */ budgets?: Array | null; + + /** The number of milliseconds to wait after FCP until the page should be considered loaded. */ + pauseAfterFcpMs?: number; + /** The number of milliseconds to wait after the load event until the page should be considered loaded. */ + pauseAfterLoadMs?: number; + /** The number of milliseconds to wait between high priority network requests or 3 simulataneous requests before the page should be considered loaded. */ + networkQuietThresholdMs?: number; + /** The number of milliseconds to wait between long tasks until the page should be considered loaded. */ + cpuQuietThresholdMs?: number; + /** The URL to use for the "blank" neutral page in between navigations. Defaults to `about:blank`. */ + blankPage?: string; } export interface ConfigSettings extends Required {