diff --git a/lighthouse-cli/bin.js b/lighthouse-cli/bin.js index 1d25c5b58cd4..c1637015bd69 100644 --- a/lighthouse-cli/bin.js +++ b/lighthouse-cli/bin.js @@ -120,7 +120,7 @@ async function begin() { } if (cliFlags.printConfig) { - const config = lighthouse.generateConfig(configJson, cliFlags); + const config = await lighthouse.generateConfig(configJson, cliFlags); process.stdout.write(config.getPrintString()); return; } diff --git a/lighthouse-core/config/config-helpers.js b/lighthouse-core/config/config-helpers.js index 090ddb2b5253..2d9be28f2fb1 100644 --- a/lighthouse-core/config/config-helpers.js +++ b/lighthouse-core/config/config-helpers.js @@ -211,7 +211,8 @@ const bundledModules = new Map(/* BUILD_REPLACE_BUNDLED_MODULES */); * See build-bundle.js * @param {string} requirePath */ -function requireWrapper(requirePath) { +async function requireWrapper(requirePath) { + // This is async because eventually this function needs to do async dynamic imports. return bundledModules.get(requirePath) || require(requirePath); } @@ -219,9 +220,9 @@ function requireWrapper(requirePath) { * @param {string} gathererPath * @param {Array} coreGathererList * @param {string=} configDir - * @return {LH.Config.GathererDefn} + * @return {Promise} */ -function requireGatherer(gathererPath, coreGathererList, configDir) { +async function requireGatherer(gathererPath, coreGathererList, configDir) { const coreGatherer = coreGathererList.find(a => a === `${gathererPath}.js`); let requirePath = `../gather/gatherers/${gathererPath}`; @@ -230,7 +231,7 @@ function requireGatherer(gathererPath, coreGathererList, configDir) { requirePath = resolveModulePath(gathererPath, configDir, 'gatherer'); } - const GathererClass = /** @type {GathererConstructor} */ (requireWrapper(requirePath)); + const GathererClass = /** @type {GathererConstructor} */ (await requireWrapper(requirePath)); return { instance: new GathererClass(), @@ -243,7 +244,7 @@ function requireGatherer(gathererPath, coreGathererList, configDir) { * @param {string} auditPath * @param {Array} coreAuditList * @param {string=} configDir - * @return {LH.Config.AuditDefn['implementation']} + * @return {Promise} */ function requireAudit(auditPath, coreAuditList, configDir) { // See if the audit is a Lighthouse core audit. @@ -328,9 +329,9 @@ function resolveSettings(settingsJson = {}, overrides = undefined) { * @param {LH.Config.Json} configJSON * @param {string | undefined} configDir * @param {{plugins?: string[]} | undefined} flags - * @return {LH.Config.Json} + * @return {Promise} */ -function mergePlugins(configJSON, configDir, flags) { +async function mergePlugins(configJSON, configDir, flags) { const configPlugins = configJSON.plugins || []; const flagPlugins = flags?.plugins || []; const pluginNames = new Set([...configPlugins, ...flagPlugins]); @@ -342,7 +343,7 @@ function mergePlugins(configJSON, configDir, flags) { const pluginPath = isBundledEnvironment() ? pluginName : resolveModulePath(pluginName, configDir, 'plugin'); - const rawPluginJson = requireWrapper(pluginPath); + const rawPluginJson = await requireWrapper(pluginPath); const pluginJson = ConfigPlugin.parsePlugin(rawPluginJson, pluginName); configJSON = mergeConfigFragment(configJSON, pluginJson); @@ -360,9 +361,9 @@ function mergePlugins(configJSON, configDir, flags) { * @param {LH.Config.GathererJson} gathererJson * @param {Array} coreGathererList * @param {string=} configDir - * @return {LH.Config.GathererDefn} + * @return {Promise} */ -function resolveGathererToDefn(gathererJson, coreGathererList, configDir) { +async function resolveGathererToDefn(gathererJson, coreGathererList, configDir) { const gathererDefn = expandGathererShorthand(gathererJson); if (gathererDefn.instance) { return { @@ -391,21 +392,21 @@ function resolveGathererToDefn(gathererJson, coreGathererList, configDir) { * leaving only an array of AuditDefns. * @param {LH.Config.Json['audits']} audits * @param {string=} configDir - * @return {Array|null} + * @return {Promise|null>} */ -function resolveAuditsToDefns(audits, configDir) { +async function resolveAuditsToDefns(audits, configDir) { if (!audits) { return null; } const coreList = Runner.getAuditList(); - const auditDefns = audits.map(auditJson => { + const auditDefnsPromises = audits.map(async (auditJson) => { const auditDefn = expandAuditShorthand(auditJson); let implementation; if ('implementation' in auditDefn) { implementation = auditDefn.implementation; } else { - implementation = requireAudit(auditDefn.path, coreList, configDir); + implementation = await requireAudit(auditDefn.path, coreList, configDir); } return { @@ -414,6 +415,7 @@ function resolveAuditsToDefns(audits, configDir) { options: auditDefn.options || {}, }; }); + const auditDefns = await Promise.all(auditDefnsPromises); const mergedAuditDefns = mergeOptionsOfItems(auditDefns); mergedAuditDefns.forEach(audit => validation.assertValidAudit(audit)); diff --git a/lighthouse-core/config/config.js b/lighthouse-core/config/config.js index f2f200238890..e2cc45a545a1 100644 --- a/lighthouse-core/config/config.js +++ b/lighthouse-core/config/config.js @@ -149,17 +149,18 @@ function assertValidFlags(flags) { } } - /** * @implements {LH.Config.Config} */ class Config { /** - * @constructor - * @param {LH.Config.Json=} configJSON + * Resolves the provided config (inherits from extended config, if set), resolves + * all referenced modules, and validates. + * @param {LH.Config.Json=} configJSON If not provided, uses the default config. * @param {LH.Flags=} flags + * @return {Promise} */ - constructor(configJSON, flags) { + static async fromJson(configJSON, flags) { const status = {msg: 'Create config', id: 'lh:init:config'}; log.time(status, 'verbose'); let configPath = flags?.configPath; @@ -188,7 +189,7 @@ class Config { const configDir = configPath ? path.dirname(configPath) : undefined; // Validate and merge in plugins (if any). - configJSON = mergePlugins(configJSON, configDir, flags); + configJSON = await mergePlugins(configJSON, configDir, flags); if (flags) { assertValidFlags(flags); @@ -198,14 +199,28 @@ class Config { // Augment passes with necessary defaults and require gatherers. const passesWithDefaults = Config.augmentPassesWithDefaults(configJSON.passes); Config.adjustDefaultPassForThrottling(settings, passesWithDefaults); - const passes = Config.requireGatherers(passesWithDefaults, configDir); + const passes = await Config.requireGatherers(passesWithDefaults, configDir); + const audits = await Config.requireAudits(configJSON.audits, configDir); + + const config = new Config(configJSON, {settings, passes, audits}); + log.timeEnd(status); + return config; + } + + /** + * @deprecated `Config.fromJson` should be used instead. + * @constructor + * @param {LH.Config.Json} configJSON + * @param {{settings: LH.Config.Settings, passes: ?LH.Config.Pass[], audits: ?LH.Config.AuditDefn[]}} opts + */ + constructor(configJSON, opts) { /** @type {LH.Config.Settings} */ - this.settings = settings; + this.settings = opts.settings; /** @type {?Array} */ - this.passes = passes; + this.passes = opts.passes; /** @type {?Array} */ - this.audits = Config.requireAudits(configJSON.audits, configDir); + this.audits = opts.audits; /** @type {?Record} */ this.categories = configJSON.categories || null; /** @type {?Record} */ @@ -215,8 +230,6 @@ class Config { assertValidPasses(this.passes, this.audits); validation.assertValidCategories(this.categories, this.audits, this.groups); - - log.timeEnd(status); } /** @@ -499,12 +512,12 @@ class Config { * leaving only an array of AuditDefns. * @param {LH.Config.Json['audits']} audits * @param {string=} configDir - * @return {Config['audits']} + * @return {Promise} */ - static requireAudits(audits, configDir) { + static async requireAudits(audits, configDir) { const status = {msg: 'Requiring audits', id: 'lh:config:requireAudits'}; log.time(status, 'verbose'); - const auditDefns = resolveAuditsToDefns(audits, configDir); + const auditDefns = await resolveAuditsToDefns(audits, configDir); log.timeEnd(status); return auditDefns; } @@ -515,9 +528,9 @@ class Config { * provided) using `resolveModulePath`, returning an array of full Passes. * @param {?Array>} passes * @param {string=} configDir - * @return {Config['passes']} + * @return {Promise} */ - static requireGatherers(passes, configDir) { + static async requireGatherers(passes, configDir) { if (!passes) { return null; } @@ -525,9 +538,11 @@ class Config { log.time(status, 'verbose'); const coreList = Runner.getGathererList(); - const fullPasses = passes.map(pass => { - const gathererDefns = pass.gatherers - .map(gatherer => resolveGathererToDefn(gatherer, coreList, configDir)); + const fullPassesPromises = passes.map(async (pass) => { + const gathererDefns = await Promise.all( + pass.gatherers + .map(gatherer => resolveGathererToDefn(gatherer, coreList, configDir)) + ); // De-dupe gatherers by artifact name because artifact IDs must be unique at runtime. const uniqueDefns = Array.from( @@ -537,6 +552,8 @@ class Config { return Object.assign(pass, {gatherers: uniqueDefns}); }); + const fullPasses = await Promise.all(fullPassesPromises); + log.timeEnd(status); return fullPasses; } diff --git a/lighthouse-core/fraggle-rock/config/config.js b/lighthouse-core/fraggle-rock/config/config.js index 1d1480b77032..2fece7796b58 100644 --- a/lighthouse-core/fraggle-rock/config/config.js +++ b/lighthouse-core/fraggle-rock/config/config.js @@ -121,9 +121,9 @@ function resolveArtifactDependencies(artifact, gatherer, artifactDefnsBySymbol) * * @param {LH.Config.ArtifactJson[]|null|undefined} artifacts * @param {string|undefined} configDir - * @return {LH.Config.AnyArtifactDefn[] | null} + * @return {Promise} */ -function resolveArtifactsToDefns(artifacts, configDir) { +async function resolveArtifactsToDefns(artifacts, configDir) { if (!artifacts) return null; const status = {msg: 'Resolve artifact definitions', id: 'lh:config:resolveArtifactsToDefns'}; @@ -133,12 +133,12 @@ function resolveArtifactsToDefns(artifacts, configDir) { const artifactDefnsBySymbol = new Map(); const coreGathererList = Runner.getGathererList(); - const artifactDefns = artifacts.map(artifactJson => { + const artifactDefnsPromises = artifacts.map(async (artifactJson) => { /** @type {LH.Config.GathererJson} */ // @ts-expect-error - remove when legacy runner path is removed. const gathererJson = artifactJson.gatherer; - const gatherer = resolveGathererToDefn(gathererJson, coreGathererList, configDir); + const gatherer = await resolveGathererToDefn(gathererJson, coreGathererList, configDir); if (!isFRGathererDefn(gatherer)) { throw new Error(`${gatherer.instance.name} gatherer does not have a Fraggle Rock meta obj`); } @@ -156,6 +156,7 @@ function resolveArtifactsToDefns(artifacts, configDir) { if (symbol) artifactDefnsBySymbol.set(symbol, artifact); return artifact; }); + const artifactDefns = await Promise.all(artifactDefnsPromises); log.timeEnd(status); return artifactDefns; @@ -242,28 +243,28 @@ function resolveNavigationsToDefns(navigations, artifactDefns, settings) { /** * @param {LH.Config.Json|undefined} configJSON * @param {ConfigContext} context - * @return {{config: LH.Config.FRConfig, warnings: string[]}} + * @return {Promise<{config: LH.Config.FRConfig, warnings: string[]}>} */ -function initializeConfig(configJSON, context) { +async function initializeConfig(configJSON, context) { const status = {msg: 'Initialize config', id: 'lh:config'}; log.time(status, 'verbose'); let {configWorkingCopy, configDir} = resolveWorkingCopy(configJSON, context); // eslint-disable-line prefer-const configWorkingCopy = resolveExtensions(configWorkingCopy); - configWorkingCopy = mergePlugins(configWorkingCopy, configDir, context.settingsOverrides); + configWorkingCopy = await mergePlugins(configWorkingCopy, configDir, context.settingsOverrides); const settings = resolveSettings(configWorkingCopy.settings || {}, context.settingsOverrides); overrideSettingsForGatherMode(settings, context); - const artifacts = resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir); + const artifacts = await resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir); const navigations = resolveNavigationsToDefns(configWorkingCopy.navigations, artifacts, settings); /** @type {LH.Config.FRConfig} */ let config = { artifacts, navigations, - audits: resolveAuditsToDefns(configWorkingCopy.audits, configDir), + audits: await resolveAuditsToDefns(configWorkingCopy.audits, configDir), categories: configWorkingCopy.categories || null, groups: configWorkingCopy.groups || null, settings, diff --git a/lighthouse-core/fraggle-rock/gather/navigation-runner.js b/lighthouse-core/fraggle-rock/gather/navigation-runner.js index cc799b745224..bdcefbc27a80 100644 --- a/lighthouse-core/fraggle-rock/gather/navigation-runner.js +++ b/lighthouse-core/fraggle-rock/gather/navigation-runner.js @@ -310,7 +310,8 @@ async function navigationGather(requestor, options) { const {configContext = {}} = options; log.setLevel(configContext.logLevel || 'error'); - const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'navigation'}); + const {config} = + await initializeConfig(options.config, {...configContext, gatherMode: 'navigation'}); const computedCache = new Map(); const internalOptions = { skipAboutBlank: configContext.skipAboutBlank, diff --git a/lighthouse-core/fraggle-rock/gather/snapshot-runner.js b/lighthouse-core/fraggle-rock/gather/snapshot-runner.js index 92a60a564777..c4038448b336 100644 --- a/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +++ b/lighthouse-core/fraggle-rock/gather/snapshot-runner.js @@ -24,7 +24,8 @@ async function snapshotGather(options) { const {configContext = {}} = options; log.setLevel(configContext.logLevel || 'error'); - const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'snapshot'}); + const {config} = + await initializeConfig(options.config, {...configContext, gatherMode: 'snapshot'}); const driver = new Driver(options.page); await driver.connect(); diff --git a/lighthouse-core/fraggle-rock/gather/timespan-runner.js b/lighthouse-core/fraggle-rock/gather/timespan-runner.js index 25ceab578caa..8daeb1da225e 100644 --- a/lighthouse-core/fraggle-rock/gather/timespan-runner.js +++ b/lighthouse-core/fraggle-rock/gather/timespan-runner.js @@ -25,7 +25,8 @@ async function startTimespanGather(options) { const {configContext = {}} = options; log.setLevel(configContext.logLevel || 'error'); - const {config} = initializeConfig(options.config, {...configContext, gatherMode: 'timespan'}); + const {config} = + await initializeConfig(options.config, {...configContext, gatherMode: 'timespan'}); const driver = new Driver(options.page); await driver.connect(); diff --git a/lighthouse-core/fraggle-rock/user-flow.js b/lighthouse-core/fraggle-rock/user-flow.js index 552879e24fc5..ebbee466c780 100644 --- a/lighthouse-core/fraggle-rock/user-flow.js +++ b/lighthouse-core/fraggle-rock/user-flow.js @@ -205,7 +205,7 @@ async function auditGatherSteps(gatherSteps, options) { // Step specific configs take precedence over a config for the entire flow. const configJson = gatherStep.config || options.config; const {gatherMode} = artifacts.GatherContext; - const {config} = initializeConfig(configJson, {...configContext, gatherMode}); + const {config} = await initializeConfig(configJson, {...configContext, gatherMode}); runnerOptions = { config, computedCache: new Map(), diff --git a/lighthouse-core/index.js b/lighthouse-core/index.js index f4a6b5d8a4d2..4ca5dac2ed93 100644 --- a/lighthouse-core/index.js +++ b/lighthouse-core/index.js @@ -64,7 +64,7 @@ async function legacyNavigation(url, flags = {}, configJSON, userConnection) { flags.logLevel = flags.logLevel || 'error'; log.setLevel(flags.logLevel); - const config = generateConfig(configJSON, flags); + const config = await generateConfig(configJSON, flags); const computedCache = new Map(); const options = {config, computedCache}; const connection = userConnection || new ChromeProtocol(flags.port, flags.hostname); @@ -83,10 +83,10 @@ async function legacyNavigation(url, flags = {}, configJSON, userConnection) { * not present, the default config is used. * @param {LH.Flags=} flags Optional settings for the Lighthouse run. If present, * they will override any settings in the config. - * @return {Config} + * @return {Promise} */ function generateConfig(configJson, flags) { - return new Config(configJson, flags); + return Config.fromJson(configJson, flags); } lighthouse.legacyNavigation = legacyNavigation; diff --git a/lighthouse-core/scripts/print-a11y-scoring.js b/lighthouse-core/scripts/print-a11y-scoring.js index c613d2fc20c1..f179eb4f417f 100644 --- a/lighthouse-core/scripts/print-a11y-scoring.js +++ b/lighthouse-core/scripts/print-a11y-scoring.js @@ -7,9 +7,8 @@ // node lighthouse-core/scripts/print-a11y-scoring.js import Config from '../config/config.js'; -import defaultConfig from '../config/default-config.js'; -const config = new Config(defaultConfig); +const config = await Config.fromJson(); if (!config.categories || !config.audits) throw new Error('wut'); const auditRefs = config.categories.accessibility.auditRefs; diff --git a/lighthouse-core/test/config/config-helpers-test.js b/lighthouse-core/test/config/config-helpers-test.js index 746b034eab6b..31f49dc63f5a 100644 --- a/lighthouse-core/test/config/config-helpers-test.js +++ b/lighthouse-core/test/config/config-helpers-test.js @@ -197,13 +197,13 @@ describe('.mergePlugins', () => { // Include a configPath flag so that config.js looks for the plugins in the fixtures dir. const configDir = `${LH_ROOT}/lighthouse-core/test/fixtures/config-plugins/`; - it('merge plugins from the config', () => { + it('merge plugins from the config', async () => { const configJson = { audits: ['installable-manifest', 'metrics'], plugins: ['lighthouse-plugin-simple'], }; - const config = mergePlugins(configJson, configDir, {}); + const config = await mergePlugins(configJson, configDir, {}); expect(config).toMatchObject({ audits: [ 'installable-manifest', @@ -220,13 +220,13 @@ describe('.mergePlugins', () => { }); }); - it('merge plugins from flags', () => { + it('merge plugins from flags', async () => { const configJson = { audits: ['installable-manifest', 'metrics'], plugins: ['lighthouse-plugin-simple'], }; const flags = {plugins: ['lighthouse-plugin-no-groups']}; - const config = mergePlugins(configJson, configDir, flags); + const config = await mergePlugins(configJson, configDir, flags); expect(config.categories).toHaveProperty('lighthouse-plugin-simple'); expect(config.categories).toHaveProperty('lighthouse-plugin-no-groups'); @@ -235,19 +235,19 @@ describe('.mergePlugins', () => { it('validate plugin name', () => { const configJson = {audits: ['installable-manifest', 'metrics']}; const flags = {plugins: ['not-a-plugin']}; - expect(() => mergePlugins(configJson, configDir, flags)).toThrow(/does not start/); + expect(mergePlugins(configJson, configDir, flags)).rejects.toThrow(/does not start/); }); it('validate plugin existence', () => { const configJson = {audits: ['installable-manifest', 'metrics']}; const flags = {plugins: ['lighthouse-plugin-missing']}; - expect(() => mergePlugins(configJson, configDir, flags)).toThrow(/Unable to locate plugin/); + expect(mergePlugins(configJson, configDir, flags)).rejects.toThrow(/Unable to locate plugin/); }); it('validate plugin structure', () => { const configJson = {audits: ['installable-manifest', 'metrics']}; const flags = {plugins: ['lighthouse-plugin-no-category']}; - expect(() => mergePlugins(configJson, configDir, flags)).toThrow(/no valid category/); + expect(mergePlugins(configJson, configDir, flags)).rejects.toThrow(/no valid category/); }); }); @@ -340,8 +340,8 @@ describe('.resolveSettings', () => { describe('.resolveGathererToDefn', () => { const coreList = Runner.getGathererList(); - it('should expand gatherer path short-hand', () => { - const result = resolveGathererToDefn('image-elements', coreList); + it('should expand gatherer path short-hand', async () => { + const result = await resolveGathererToDefn('image-elements', coreList); expect(result).toEqual({ path: 'image-elements', implementation: ImageElementsGatherer, @@ -349,9 +349,9 @@ describe('.resolveGathererToDefn', () => { }); }); - it('should find relative to configDir', () => { + it('should find relative to configDir', async () => { const configDir = path.resolve(moduleDir, '../../gather/'); - const result = resolveGathererToDefn('gatherers/image-elements', [], configDir); + const result = await resolveGathererToDefn('gatherers/image-elements', [], configDir); expect(result).toEqual({ path: 'gatherers/image-elements', implementation: ImageElementsGatherer, @@ -359,8 +359,8 @@ describe('.resolveGathererToDefn', () => { }); }); - it('should expand gatherer impl short-hand', () => { - const result = resolveGathererToDefn({implementation: ImageElementsGatherer}, coreList); + it('should expand gatherer impl short-hand', async () => { + const result = await resolveGathererToDefn({implementation: ImageElementsGatherer}, coreList); expect(result).toEqual({ implementation: ImageElementsGatherer, instance: expect.any(ImageElementsGatherer), @@ -368,39 +368,39 @@ describe('.resolveGathererToDefn', () => { }); it('throws for invalid gathererDefn', () => { - expect(() => resolveGathererToDefn({})).toThrow(/Invalid Gatherer type/); + expect(resolveGathererToDefn({})).rejects.toThrow(/Invalid Gatherer type/); }); }); describe('.resolveAuditsToDefns', () => { - it('should expand audit short-hand', () => { - const result = resolveAuditsToDefns(['user-timings']); + it('should expand audit short-hand', async () => { + const result = await resolveAuditsToDefns(['user-timings']); expect(result).toEqual([{path: 'user-timings', options: {}, implementation: UserTimingsAudit}]); }); - it('should find relative to configDir', () => { + it('should find relative to configDir', async () => { const configDir = path.resolve(moduleDir, '../../'); - const result = resolveAuditsToDefns(['audits/user-timings'], configDir); + const result = await resolveAuditsToDefns(['audits/user-timings'], configDir); expect(result).toEqual([ {path: 'audits/user-timings', options: {}, implementation: UserTimingsAudit}, ]); }); - it('should handle multiple audit definition styles', () => { - const result = resolveAuditsToDefns(['user-timings', {implementation: UserTimingsAudit}]); + it('should handle multiple audit definition styles', async () => { + const result = await resolveAuditsToDefns(['user-timings', {implementation: UserTimingsAudit}]); expect(result).toMatchObject([{path: 'user-timings'}, {implementation: UserTimingsAudit}]); }); - it('should merge audit options', () => { + it('should merge audit options', async () => { const audits = [ 'user-timings', {path: 'is-on-https', options: {x: 1, y: 1}}, {path: 'is-on-https', options: {x: 2}}, ]; - const merged = resolveAuditsToDefns(audits); + const merged = await resolveAuditsToDefns(audits); expect(merged).toMatchObject([ {path: 'user-timings', options: {}}, {path: 'is-on-https', options: {x: 2, y: 1}}, @@ -408,7 +408,7 @@ describe('.resolveAuditsToDefns', () => { }); it('throws for invalid auditDefns', () => { - expect(() => resolveAuditsToDefns([new Gatherer()])).toThrow(/Invalid Audit type/); + expect(resolveAuditsToDefns([new Gatherer()])).rejects.toThrow(/Invalid Audit type/); }); }); diff --git a/lighthouse-core/test/config/config-test.js b/lighthouse-core/test/config/config-test.js index edf72d374a18..3d26f87c259a 100644 --- a/lighthouse-core/test/config/config-test.js +++ b/lighthouse-core/test/config/config-test.js @@ -29,15 +29,15 @@ describe('Config', () => { origConfig = JSON.parse(JSON.stringify(defaultConfig)); }); - it('returns new object', () => { + it('returns new object', async () => { const config = { audits: ['is-on-https'], }; - const newConfig = new Config(config); + const newConfig = Config.fromJson(config, {}); assert.notEqual(config, newConfig); }); - it('doesn\'t change directly injected gatherer implementations', () => { + it('doesn\'t change directly injected gatherer implementations', async () => { class MyGatherer extends Gatherer {} class MyAudit extends Audit { static get meta() { @@ -60,12 +60,12 @@ describe('Config', () => { }], audits: [MyAudit], }; - const newConfig = new Config(config); + const newConfig = await Config.fromJson(config); assert.equal(MyGatherer, newConfig.passes[0].gatherers[0].implementation); assert.equal(MyAudit, newConfig.audits[0].implementation); }); - it('doesn\'t change directly injected gatherer instances', () => { + it('doesn\'t change directly injected gatherer instances', async () => { class MyGatherer extends Gatherer { constructor(secretVal) { super(); @@ -87,7 +87,7 @@ describe('Config', () => { ], }], }; - const newConfig = new Config(config); + const newConfig = await Config.fromJson(config); const configGatherers = newConfig.passes[0].gatherers; assert(configGatherers[0].instance instanceof MyGatherer); assert.equal(configGatherers[0].instance.secret, 1729); @@ -95,13 +95,13 @@ describe('Config', () => { assert.equal(configGatherers[1].instance.secret, 6); }); - it('uses the default config when no config is provided', () => { - const config = new Config(); + it('uses the default config when no config is provided', async () => { + const config = await Config.fromJson(); assert.deepStrictEqual(config.categories, origConfig.categories); assert.deepStrictEqual(config.audits.map(a => a.path), origConfig.audits); }); - it('throws when a passName is used twice', () => { + it('throws when a passName is used twice', async () => { const unlikelyPassName = 'unlikelyPassName'; const configJson = { passes: [{ @@ -113,10 +113,10 @@ describe('Config', () => { }], }; - assert.throws(_ => new Config(configJson), /unique/); + await assert.rejects(Config.fromJson(configJson), /unique/); }); - it('defaults passName to defaultPass', () => { + it('defaults passName to defaultPass', async () => { class MyGatherer extends Gatherer {} const configJson = { passes: [{ @@ -124,7 +124,7 @@ describe('Config', () => { }], }; - const config = new Config(configJson); + const config = await Config.fromJson(configJson); const defaultPass = config.passes.find(pass => pass.passName === 'defaultPass'); assert.ok( defaultPass.gatherers.find(gatherer => gatherer.implementation === MyGatherer), @@ -132,7 +132,7 @@ describe('Config', () => { ); }); - it('throws when an audit requires an artifact with no gatherer supplying it', async () => { + it('throws when an audit requires an artifact with no gatherer supplying it', () => { class NeedsWhatYouCantGive extends Audit { static get meta() { return { @@ -153,11 +153,11 @@ describe('Config', () => { static audit() {} } - expect(() => new Config({ + expect(Config.fromJson({ extends: 'lighthouse:default', audits: [NeedsWhatYouCantGive], // eslint-disable-next-line max-len - })).toThrow('VRMLElements gatherer, required by audit missing-artifact-audit, was not found in config'); + })).rejects.toThrow('VRMLElements gatherer, required by audit missing-artifact-audit, was not found in config'); }); // eslint-disable-next-line max-len @@ -183,7 +183,7 @@ describe('Config', () => { } // Shouldn't throw. - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', audits: [DoesntNeedYourCrap], }, { @@ -214,7 +214,7 @@ describe('Config', () => { static audit() {} } - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', audits: [ButWillStillTakeYourCrap], }, { @@ -246,7 +246,7 @@ describe('Config', () => { static audit() {} } - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', audits: [ButWillStillTakeYourCrap], }, { @@ -278,7 +278,7 @@ describe('Config', () => { static audit() {} } - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', audits: [ButWillStillTakeYourCrap], }, { @@ -289,7 +289,7 @@ describe('Config', () => { .toEqual(['viewport-dimensions', 'source-maps']); }); - it('does not throw when an audit requires only base artifacts', () => { + it('does not throw when an audit requires only base artifacts', async () => { class BaseArtifactsAudit extends Audit { static get meta() { return { @@ -304,7 +304,7 @@ describe('Config', () => { static audit() {} } - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', audits: [BaseArtifactsAudit], }, {onlyAudits: ['base-artifacts-audit']}); @@ -313,7 +313,7 @@ describe('Config', () => { assert.strictEqual(config.audits[0].implementation.meta.id, 'base-artifacts-audit'); }); - it('throws for unknown gatherers', () => { + it('throws for unknown gatherers', async () => { const config = { passes: [{ gatherers: ['fuzz'], @@ -323,11 +323,10 @@ describe('Config', () => { ], }; - return assert.throws(_ => new Config(config), - /Unable to locate/); + await assert.rejects(Config.fromJson(config), /Unable to locate/); }); - it('doesn\'t mutate old gatherers when filtering passes', () => { + it('doesn\'t mutate old gatherers when filtering passes', async () => { const configJSON = { passes: [{ gatherers: [ @@ -339,12 +338,12 @@ describe('Config', () => { audits: ['is-on-https'], }; - const _ = new Config(configJSON); + const _ = await Config.fromJson(configJSON); assert.equal(configJSON.passes[0].gatherers.length, 3); }); - it('expands audits', () => { - const config = new Config({ + it('expands audits', async () => { + const config = await Config.fromJson({ audits: ['user-timings'], }); @@ -355,16 +354,16 @@ describe('Config', () => { assert.deepEqual(config.audits[0].options, {}); }); - it('throws when an audit is not found', () => { - return assert.throws(_ => new Config({ + it('throws when an audit is not found', async () => { + await assert.rejects(Config.fromJson({ audits: ['/fake-path/non-existent-audit'], }), /locate audit/); }); - it('throws on a non-absolute config path', () => { + it('throws on a non-absolute config path', async () => { const configPath = '../../config/default-config.js'; - return assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [], }, {configPath}), /absolute path/); }); @@ -372,13 +371,13 @@ describe('Config', () => { it('loads an audit relative to a config path', () => { const configPath = modulePath; - return assert.doesNotThrow(_ => new Config({ + return assert.doesNotThrow(_ => Config.fromJson({ audits: ['../fixtures/valid-custom-audit'], }, {configPath})); }); - it('loads an audit from node_modules/', () => { - return assert.throws(_ => new Config({ + it('loads an audit from node_modules/', async () => { + await assert.rejects(Config.fromJson({ // Use a lighthouse dep as a stand in for a module. audits: ['lighthouse-logger'], }), function(err) { @@ -387,20 +386,20 @@ describe('Config', () => { }); }); - it('loads an audit relative to the working directory', () => { + it('loads an audit relative to the working directory', async () => { // Construct an audit URL relative to current working directory, regardless // of where test was started from. const absoluteAuditPath = path.resolve(moduleDir, '../fixtures/valid-custom-audit'); assert.doesNotThrow(_ => require.resolve(absoluteAuditPath)); const relativePath = path.relative(process.cwd(), absoluteAuditPath); - return assert.doesNotThrow(_ => new Config({ + return assert.doesNotThrow(_ => Config.fromJson({ audits: [relativePath], })); }); - it('throws but not for missing audit when audit has a dependency error', () => { - return assert.throws(_ => new Config({ + it('throws but not for missing audit when audit has a dependency error', async () => { + await assert.rejects(Config.fromJson({ audits: [path.resolve(moduleDir, '../fixtures/invalid-audits/require-error.js')], }), function(err) { // We're expecting not to find parent class Audit, so only reject on our @@ -409,21 +408,21 @@ describe('Config', () => { }); }); - it('throws when it finds invalid audits', () => { + it('throws when it finds invalid audits', async () => { const basePath = path.resolve(moduleDir, '../fixtures/invalid-audits'); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [basePath + '/missing-audit'], }), /audit\(\) method/); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [basePath + '/missing-id'], }), /meta.id property/); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [basePath + '/missing-title'], }), /meta.title property/); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [ class BinaryButNoFailureTitleAudit extends Audit { static get meta() { @@ -443,11 +442,11 @@ describe('Config', () => { ], }), /no meta.failureTitle and should/); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [basePath + '/missing-description'], }), /meta.description property/); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [ class EmptyStringDescriptionAudit extends Audit { static get meta() { @@ -467,13 +466,13 @@ describe('Config', () => { ], }), /empty meta.description string/); - assert.throws(_ => new Config({ + await assert.rejects(Config.fromJson({ audits: [basePath + '/missing-required-artifacts'], }), /meta.requiredArtifacts property/); }); - it('throws when a category references a non-existent audit', () => { - return assert.throws(_ => new Config({ + it('throws when a category references a non-existent audit', async () => { + await assert.rejects(Config.fromJson({ audits: [], categories: { pwa: { @@ -485,8 +484,8 @@ describe('Config', () => { }), /could not find missing-audit/); }); - it('throws when a category fails to specify an audit id', () => { - return assert.throws(_ => new Config({ + it('throws when a category fails to specify an audit id', async () => { + await assert.rejects(Config.fromJson({ audits: [], categories: { pwa: { @@ -498,8 +497,8 @@ describe('Config', () => { }), /missing an audit id at pwa\[0\]/); }); - it('throws when an accessibility audit does not have a group', () => { - return assert.throws(_ => new Config({ + it('throws when an accessibility audit does not have a group', async () => { + await assert.rejects(Config.fromJson({ audits: ['accessibility/color-contrast'], categories: { accessibility: { @@ -511,8 +510,8 @@ describe('Config', () => { }), /does not have a group/); }); - it('throws when an audit references an unknown group', () => { - return assert.throws(_ => new Config({ + it('throws when an audit references an unknown group', async () => { + await assert.rejects(Config.fromJson({ groups: { 'group-a': { title: 'Group A', @@ -531,8 +530,8 @@ describe('Config', () => { }), /unknown group missing-group/); }); - it('throws when a manual audit has weight', () => { - return assert.throws(_ => new Config({ + it('throws when a manual audit has weight', async () => { + await assert.rejects(Config.fromJson({ audits: ['manual/pwa-cross-browser'], categories: { accessibility: { @@ -544,8 +543,8 @@ describe('Config', () => { }), /cross-browser .*has a positive weight/); }); - it('filters the config', () => { - const config = new Config({ + it('filters the config', async () => { + const config = await Config.fromJson({ settings: { onlyCategories: ['needed-category'], onlyAudits: ['color-contrast'], @@ -589,8 +588,8 @@ describe('Config', () => { assert.equal(config.categories['other-category'].auditRefs.length, 1); }); - it('filters the config w/ skipAudits', () => { - const config = new Config({ + it('filters the config w/ skipAudits', async () => { + const config = await Config.fromJson({ settings: { skipAudits: ['first-meaningful-paint'], }, @@ -629,11 +628,11 @@ describe('Config', () => { }); - it('filtering filters out traces when not needed', () => { + it('filtering filters out traces when not needed', async () => { const warnings = []; const saveWarning = evt => warnings.push(evt); log.events.addListener('warning', saveWarning); - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { onlyCategories: ['accessibility'], @@ -647,11 +646,11 @@ describe('Config', () => { assert.equal(config.passes[0].recordTrace, false, 'turns off tracing if not needed'); }); - it('forces the first pass to have a fatal loadFailureMode', () => { + it('forces the first pass to have a fatal loadFailureMode', async () => { const warnings = []; const saveWarning = evt => warnings.push(evt); log.events.addListener('warning', saveWarning); - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { onlyCategories: ['performance', 'pwa'], @@ -667,8 +666,8 @@ describe('Config', () => { expect(config.passes[0]).toHaveProperty('loadFailureMode', 'fatal'); }); - it('filters works with extension', () => { - const config = new Config({ + it('filters works with extension', async () => { + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { onlyCategories: ['performance'], @@ -684,11 +683,11 @@ describe('Config', () => { assert.ok(config.audits.find(a => a.implementation.meta.id === 'full-page-screenshot')); }); - it('warns for invalid filters', () => { + it('warns for invalid filters', async () => { const warnings = []; const saveWarning = evt => warnings.push(evt); log.events.addListener('warning', saveWarning); - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { onlyCategories: ['performance', 'missing-category'], @@ -701,9 +700,9 @@ describe('Config', () => { assert.equal(warnings.length, 3, 'did not warn enough'); }); - it('throws for invalid use of skipAudits and onlyAudits', () => { - assert.throws(() => { - new Config({ + it('throws for invalid use of skipAudits and onlyAudits', async () => { + await assert.rejects(() => { + return Config.fromJson({ extends: 'lighthouse:default', settings: { onlyAudits: ['first-meaningful-paint'], @@ -713,19 +712,22 @@ describe('Config', () => { }); }); - it('cleans up flags for settings', () => { - const config = new Config({extends: 'lighthouse:default'}, + it('cleans up flags for settings', async () => { + const config = await Config.fromJson({extends: 'lighthouse:default'}, {nonsense: 1, foo: 2, throttlingMethod: 'provided'}); assert.equal(config.settings.throttlingMethod, 'provided'); assert.ok(config.settings.nonsense === undefined, 'did not cleanup settings'); }); - it('allows overriding of array-typed settings', () => { - const config = new Config({extends: 'lighthouse:default'}, {output: ['html']}); + it('allows overriding of array-typed settings', async () => { + const config = await Config.fromJson( + {extends: 'lighthouse:default'}, + {output: ['html']} + ); assert.deepStrictEqual(config.settings.output, ['html']); }); - it('extends the config', () => { + it('extends the config', async () => { class CustomAudit extends Audit { static get meta() { return { @@ -742,7 +744,7 @@ describe('Config', () => { } } - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', audits: [ CustomAudit, @@ -756,8 +758,8 @@ describe('Config', () => { assert.ok(auditNames.has('first-meaningful-paint'), 'did not include default audits'); }); - it('ensures quiet thresholds are sufficient when using devtools', () => { - const config = new Config({ + it('ensures quiet thresholds are sufficient when using devtools', async () => { + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { throttlingMethod: 'devtools', @@ -773,8 +775,8 @@ describe('Config', () => { assert.equal(config.passes[1].pauseAfterLoadMs, 0, 'should not have touched non-defaultPass'); }); - it('does nothing when thresholds for devtools are already sufficient', () => { - const config = new Config({ + it('does nothing when thresholds for devtools are already sufficient', async () => { + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { throttlingMethod: 'devtools', @@ -796,15 +798,15 @@ describe('Config', () => { }); it('only supports `lighthouse:default` extension', () => { - const createConfig = extendsValue => new Config({extends: extendsValue}); + const createConfig = extendsValue => Config.fromJson({extends: extendsValue}); - expect(() => createConfig(true)).toThrowError(/default` is the only valid extension/); - expect(() => createConfig('lighthouse')).toThrowError(/default` is the only valid/); - expect(() => createConfig('lighthouse:full')).toThrowError(/default` is the only valid/); + expect(createConfig(true)).rejects.toThrow(/default` is the only valid extension/); + expect(createConfig('lighthouse')).rejects.toThrow(/default` is the only valid/); + expect(createConfig('lighthouse:full')).rejects.toThrow(/default` is the only valid/); }); - it('merges settings with correct priority', () => { - const config = new Config( + it('merges settings with correct priority', async () => { + const config = await Config.fromJson( { extends: 'lighthouse:default', settings: { @@ -824,42 +826,45 @@ describe('Config', () => { assert.ok(config.settings.formFactor === 'desktop', 'missing setting from flags'); }); - it('inherits default settings when undefined', () => { - const config = new Config({settings: undefined}); + it('inherits default settings when undefined', async () => { + const config = await Config.fromJson({settings: undefined}); assert.ok(typeof config.settings.maxWaitForLoad === 'number', 'missing setting from default'); }); describe('locale', () => { - it('falls back to default locale if none specified', () => { - const config = new Config({settings: undefined}); + it('falls back to default locale if none specified', async () => { + const config = await Config.fromJson({settings: undefined}); // Don't assert specific locale so it isn't tied to where tests are run, but // check that it's valid and available. assert.ok(config.settings.locale); assert.strictEqual(config.settings.locale, i18n.lookupLocale(config.settings.locale)); }); - it('uses config setting for locale if set', () => { + it('uses config setting for locale if set', async () => { const locale = 'ar-XB'; - const config = new Config({settings: {locale}}); + const config = await Config.fromJson({settings: {locale}}); assert.strictEqual(config.settings.locale, locale); }); - it('uses flag setting for locale if set', () => { + it('uses flag setting for locale if set', async () => { const settingsLocale = 'en-XA'; const flagsLocale = 'ar-XB'; - const config = new Config({settings: {locale: settingsLocale}}, {locale: flagsLocale}); + const config = await Config.fromJson({ + settings: {locale: settingsLocale}}, + {locale: flagsLocale} + ); assert.strictEqual(config.settings.locale, flagsLocale); }); }); describe('emulatedUserAgent', () => { - it('uses the default UA string when emulatedUserAgent is undefined', () => { - const config = new Config({}); + it('uses the default UA string when emulatedUserAgent is undefined', async () => { + const config = await Config.fromJson({}); expect(config.settings.emulatedUserAgent).toMatch(/^Mozilla\/5.*Chrome-Lighthouse$/); }); - it('uses the default UA string when emulatedUserAgent is true', () => { - const config = new Config({ + it('uses the default UA string when emulatedUserAgent is true', async () => { + const config = await Config.fromJson({ settings: { emulatedUserAgent: true, }, @@ -867,8 +872,8 @@ describe('Config', () => { expect(config.settings.emulatedUserAgent).toMatch(/^Mozilla\/5.*Chrome-Lighthouse$/); }); - it('does not use a UA string when emulatedUserAgent is false', () => { - const config = new Config({ + it('does not use a UA string when emulatedUserAgent is false', async () => { + const config = await Config.fromJson({ settings: { emulatedUserAgent: false, }, @@ -876,9 +881,9 @@ describe('Config', () => { expect(config.settings.emulatedUserAgent).toEqual(false); }); - it('uses the UA string provided if it is a string', () => { + it('uses the UA string provided if it is a string', async () => { const emulatedUserAgent = 'one weird trick to get a perfect LH score'; - const config = new Config({ + const config = await Config.fromJson({ settings: { emulatedUserAgent, }, @@ -887,29 +892,30 @@ describe('Config', () => { }); }); - it('is idempotent when accepting a canonicalized Config as valid ConfigJson input', () => { - const config = new Config(defaultConfig); - const configAgain = new Config(config); + it('is idempotent when accepting a canonicalized Config as valid ConfigJson input', async () => { + const config = await Config.fromJson(defaultConfig); + const configAgain = await Config.fromJson(config); assert.deepEqual(config, configAgain); }); - it('is idempotent accepting a canonicalized filtered Config as valid ConfigJson input', () => { + // eslint-disable-next-line max-len + it('is idempotent accepting a canonicalized filtered Config as valid ConfigJson input', async () => { const extendedJson = { extends: 'lighthouse:default', settings: { onlyCategories: ['pwa'], }, }; - const config = new Config(extendedJson); + const config = await Config.fromJson(extendedJson); assert.equal(config.passes.length, 2, 'did not filter config'); assert.equal(Object.keys(config.categories).length, 1, 'did not filter config'); assert.deepEqual(config.settings.onlyCategories, ['pwa']); - const configAgain = new Config(config); + const configAgain = await Config.fromJson(config); assert.deepEqual(config, configAgain); }); describe('#extendConfigJSON', () => { - it('should merge passes', () => { + it('should merge passes', async () => { const configA = { passes: [ {passName: 'passA', gatherers: ['a']}, @@ -931,14 +937,14 @@ describe('Config', () => { assert.deepEqual(merged.passes[3].gatherers, ['e']); }); - it('should merge audits', () => { + it('should merge audits', async () => { const configA = {audits: ['a', 'b']}; const configB = {audits: ['c']}; const merged = Config.extendConfigJSON(configA, configB); assert.deepEqual(merged.audits, ['a', 'b', 'c']); }); - it('should merge categories', () => { + it('should merge categories', async () => { const configA = {categories: {A: {title: 'Acat'}, B: {title: 'Bcat'}}}; const configB = {categories: {C: {title: 'Ccat'}}}; const merged = Config.extendConfigJSON(configA, configB); @@ -949,7 +955,7 @@ describe('Config', () => { }); }); - it('should merge other values', () => { + it('should merge other values', async () => { const artifacts = { traces: {defaultPass: '../some/long/path'}, devtoolsLogs: {defaultPass: 'path/to/devtools/log'}, @@ -966,17 +972,17 @@ describe('Config', () => { // Include a configPath flag so that config.js looks for the plugins in the fixtures dir. const configFixturePath = moduleDir + '/../fixtures/config-plugins/'; - it('should append audits', () => { + it('should append audits', async () => { const configJson = { audits: ['installable-manifest', 'metrics'], plugins: ['lighthouse-plugin-simple'], }; - const config = new Config(configJson, {configPath: configFixturePath}); + const config = await Config.fromJson(configJson, {configPath: configFixturePath}); assert.deepStrictEqual(config.audits.map(a => a.path), ['installable-manifest', 'metrics', 'redirects', 'user-timings']); }); - it('should append and use plugin-prefixed groups', () => { + it('should append and use plugin-prefixed groups', async () => { const configJson = { audits: ['installable-manifest', 'metrics'], plugins: ['lighthouse-plugin-simple'], @@ -984,7 +990,7 @@ describe('Config', () => { configGroup: {title: 'This is a group in the base config'}, }, }; - const config = new Config(configJson, {configPath: configFixturePath}); + const config = await Config.fromJson(configJson, {configPath: configFixturePath}); const groupIds = Object.keys(config.groups); assert.ok(groupIds.length === 2); @@ -995,12 +1001,12 @@ describe('Config', () => { 'lighthouse-plugin-simple-new-group'); }); - it('should append a category', () => { + it('should append a category', async () => { const configJson = { extends: 'lighthouse:default', plugins: ['lighthouse-plugin-simple'], }; - const config = new Config(configJson, {configPath: configFixturePath}); + const config = await Config.fromJson(configJson, {configPath: configFixturePath}); const categoryNames = Object.keys(config.categories); assert.ok(categoryNames.length > 1); assert.strictEqual(categoryNames[categoryNames.length - 1], 'lighthouse-plugin-simple'); @@ -1008,7 +1014,7 @@ describe('Config', () => { }); describe('budget setting', () => { - it('should be initialized', () => { + it('should be initialized', async () => { const configJson = { settings: { budgets: [{ @@ -1020,19 +1026,20 @@ describe('Config', () => { }], }, }; - const config = new Config(configJson); + const config = await Config.fromJson(configJson); assert.equal(config.settings.budgets[0].resourceCounts.length, 1); assert.equal(config.settings.budgets[0].resourceCounts[0].resourceType, 'image'); assert.equal(config.settings.budgets[0].resourceCounts[0].budget, 500); }); - it('should throw when provided an invalid budget', () => { - assert.throws(() => new Config({settings: {budgets: ['invalid123']}}), + it('should throw when provided an invalid budget', async () => { + await assert.rejects( + () => Config.fromJson({settings: {budgets: ['invalid123']}}), /Budget file is not defined as an array of budgets/); }); }); - it('should load plugins from the config and from passed-in flags', () => { + it('should load plugins from the config and from passed-in flags', async () => { const baseConfigJson = { audits: ['installable-manifest'], categories: { @@ -1047,14 +1054,14 @@ describe('Config', () => { const allConfigConfigJson = {...baseConfigJson, plugins: [simplePluginName, noGroupsPluginName]}; - const allPluginsInConfigConfig = new Config(allConfigConfigJson, baseFlags); + const allPluginsInConfigConfig = await Config.fromJson(allConfigConfigJson, baseFlags); const allFlagsFlags = {...baseFlags, plugins: [simplePluginName, noGroupsPluginName]}; - const allPluginsInFlagsConfig = new Config(baseConfigJson, allFlagsFlags); + const allPluginsInFlagsConfig = await Config.fromJson(baseConfigJson, allFlagsFlags); const mixedConfigJson = {...baseConfigJson, plugins: [simplePluginName]}; const mixedFlags = {...baseFlags, plugins: [noGroupsPluginName]}; - const pluginsInConfigAndFlagsConfig = new Config(mixedConfigJson, mixedFlags); + const pluginsInConfigAndFlagsConfig = await Config.fromJson(mixedConfigJson, mixedFlags); // Double check that we're not comparing empty objects. const categoryNames = Object.keys(allPluginsInConfigConfig.categories); @@ -1065,35 +1072,38 @@ describe('Config', () => { assert.deepStrictEqual(allPluginsInConfigConfig, pluginsInConfigAndFlagsConfig); }); - it('should throw if the plugin is invalid', () => { + it('should throw if the plugin is invalid', async () => { const configJson = { extends: 'lighthouse:default', plugins: ['lighthouse-plugin-no-category'], }; // Required to have a `category`, so plugin is invalid. - assert.throws(() => new Config(configJson, {configPath: configFixturePath}), + await assert.rejects( + () => Config.fromJson(configJson, {configPath: configFixturePath}), /^Error: lighthouse-plugin-no-category has no valid category/); }); - it('should throw if the plugin is not found', () => { + it('should throw if the plugin is not found', async () => { const configJson = { extends: 'lighthouse:default', plugins: ['lighthouse-plugin-not-a-plugin'], }; - assert.throws(() => new Config(configJson, {configPath: configFixturePath}), + await assert.rejects( + () => Config.fromJson(configJson, {configPath: configFixturePath}), /^Error: Unable to locate plugin: `lighthouse-plugin-not-a-plugin/); }); - it('should throw if the plugin name does not begin with "lighthouse-plugin-"', () => { + it('should throw if the plugin name does not begin with "lighthouse-plugin-"', async () => { const configJson = { extends: 'lighthouse:default', plugins: ['just-let-me-be-a-plugin'], }; - assert.throws(() => new Config(configJson, {configPath: configFixturePath}), + await assert.rejects( + () => Config.fromJson(configJson, {configPath: configFixturePath}), /^Error: plugin name 'just-let-me-be-a-plugin' does not start with 'lighthouse-plugin-'/); }); - it('should throw if the plugin name would shadow a category id', () => { + it('should throw if the plugin name would shadow a category id', async () => { const configJson = { extends: 'lighthouse:default', plugins: ['lighthouse-plugin-simple'], @@ -1101,25 +1111,26 @@ describe('Config', () => { 'lighthouse-plugin-simple': {auditRefs: [{id: 'missing-audit'}]}, }, }; - assert.throws(() => new Config(configJson, {configPath: configFixturePath}), + await assert.rejects( + () => Config.fromJson(configJson, {configPath: configFixturePath}), /^Error: plugin name 'lighthouse-plugin-simple' not allowed because it is the id of a category/); // eslint-disable-line max-len }); }); describe('filterConfigIfNeeded', () => { - it('should not mutate the original config', () => { + it('should not mutate the original config', async () => { const configCopy = JSON.parse(JSON.stringify(origConfig)); configCopy.settings.onlyCategories = ['performance']; - const config = new Config(configCopy); + const config = await Config.fromJson(configCopy); configCopy.settings.onlyCategories = null; assert.equal(config.passes.length, 1, 'did not filter config'); assert.deepStrictEqual(configCopy, origConfig, 'had mutations'); }); - it('should generate the same filtered config, extended or original', () => { + it('should generate the same filtered config, extended or original', async () => { const configCopy = JSON.parse(JSON.stringify(origConfig)); configCopy.settings.onlyCategories = ['performance']; - const config = new Config(configCopy); + const config = await Config.fromJson(configCopy); const extended = { extends: 'lighthouse:default', @@ -1127,7 +1138,7 @@ describe('Config', () => { onlyCategories: ['performance'], }, }; - const extendedConfig = new Config(extended); + const extendedConfig = await Config.fromJson(extended); // When gatherers have instance properties that are bind()'d, they'll not match. // Gatherers in each config will still be compared via the constructor on .implementation. @@ -1146,7 +1157,7 @@ describe('Config', () => { expect(config).toEqual(extendedConfig); // ensure we didn't have mutations }); - it('should filter out other passes if passed Performance', () => { + it('should filter out other passes if passed Performance', async () => { const totalAuditCount = origConfig.audits.length; const extended = { extends: 'lighthouse:default', @@ -1154,13 +1165,13 @@ describe('Config', () => { onlyCategories: ['performance'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); assert.equal(Object.keys(config.categories).length, 1, 'other categories are present'); assert.equal(config.passes.length, 1, 'incorrect # of passes'); assert.ok(config.audits.length < totalAuditCount, 'audit filtering probably failed'); }); - it('should filter out other passes if passed PWA', () => { + it('should filter out other passes if passed PWA', async () => { const totalAuditCount = origConfig.audits.length; const extended = { extends: 'lighthouse:default', @@ -1168,12 +1179,12 @@ describe('Config', () => { onlyCategories: ['pwa'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); assert.equal(Object.keys(config.categories).length, 1, 'other categories are present'); assert.ok(config.audits.length < totalAuditCount, 'audit filtering probably failed'); }); - it('should filter out other passes if passed Best Practices', () => { + it('should filter out other passes if passed Best Practices', async () => { const totalAuditCount = origConfig.audits.length; const extended = { extends: 'lighthouse:default', @@ -1181,20 +1192,20 @@ describe('Config', () => { onlyCategories: ['best-practices'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); assert.equal(Object.keys(config.categories).length, 1, 'other categories are present'); assert.equal(config.passes.length, 1, 'incorrect # of passes'); assert.ok(config.audits.length < totalAuditCount, 'audit filtering probably failed'); }); - it('should only run audits for ones named by the category', () => { + it('should only run audits for ones named by the category', async () => { const extended = { extends: 'lighthouse:default', settings: { onlyCategories: ['performance'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); const selectedCategory = origConfig.categories.performance; // +1 for `full-page-screenshot`. const auditCount = Object.keys(selectedCategory.auditRefs).length + 1; @@ -1203,19 +1214,19 @@ describe('Config', () => { assert.ok(config.audits.find(a => a.implementation.meta.id === 'full-page-screenshot')); }); - it('should only run specified audits', () => { + it('should only run specified audits', async () => { const extended = { extends: 'lighthouse:default', settings: { onlyAudits: ['service-worker'], // something from non-defaultPass }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); assert.equal(config.passes.length, 2, 'incorrect # of passes'); assert.equal(config.audits.length, 1, 'audit filtering failed'); }); - it('should combine audits and categories additively', () => { + it('should combine audits and categories additively', async () => { const extended = { extends: 'lighthouse:default', settings: { @@ -1223,7 +1234,7 @@ describe('Config', () => { onlyAudits: ['service-worker'], // something from non-defaultPass }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); const selectedCategory = origConfig.categories.performance; // +1 for `service-worker`, +1 for `full-page-screenshot`. const auditCount = Object.keys(selectedCategory.auditRefs).length + 2; @@ -1233,7 +1244,7 @@ describe('Config', () => { assert.ok(config.audits.find(a => a.implementation.meta.id === 'full-page-screenshot')); }); - it('should support redundant filtering', () => { + it('should support redundant filtering', async () => { const extended = { extends: 'lighthouse:default', settings: { @@ -1241,7 +1252,7 @@ describe('Config', () => { onlyAudits: ['apple-touch-icon'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); const selectedCategory = origConfig.categories.pwa; // +1 for `full-page-screenshot`. const auditCount = Object.keys(selectedCategory.auditRefs).length + 1; @@ -1250,7 +1261,7 @@ describe('Config', () => { assert.ok(config.audits.find(a => a.implementation.meta.id === 'full-page-screenshot')); }); - it('should keep full-page-screenshot even if onlyCategories is set', () => { + it('should keep full-page-screenshot even if onlyCategories is set', async () => { assert.ok(origConfig.audits.includes('full-page-screenshot')); // full-page-screenshot does not belong to a category. const matchCategories = Object.values(origConfig.categories).filter(cat => @@ -1263,55 +1274,55 @@ describe('Config', () => { onlyCategories: ['accessibility'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); assert.ok(config.audits.find(a => a.implementation.meta.id === 'full-page-screenshot')); }); - it('should keep full-page-screenshot even if skipAudits is set', () => { + it('should keep full-page-screenshot even if skipAudits is set', async () => { const extended = { extends: 'lighthouse:default', settings: { skipAudits: ['font-size'], }, }; - const config = new Config(extended); + const config = await Config.fromJson(extended); assert.ok(config.audits.find(a => a.implementation.meta.id === 'full-page-screenshot')); }); }); describe('#requireAudits', () => { - it('should merge audits', () => { + it('should merge audits', async () => { const audits = [ 'user-timings', {path: 'is-on-https', options: {x: 1, y: 1}}, {path: 'is-on-https', options: {x: 2}}, ]; - const merged = Config.requireAudits(audits); + const merged = await Config.requireAudits(audits); // Round-trip through JSON to drop live 'implementation' prop. const mergedJson = JSON.parse(JSON.stringify(merged)); assert.deepEqual(mergedJson, [{path: 'user-timings', options: {}}, {path: 'is-on-https', options: {x: 2, y: 1}}]); }); - it('throws for invalid auditDefns', () => { + it('throws for invalid auditDefns', async () => { const configJson = { audits: [ new Gatherer(), ], }; - assert.throws(_ => new Config(configJson), /Invalid Audit type/); + await assert.rejects(Config.fromJson(configJson), /Invalid Audit type/); }); }); describe('#requireGatherers', () => { - it('should deduplicate gatherers', () => { + it('should deduplicate gatherers', async () => { const gatherers = [ 'viewport-dimensions', {path: 'viewport-dimensions'}, ]; - const merged = Config.requireGatherers([{gatherers}]); + const merged = await Config.requireGatherers([{gatherers}]); // Round-trip through JSON to drop live 'instance'/'implementation' props. const mergedJson = JSON.parse(JSON.stringify(merged)); @@ -1324,26 +1335,26 @@ describe('Config', () => { [{path: 'viewport-dimensions', instance: expectedInstance}]); }); - function loadGatherer(gathererEntry) { - const config = new Config({passes: [{gatherers: [gathererEntry]}]}); + async function loadGatherer(gathererEntry) { + const config = await Config.fromJson({passes: [{gatherers: [gathererEntry]}]}); return config.passes[0].gatherers[0]; } - it('loads a core gatherer', () => { - const gatherer = loadGatherer('viewport-dimensions'); + it('loads a core gatherer', async () => { + const gatherer = await loadGatherer('viewport-dimensions'); assert.equal(gatherer.instance.name, 'ViewportDimensions'); assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('loads gatherers from custom paths', () => { + it('loads gatherers from custom paths', async () => { const customPath = path.resolve(moduleDir, '../fixtures/valid-custom-gatherer'); - const gatherer = loadGatherer(customPath); + const gatherer = await loadGatherer(customPath); assert.equal(gatherer.instance.name, 'CustomGatherer'); assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('loads a gatherer relative to a config path', () => { - const config = new Config({ + it('loads a gatherer relative to a config path', async () => { + const config = await Config.fromJson({ passes: [{gatherers: ['../fixtures/valid-custom-gatherer']}], }, {configPath: modulePath}); const gatherer = config.passes[0].gatherers[0]; @@ -1352,54 +1363,55 @@ describe('Config', () => { assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('returns gatherer when gatherer class, not package-name string, is provided', () => { + it('returns gatherer when gatherer class, not package-name string, is provided', async () => { class TestGatherer extends Gatherer {} - const gatherer = loadGatherer(TestGatherer); + const gatherer = await loadGatherer(TestGatherer); assert.equal(gatherer.instance.name, 'TestGatherer'); assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('returns gatherer when gatherer instance, not package-name string, is provided', () => { + // eslint-disable-next-line max-len + it('returns gatherer when gatherer instance, not package-name string, is provided', async () => { class TestGatherer extends Gatherer {} - const gatherer = loadGatherer(new TestGatherer()); + const gatherer = await loadGatherer(new TestGatherer()); assert.equal(gatherer.instance.name, 'TestGatherer'); assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('returns gatherer when `gathererDefn` with instance is provided', () => { + it('returns gatherer when `gathererDefn` with instance is provided', async () => { class TestGatherer extends Gatherer {} - const gatherer = loadGatherer({instance: new TestGatherer()}); + const gatherer = await loadGatherer({instance: new TestGatherer()}); assert.equal(gatherer.instance.name, 'TestGatherer'); assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('throws when a gatherer is not found', () => { - assert.throws(_ => loadGatherer('/fake-non-existent-gatherer'), /locate gatherer/); + it('throws when a gatherer is not found', async () => { + await assert.rejects(loadGatherer('/fake-non-existent-gatherer'), /locate gatherer/); }); - it('loads a gatherer from node_modules/', () => { + it('loads a gatherer from node_modules/', async () => { // Use a lighthouse dep as a stand in for a module. - assert.throws(_ => loadGatherer('lighthouse-logger'), function(err) { + await assert.rejects(loadGatherer('lighthouse-logger'), function(err) { // Should throw a gatherer validation error, but *not* a gatherer not found error. return !/locate gatherer/.test(err) && /beforePass\(\) method/.test(err); }); }); - it('loads a gatherer relative to the working directory', () => { + it('loads a gatherer relative to the working directory', async () => { // Construct a gatherer URL relative to current working directory, // regardless of where test was started from. const absoluteGathererPath = path.resolve(moduleDir, '../fixtures/valid-custom-gatherer'); assert.doesNotThrow(_ => require.resolve(absoluteGathererPath)); const relativeGathererPath = path.relative(process.cwd(), absoluteGathererPath); - const gatherer = loadGatherer(relativeGathererPath); + const gatherer = await loadGatherer(relativeGathererPath); assert.equal(gatherer.instance.name, 'CustomGatherer'); assert.equal(typeof gatherer.instance.beforePass, 'function'); }); - it('throws but not for missing gatherer when it has a dependency error', () => { + it('throws but not for missing gatherer when it has a dependency error', async () => { const gathererPath = path.resolve(moduleDir, '../fixtures/invalid-gatherers/require-error'); - return assert.throws(_ => loadGatherer(gathererPath), + await assert.rejects(loadGatherer(gathererPath), function(err) { // We're expecting not to find parent class Gatherer, so only reject on // our own custom locate gatherer error, not the usual MODULE_NOT_FOUND. @@ -1407,28 +1419,27 @@ describe('Config', () => { }); }); - it('throws for invalid gathererDefns', () => { - assert.throws(_ => loadGatherer({path: 55}), /Invalid Gatherer type/); - - assert.throws(_ => loadGatherer(new Audit()), /Invalid Gatherer type/); + it('throws for invalid gathererDefns', async () => { + await assert.rejects(loadGatherer({path: 55}), /Invalid Gatherer type/); + await assert.rejects(loadGatherer(new Audit()), /Invalid Gatherer type/); }); - it('throws for invalid gatherers', () => { + it('throws for invalid gatherers', async () => { const root = path.resolve(moduleDir, '../fixtures/invalid-gatherers'); - assert.throws(_ => loadGatherer(`${root}/missing-before-pass`), + await assert.rejects(loadGatherer(`${root}/missing-before-pass`), /beforePass\(\) method/); - assert.throws(_ => loadGatherer(`${root}/missing-pass`), + await assert.rejects(loadGatherer(`${root}/missing-pass`), /pass\(\) method/); - assert.throws(_ => loadGatherer(`${root}/missing-after-pass`), + await assert.rejects(loadGatherer(`${root}/missing-after-pass`), /afterPass\(\) method/); }); }); describe('#getPrintString', () => { - it('doesn\'t include empty audit options in output', () => { + it('doesn\'t include empty audit options in output', async () => { const aOpt = 'auditOption'; const configJson = { extends: 'lighthouse:default', @@ -1444,7 +1455,7 @@ describe('Config', () => { ], }; - const printed = new Config(configJson).getPrintString(); + const printed = (await Config.fromJson(configJson)).getPrintString(); const printedConfig = JSON.parse(printed); // Check that options weren't completely eliminated. @@ -1458,8 +1469,8 @@ describe('Config', () => { } }); - it('prints localized category titles', () => { - const printed = new Config(defaultConfig).getPrintString(); + it('prints localized category titles', async () => { + const printed = (await Config.fromJson(defaultConfig)).getPrintString(); const printedConfig = JSON.parse(printed); let localizableCount = 0; @@ -1475,12 +1486,12 @@ describe('Config', () => { assert.ok(localizableCount > 0); }); - it('prints a valid ConfigJson that can make an identical Config', () => { + it('prints a valid ConfigJson that can make an identical Config', async () => { // depends on defaultConfig having a `path` for all gatherers and audits. - const firstConfig = new Config(defaultConfig); + const firstConfig = await Config.fromJson(defaultConfig); const firstPrint = firstConfig.getPrintString(); - const secondConfig = new Config(JSON.parse(firstPrint)); + const secondConfig = await Config.fromJson(JSON.parse(firstPrint)); const secondPrint = secondConfig.getPrintString(); assert.strictEqual(firstPrint, secondPrint); diff --git a/lighthouse-core/test/fraggle-rock/config/config-test.js b/lighthouse-core/test/fraggle-rock/config/config-test.js index db04fcc899f6..02661c201a56 100644 --- a/lighthouse-core/test/fraggle-rock/config/config-test.js +++ b/lighthouse-core/test/fraggle-rock/config/config-test.js @@ -22,30 +22,29 @@ describe('Fraggle Rock Config', () => { gatherMode = 'snapshot'; }); - it('should throw if the config path is not absolute', () => { - const configFn = () => - initializeConfig(undefined, {gatherMode, configPath: '../relative/path'}); - expect(configFn).toThrow(/must be an absolute path/); + it('should throw if the config path is not absolute', async () => { + expect(initializeConfig(undefined, {gatherMode, configPath: '../relative/path'})) + .rejects.toThrow(/must be an absolute path/); }); - it('should not mutate the original input', () => { + it('should not mutate the original input', async () => { const configJson = {artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}]}; - const {config} = initializeConfig(configJson, {gatherMode}); + const {config} = await initializeConfig(configJson, {gatherMode}); expect(configJson).toEqual({artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}]}); expect(config).not.toBe(configJson); expect(config).not.toEqual(configJson); expect(config.artifacts).toMatchObject([{gatherer: {path: 'accessibility'}}]); }); - it('should use default config when none passed in', () => { - const {config} = initializeConfig(undefined, {gatherMode}); + it('should use default config when none passed in', async () => { + const {config} = await initializeConfig(undefined, {gatherMode}); expect(config.settings).toMatchObject({formFactor: 'mobile'}); if (!config.audits) throw new Error('Did not define audits'); expect(config.audits.length).toBeGreaterThan(0); }); - it('should resolve settings with defaults', () => { - const {config} = initializeConfig( + it('should resolve settings with defaults', async () => { + const {config} = await initializeConfig( {settings: {output: 'csv', maxWaitForFcp: 1234}}, {settingsOverrides: {maxWaitForFcp: 12345}, gatherMode} ); @@ -57,8 +56,8 @@ describe('Fraggle Rock Config', () => { }); }); - it('should override throttlingMethod in timespan mode', () => { - const {config} = initializeConfig( + it('should override throttlingMethod in timespan mode', async () => { + const {config} = await initializeConfig( undefined, {settingsOverrides: {throttlingMethod: 'simulate'}, gatherMode: 'timespan'} ); @@ -68,9 +67,9 @@ describe('Fraggle Rock Config', () => { }); }); - it('should resolve artifact definitions', () => { + it('should resolve artifact definitions', async () => { const configJson = {artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}]}; - const {config} = initializeConfig(configJson, {gatherMode}); + const {config} = await initializeConfig(configJson, {gatherMode}); expect(config).toMatchObject({ artifacts: [{id: 'Accessibility', gatherer: {path: 'accessibility'}}], @@ -81,10 +80,10 @@ describe('Fraggle Rock Config', () => { const nonFRGatherer = new BaseGatherer(); nonFRGatherer.getArtifact = jest.fn(); const configJson = {artifacts: [{id: 'LegacyGather', gatherer: {instance: nonFRGatherer}}]}; - expect(() => initializeConfig(configJson, {gatherMode})).toThrow(/FRGatherer gatherer/); + expect(initializeConfig(configJson, {gatherMode})).rejects.toThrow(/FRGatherer gatherer/); }); - it('should filter configuration by gatherMode', () => { + it('should filter configuration by gatherMode', async () => { const timespanGatherer = new BaseGatherer(); timespanGatherer.getArtifact = jest.fn(); timespanGatherer.meta = {supportedModes: ['timespan']}; @@ -96,14 +95,14 @@ describe('Fraggle Rock Config', () => { ], }; - const {config} = initializeConfig(configJson, {gatherMode: 'snapshot'}); + const {config} = await initializeConfig(configJson, {gatherMode: 'snapshot'}); expect(config).toMatchObject({ artifacts: [{id: 'Accessibility', gatherer: {path: 'accessibility'}}], }); }); - it('should filter configuration by only/skip filters', () => { - const {config} = initializeConfig(undefined, { + it('should filter configuration by only/skip filters', async () => { + const {config} = await initializeConfig(undefined, { gatherMode: 'navigation', settingsOverrides: { onlyAudits: ['color-contrast'], @@ -119,8 +118,8 @@ describe('Fraggle Rock Config', () => { expect(auditIds).not.toContain('robots-txt'); // from skipAudits }); - it('should support plugins', () => { - const {config} = initializeConfig(undefined, { + it('should support plugins', async () => { + const {config} = await initializeConfig(undefined, { gatherMode: 'navigation', configPath: `${LH_ROOT}/lighthouse-core/test/fixtures/config-plugins/`, settingsOverrides: {plugins: ['lighthouse-plugin-simple']}, @@ -169,8 +168,8 @@ describe('Fraggle Rock Config', () => { }; }); - it('should resolve artifact dependencies', () => { - const {config} = initializeConfig(configJson, {gatherMode: 'snapshot'}); + it('should resolve artifact dependencies', async () => { + const {config} = await initializeConfig(configJson, {gatherMode: 'snapshot'}); expect(config).toMatchObject({ artifacts: [ {id: 'Dependency', gatherer: {instance: dependencyGatherer}}, @@ -187,8 +186,8 @@ describe('Fraggle Rock Config', () => { }); }); - it('should resolve artifact dependencies in navigations', () => { - const {config} = initializeConfig(configJson, {gatherMode: 'snapshot'}); + it('should resolve artifact dependencies in navigations', async () => { + const {config} = await initializeConfig(configJson, {gatherMode: 'snapshot'}); expect(config).toMatchObject({ navigations: [ {artifacts: [{id: 'Dependency'}]}, @@ -209,49 +208,49 @@ describe('Fraggle Rock Config', () => { it('should throw when dependencies are out of order in artifacts', () => { if (!configJson.artifacts) throw new Error('Failed to run beforeEach'); configJson.artifacts = [configJson.artifacts[1], configJson.artifacts[0]]; - expect(() => initializeConfig(configJson, {gatherMode: 'snapshot'})) - .toThrow(/Failed to find dependency/); + expect(initializeConfig(configJson, {gatherMode: 'snapshot'})) + .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(configJson, {gatherMode: 'snapshot'})) - .toThrow(/Failed to find dependency/); + expect(initializeConfig(configJson, {gatherMode: 'snapshot'})) + .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(configJson, {gatherMode: 'snapshot'})) - .toThrow(/Failed to find dependency/); + expect(initializeConfig(configJson, {gatherMode: 'snapshot'})) + .rejects.toThrow(/Failed to find dependency/); }); it('should throw when timespan needs snapshot', () => { dependentGatherer.meta.supportedModes = ['timespan']; dependencyGatherer.meta.supportedModes = ['snapshot']; - expect(() => initializeConfig(configJson, {gatherMode: 'navigation'})) - .toThrow(/Dependency.*is invalid/); + expect(initializeConfig(configJson, {gatherMode: 'navigation'})) + .rejects.toThrow(/Dependency.*is invalid/); }); it('should throw when timespan needs navigation', () => { dependentGatherer.meta.supportedModes = ['timespan']; dependencyGatherer.meta.supportedModes = ['navigation']; - expect(() => initializeConfig(configJson, {gatherMode: 'navigation'})) - .toThrow(/Dependency.*is invalid/); + expect(initializeConfig(configJson, {gatherMode: 'navigation'})) + .rejects.toThrow(/Dependency.*is invalid/); }); }); describe('.resolveNavigationsToDefns', () => { - it('should resolve navigation definitions', () => { + it('should resolve navigation definitions', async () => { gatherMode = 'navigation'; const configJson = { artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}], navigations: [{id: 'default', artifacts: ['Accessibility']}], }; - const {config} = initializeConfig(configJson, {gatherMode}); + const {config} = await initializeConfig(configJson, {gatherMode}); expect(config).toMatchObject({ artifacts: [{id: 'Accessibility', gatherer: {path: 'accessibility'}}], @@ -266,7 +265,7 @@ describe('Fraggle Rock Config', () => { navigations: [{id: 'default', artifacts: ['Accessibility']}], }; - expect(() => initializeConfig(configJson, {gatherMode})).toThrow(/Cannot use navigations/); + expect(initializeConfig(configJson, {gatherMode})).rejects.toThrow(/Cannot use navigations/); }); it('should throw when navigations use unrecognized artifacts', () => { @@ -275,16 +274,16 @@ describe('Fraggle Rock Config', () => { navigations: [{id: 'default', artifacts: ['Accessibility']}], }; - expect(() => initializeConfig(configJson, {gatherMode})).toThrow(/Unrecognized artifact/); + expect(initializeConfig(configJson, {gatherMode})).rejects.toThrow(/Unrecognized artifact/); }); - it('should set default properties on navigations', () => { + it('should set default properties on navigations', async () => { gatherMode = 'navigation'; const configJson = { artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}], navigations: [{id: 'default', artifacts: ['Accessibility']}], }; - const {config} = initializeConfig(configJson, {gatherMode}); + const {config} = await initializeConfig(configJson, {gatherMode}); expect(config).toMatchObject({ navigations: [ @@ -301,7 +300,7 @@ describe('Fraggle Rock Config', () => { }); }); - it('should ensure minimum quiet thresholds when throttlingMethod is devtools', () => { + it('should ensure minimum quiet thresholds when throttlingMethod is devtools', async () => { gatherMode = 'navigation'; const configJson = { artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}], @@ -312,7 +311,7 @@ describe('Fraggle Rock Config', () => { ], }; - const {config} = initializeConfig(configJson, { + const {config} = await initializeConfig(configJson, { gatherMode, settingsOverrides: {throttlingMethod: 'devtools'}, }); @@ -380,8 +379,8 @@ describe('Fraggle Rock Config', () => { }; }); - it('should do nothing when not extending', () => { - const {config} = initializeConfig({ + it('should do nothing when not extending', async () => { + const {config} = await initializeConfig({ artifacts: [ {id: 'Accessibility', gatherer: 'accessibility'}, ], @@ -402,9 +401,9 @@ describe('Fraggle Rock Config', () => { }); }); - it('should extend the default config with filters', () => { + it('should extend the default config with filters', async () => { const gatherMode = 'navigation'; - const {config} = initializeConfig({ + const {config} = await initializeConfig({ extends: 'lighthouse:default', settings: {onlyCategories: ['accessibility']}, }, {gatherMode}); @@ -422,16 +421,16 @@ describe('Fraggle Rock Config', () => { expect(config.categories).not.toHaveProperty('performance'); }); - it('should merge in artifacts', () => { - const {config} = initializeConfig(extensionConfig, {gatherMode: 'navigation'}); + it('should merge in artifacts', async () => { + const {config} = await initializeConfig(extensionConfig, {gatherMode: 'navigation'}); if (!config.artifacts) throw new Error(`No artifacts created`); const hasExtraArtifact = config.artifacts.some(a => a.id === 'ExtraArtifact'); if (!hasExtraArtifact) expect(config.artifacts).toContain('ExtraArtifact'); }); - it('should merge in navigations', () => { - const {config} = initializeConfig(extensionConfig, {gatherMode: 'navigation'}); + it('should merge in navigations', async () => { + const {config} = await initializeConfig(extensionConfig, {gatherMode: 'navigation'}); if (!config.navigations) throw new Error(`No navigations created`); expect(config.navigations).toHaveLength(1); @@ -440,8 +439,8 @@ describe('Fraggle Rock Config', () => { if (!hasNavigation) expect(config.navigations[0].artifacts).toContain('ExtraArtifact'); }); - it('should merge in audits', () => { - const {config} = initializeConfig(extensionConfig, {gatherMode: 'navigation'}); + it('should merge in audits', async () => { + const {config} = await initializeConfig(extensionConfig, {gatherMode: 'navigation'}); if (!config.audits) throw new Error(`No audits created`); const hasExtraAudit = config.audits. @@ -449,8 +448,8 @@ describe('Fraggle Rock Config', () => { if (!hasExtraAudit) expect(config.audits).toContain('extra-audit'); }); - it('should merge in categories', () => { - const {config} = initializeConfig(extensionConfig, {gatherMode: 'navigation'}); + it('should merge in categories', async () => { + const {config} = await initializeConfig(extensionConfig, {gatherMode: 'navigation'}); if (!config.categories) throw new Error(`No categories created`); const hasCategory = config.categories.performance.auditRefs.some(a => a.id === 'extra-audit'); @@ -458,28 +457,28 @@ describe('Fraggle Rock Config', () => { }); }); - it('should validate the config with warnings', () => { + it('should validate the config with warnings', async () => { /** @type {LH.Config.Json} */ const extensionConfig = { extends: 'lighthouse:default', navigations: [{id: 'default', loadFailureMode: 'warn'}], }; - const {config, warnings} = initializeConfig(extensionConfig, {gatherMode: 'navigation'}); + const {config, warnings} = await initializeConfig(extensionConfig, {gatherMode: 'navigation'}); const navigations = config.navigations; if (!navigations) throw new Error(`Failed to initialize navigations`); expect(warnings).toHaveLength(1); expect(navigations[0].loadFailureMode).toEqual('fatal'); }); - it('should validate the config with fatal errors', () => { + it('should validate the config with fatal errors', async () => { /** @type {LH.Config.Json} */ const extensionConfig = { extends: 'lighthouse:default', artifacts: [{id: 'artifact', gatherer: {instance: new BaseGatherer()}}], }; - const invocation = () => initializeConfig(extensionConfig, {gatherMode: 'navigation'}); - expect(invocation).toThrow(/did not support any gather modes/); + expect(initializeConfig(extensionConfig, {gatherMode: 'navigation'})) + .rejects.toThrow(/did not support any gather modes/); }); }); diff --git a/lighthouse-core/test/fraggle-rock/config/filters-test.js b/lighthouse-core/test/fraggle-rock/config/filters-test.js index 786c6d5f9ce1..847bb57a3e7d 100644 --- a/lighthouse-core/test/fraggle-rock/config/filters-test.js +++ b/lighthouse-core/test/fraggle-rock/config/filters-test.js @@ -525,8 +525,8 @@ describe('Fraggle Rock Config Filtering', () => { }); }); - it('should preserve full-page-screenshot', () => { - config = initializeConfig(undefined, {gatherMode: 'navigation'}).config; + it('should preserve full-page-screenshot', async () => { + config = (await initializeConfig(undefined, {gatherMode: 'navigation'})).config; const filtered = filters.filterConfigByExplicitFilters(config, { onlyAudits: ['color-contrast'], diff --git a/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js b/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js index 7b50e51a379f..8206255cc029 100644 --- a/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js +++ b/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js @@ -30,20 +30,20 @@ describe('getBaseArtifacts', () => { }); it('should fetch benchmark index', async () => { - const {config} = initializeConfig(undefined, {gatherMode: 'navigation'}); + const {config} = await initializeConfig(undefined, {gatherMode: 'navigation'}); const artifacts = await getBaseArtifacts(config, driverMock.asDriver(), {gatherMode}); expect(artifacts.BenchmarkIndex).toEqual(500); }); it('should fetch host user agent', async () => { - const {config} = initializeConfig(undefined, {gatherMode: 'navigation'}); + const {config} = await initializeConfig(undefined, {gatherMode: 'navigation'}); const artifacts = await getBaseArtifacts(config, driverMock.asDriver(), {gatherMode}); expect(artifacts.HostUserAgent).toContain('Macintosh'); expect(artifacts.HostFormFactor).toEqual('desktop'); }); it('should return settings', async () => { - const {config} = initializeConfig(undefined, {gatherMode: 'navigation'}); + const {config} = await initializeConfig(undefined, {gatherMode: 'navigation'}); const artifacts = await getBaseArtifacts(config, driverMock.asDriver(), {gatherMode}); expect(artifacts.settings).toEqual(config.settings); }); @@ -56,7 +56,7 @@ describe('finalizeArtifacts', () => { let gathererArtifacts = {}; beforeEach(async () => { - const {config} = initializeConfig(undefined, {gatherMode}); + const {config} = await initializeConfig(undefined, {gatherMode}); const driver = getMockDriverForArtifacts().asDriver(); baseArtifacts = await getBaseArtifacts(config, driver, {gatherMode}); baseArtifacts.URL = {initialUrl: 'about:blank', finalUrl: 'https://example.com'}; diff --git a/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js b/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js index 2306a6302c6a..6e6190670de9 100644 --- a/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js +++ b/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js @@ -104,11 +104,11 @@ describe('NavigationRunner', () => { }; } - beforeEach(() => { + beforeEach(async () => { requestedUrl = 'http://example.com'; requestor = requestedUrl; mockRunner.reset(); - config = initializeConfig(undefined, {gatherMode: 'navigation'}).config; + config = (await initializeConfig(undefined, {gatherMode: 'navigation'})).config; navigation = createNavigation().navigation; computedCache = new Map(); baseArtifacts = createMockBaseArtifacts(); @@ -187,7 +187,7 @@ describe('NavigationRunner', () => { }); it('should navigate as many times as there are navigations', async () => { - config = initializeConfig( + config = (await initializeConfig( { ...config, navigations: [ @@ -198,7 +198,7 @@ describe('NavigationRunner', () => { ], }, {gatherMode: 'navigation'} - ).config; + )).config; await run(); const navigations = mocks.navigationMock.gotoURL.mock.calls; @@ -209,7 +209,7 @@ describe('NavigationRunner', () => { it('should backfill requested URL using a callback requestor', async () => { requestedUrl = 'https://backfill.example.com'; requestor = () => {}; - config = initializeConfig( + config = (await initializeConfig( { ...config, navigations: [ @@ -217,7 +217,7 @@ describe('NavigationRunner', () => { ], }, {gatherMode: 'navigation'} - ).config; + )).config; mocks.navigationMock.gotoURL.mockReturnValue({ requestedUrl, mainDocumentUrl: requestedUrl, @@ -235,7 +235,7 @@ describe('NavigationRunner', () => { }); it('should merge artifacts between navigations', async () => { - config = initializeConfig( + config = (await initializeConfig( { ...config, navigations: [ @@ -244,7 +244,7 @@ describe('NavigationRunner', () => { ], }, {gatherMode: 'navigation'} - ).config; + )).config; // Both gatherers will error in these test conditions, but artifact errors // will be merged into single `artifacts` object. @@ -255,7 +255,7 @@ describe('NavigationRunner', () => { }); it('should retain PageLoadError and associated warnings', async () => { - config = initializeConfig( + config = (await initializeConfig( { ...config, navigations: [ @@ -264,7 +264,7 @@ describe('NavigationRunner', () => { ], }, {gatherMode: 'navigation'} - ).config; + )).config; // Ensure the first real page load fails. mocks.navigationMock.gotoURL.mockImplementation((driver, url) => { diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 4212e1231be9..145773c8415b 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -83,8 +83,8 @@ beforeAll(async () => { /** * @param {LH.Config.Json} json */ -function makeConfig(json) { - const config = new Config(json); +async function makeConfig(json) { + const config = await Config.fromJson(json); // Since the config is for `gather-runner`, ensure it has `passes`. if (!config.passes) { @@ -235,7 +235,7 @@ describe('GatherRunner', function() { it('collects benchmark as an artifact', async () => { const requestedUrl = 'https://example.com'; const driver = fakeDriver; - const config = makeConfig({passes: [{passName: 'defaultPass'}]}); + const config = await makeConfig({passes: [{passName: 'defaultPass'}]}); const options = {requestedUrl, driver, settings: config.settings, computedCache: new Map()}; const results = await GatherRunner.run(config.passes, options); @@ -245,7 +245,7 @@ describe('GatherRunner', function() { it('collects host user agent as an artifact', async () => { const requestedUrl = 'https://example.com'; const driver = fakeDriver; - const config = makeConfig({passes: [{passName: 'defaultPass'}]}); + const config = await makeConfig({passes: [{passName: 'defaultPass'}]}); const options = {requestedUrl, driver, settings: config.settings, computedCache: new Map()}; const results = await GatherRunner.run(config.passes, options); @@ -256,19 +256,19 @@ describe('GatherRunner', function() { it('collects network user agent as an artifact', async () => { const requestedUrl = 'https://example.com'; const driver = fakeDriver; - const config = makeConfig({passes: [{passName: 'defaultPass'}]}); + const config = await makeConfig({passes: [{passName: 'defaultPass'}]}); const options = {requestedUrl, driver, settings: config.settings, computedCache: new Map()}; const results = await GatherRunner.run(config.passes, options); expect(results.NetworkUserAgent).toContain('Mozilla'); }); - it('collects requested and final URLs as an artifact', () => { + it('collects requested and final URLs as an artifact', async () => { const requestedUrl = 'https://example.com'; const mainDocumentUrl = 'https://example.com/interstitial'; const gotoURL = requireMockAny('../../gather/driver/navigation.js').gotoURL; gotoURL.mockResolvedValue({mainDocumentUrl, timedOut: false, warnings: []}); - const config = makeConfig({passes: [{passName: 'defaultPass'}]}); + const config = await makeConfig({passes: [{passName: 'defaultPass'}]}); const options = { requestedUrl, driver: fakeDriver, @@ -299,7 +299,7 @@ describe('GatherRunner', function() { return Promise.resolve({userAgent: userAgent}); }, }); - const config = makeConfig({ + const config = await makeConfig({ passes: [{passName: 'defaultPass'}], settings: {}, }); @@ -499,7 +499,7 @@ describe('GatherRunner', function() { }, }); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -538,7 +538,7 @@ describe('GatherRunner', function() { (_, url) => url.includes('blank') ? null : Promise.reject(navigationError) ); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -582,7 +582,7 @@ describe('GatherRunner', function() { (_, url) => url.includes('blank') ? gotoUrlForAboutBlank() : gotoUrlForRealUrl() ); - const config = makeConfig({ + const config = await makeConfig({ passes: [{passName: 'defaultPass', recordTrace: true}, { loadFailureMode: 'warn', recordTrace: true, @@ -739,11 +739,11 @@ describe('GatherRunner', function() { ]); }); - it('does as many passes as are required', () => { + it('does as many passes as are required', async () => { const t1 = new TestGatherer(); const t2 = new TestGatherer(); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -769,8 +769,8 @@ describe('GatherRunner', function() { }); }); - it('respects trace names', () => { - const config = makeConfig({ + it('respects trace names', async () => { + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -806,7 +806,7 @@ describe('GatherRunner', function() { endDevtoolsLog: () => [], }); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ passName: 'firstPass', recordTrace: true, @@ -829,7 +829,7 @@ describe('GatherRunner', function() { const t1 = new (class Test1 extends TestGatherer {})(); const t2 = new (class Test2 extends TestGatherer {})(); const t3 = new (class Test3 extends TestGatherer {})(); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ passName: 'firstPass', recordTrace: true, @@ -969,7 +969,7 @@ describe('GatherRunner', function() { } }, ]; - const config = makeConfig({ + const config = await makeConfig({ passes: [{ passName: 'defaultPass', gatherers: gatherers.map(G => ({instance: new G()})), @@ -990,7 +990,7 @@ describe('GatherRunner', function() { }); }); - it('supports sync and async return of artifacts from gatherers', () => { + it('supports sync and async return of artifacts from gatherers', async () => { const gatherers = [ // sync new class BeforeSync extends Gatherer { @@ -1027,7 +1027,7 @@ describe('GatherRunner', function() { })(), ].map(instance => ({instance})); const gathererNames = gatherers.map(gatherer => gatherer.instance.name); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ passName: 'defaultPass', gatherers, @@ -1120,7 +1120,7 @@ describe('GatherRunner', function() { } } - const config = makeConfig({ + const config = await makeConfig({ passes: [{ passName: 'defaultPass', gatherers: [{instance: new WarningGatherer()}], @@ -1135,7 +1135,7 @@ describe('GatherRunner', function() { assert.deepStrictEqual(artifacts.LighthouseRunWarnings, runWarnings); }); - it('supports sync and async throwing of errors from gatherers', () => { + it('supports sync and async throwing of errors from gatherers', async () => { const gatherers = [ // sync new class BeforeSync extends Gatherer { @@ -1175,7 +1175,7 @@ describe('GatherRunner', function() { })(), ].map(instance => ({instance})); const gathererNames = gatherers.map(gatherer => gatherer.instance.name); - const config = makeConfig({ + const config = await makeConfig({ passes: [{ passName: 'defaultPass', gatherers, @@ -1196,8 +1196,8 @@ describe('GatherRunner', function() { }); }); - it('rejects if a gatherer does not provide an artifact', () => { - const config = makeConfig({ + it('rejects if a gatherer does not provide an artifact', async () => { + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -1215,8 +1215,8 @@ describe('GatherRunner', function() { }).then(_ => assert.ok(false), _ => assert.ok(true)); }); - it('rejects when domain name can\'t be resolved', () => { - const config = makeConfig({ + it('rejects when domain name can\'t be resolved', async () => { + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -1248,8 +1248,8 @@ describe('GatherRunner', function() { }); }); - it('resolves but warns when page times out', () => { - const config = makeConfig({ + it('resolves but warns when page times out', async () => { + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -1275,8 +1275,8 @@ describe('GatherRunner', function() { }); }); - it('resolves and does not warn when page times out on non-fatal pass', () => { - const config = makeConfig({ + it('resolves and does not warn when page times out on non-fatal pass', async () => { + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', @@ -1309,8 +1309,8 @@ describe('GatherRunner', function() { }); }); - it('resolves when domain name can\'t be resolved but is offline', () => { - const config = makeConfig({ + it('resolves when domain name can\'t be resolved but is offline', async () => { + const config = await makeConfig({ passes: [{ recordTrace: true, passName: 'firstPass', diff --git a/lighthouse-core/test/lib/stack-packs-test.js b/lighthouse-core/test/lib/stack-packs-test.js index cc9886d98aaf..e39c26f11e89 100644 --- a/lighthouse-core/test/lib/stack-packs-test.js +++ b/lighthouse-core/test/lib/stack-packs-test.js @@ -7,11 +7,10 @@ import lighthouseStackPacksDep from 'lighthouse-stack-packs'; import stackPacksLib from '../../lib/stack-packs.js'; -import defaultConfig from '../../config/default-config.js'; import Config from '../../config/config.js'; -function getAuditIds() { - const config = new Config(defaultConfig); +async function getAuditIds() { + const config = await Config.fromJson(); return config.audits.map(a => a.implementation.meta.id); } @@ -231,8 +230,8 @@ Array [ // Keys for plugin audits are allowed in this package. // Make sure none are typos of core audits. - it('snapshot unrecognized keys', () => { - const auditIds = getAuditIds(); + it('snapshot unrecognized keys', async () => { + const auditIds = await getAuditIds(); const unrecognizedKeys = new Set(); for (const pack of lighthouseStackPacksDep) { diff --git a/lighthouse-core/test/runner-test.js b/lighthouse-core/test/runner-test.js index 31f107fbf068..142fc22487ff 100644 --- a/lighthouse-core/test/runner-test.js +++ b/lighthouse-core/test/runner-test.js @@ -103,7 +103,7 @@ describe('Runner', () => { describe('Gather Mode & Audit Mode', () => { const url = 'https://example.com'; - const generateConfig = settings => new Config({ + const generateConfig = settings => Config.fromJson({ passes: [{ gatherers: ['viewport-dimensions'], }], @@ -117,8 +117,8 @@ describe('Runner', () => { fs.rmSync(resolvedPath, {recursive: true, force: true}); }); - it('-G gathers, quits, and doesn\'t run audits', () => { - const opts = {config: generateConfig({gatherMode: artifactsPath}), driverMock}; + it('-G gathers, quits, and doesn\'t run audits', async () => { + const opts = {config: await generateConfig({gatherMode: artifactsPath}), driverMock}; return runGatherAndAudit(createGatherFn(url), opts).then(_ => { expect(loadArtifactsSpy).not.toHaveBeenCalled(); expect(saveArtifactsSpy).toHaveBeenCalled(); @@ -137,8 +137,8 @@ describe('Runner', () => { }); // uses the files on disk from the -G test. ;) - it('-A audits from saved artifacts and doesn\'t gather', () => { - const opts = {config: generateConfig({auditMode: artifactsPath}), driverMock}; + it('-A audits from saved artifacts and doesn\'t gather', async () => { + const opts = {config: await generateConfig({auditMode: artifactsPath}), driverMock}; return runGatherAndAudit(createGatherFn(), opts).then(_ => { expect(loadArtifactsSpy).toHaveBeenCalled(); expect(gatherRunnerRunSpy).not.toHaveBeenCalled(); @@ -151,7 +151,7 @@ describe('Runner', () => { it('-A throws if the settings change', async () => { // Change throttlingMethod from its default of 'simulate' const settings = {auditMode: artifactsPath, throttlingMethod: 'provided'}; - const opts = {config: generateConfig(settings), driverMock}; + const opts = {config: await generateConfig(settings), driverMock}; try { await runGatherAndAudit(createGatherFn(), opts); assert.fail('should have thrown'); @@ -161,10 +161,9 @@ describe('Runner', () => { }); it('does not include a top-level runtimeError when gatherers were successful', async () => { - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/perflog/', - }, audits: [ 'content-width', @@ -175,9 +174,9 @@ describe('Runner', () => { assert.strictEqual(lhr.runtimeError, undefined); }); - it('-GA is a normal run but it saves artifacts and LHR to disk', () => { + it('-GA is a normal run but it saves artifacts and LHR to disk', async () => { const settings = {auditMode: artifactsPath, gatherMode: artifactsPath}; - const opts = {config: generateConfig(settings), driverMock}; + const opts = {config: await generateConfig(settings), driverMock}; return runGatherAndAudit(createGatherFn(url), opts).then(_ => { expect(loadArtifactsSpy).not.toHaveBeenCalled(); expect(gatherRunnerRunSpy).toHaveBeenCalled(); @@ -187,8 +186,8 @@ describe('Runner', () => { }); }); - it('non -G/-A run doesn\'t save artifacts to disk', () => { - const opts = {config: generateConfig(), driverMock}; + it('non -G/-A run doesn\'t save artifacts to disk', async () => { + const opts = {config: await generateConfig(), driverMock}; return runGatherAndAudit(createGatherFn(url), opts).then(_ => { expect(loadArtifactsSpy).not.toHaveBeenCalled(); expect(gatherRunnerRunSpy).toHaveBeenCalled(); @@ -211,7 +210,7 @@ describe('Runner', () => { throw new LHError(LHError.errors.UNSUPPORTED_OLD_CHROME, {featureName: 'VRML'}); } } - const gatherConfig = new Config({ + const gatherConfig = await Config.fromJson({ settings: {gatherMode: artifactsPath}, passes: [{gatherers: [WarningAndErrorGatherer]}], }); @@ -241,7 +240,7 @@ describe('Runner', () => { } static audit() {} } - const auditConfig = new Config({ + const auditConfig = await Config.fromJson({ settings: {auditMode: artifactsPath}, audits: [{implementation: DummyAudit}], }); @@ -257,9 +256,9 @@ describe('Runner', () => { }); }); - it('expands gatherers', () => { + it('expands gatherers', async () => { const url = 'https://example.com'; - const config = new Config({ + const config = await Config.fromJson({ passes: [{ gatherers: ['viewport-dimensions'], }], @@ -274,10 +273,9 @@ describe('Runner', () => { }); }); - - it('rejects when given neither passes nor artifacts', () => { + it('rejects when given neither passes nor artifacts', async () => { const url = 'https://example.com'; - const config = new Config({ + const config = await Config.fromJson({ audits: [ 'content-width', ], @@ -291,7 +289,7 @@ describe('Runner', () => { }); }); - it('accepts audit options', () => { + it('accepts audit options', async () => { const url = 'https://example.com/'; const calls = []; @@ -311,7 +309,7 @@ describe('Runner', () => { } } - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/empty-artifacts/', }, @@ -329,8 +327,8 @@ describe('Runner', () => { }); }); - it('accepts trace artifacts as paths and outputs appropriate data', () => { - const config = new Config({ + it('accepts trace artifacts as paths and outputs appropriate data', async () => { + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/perflog/', }, @@ -347,9 +345,9 @@ describe('Runner', () => { }); }); - it('rejects when given an invalid trace artifact', () => { + it('rejects when given an invalid trace artifact', async () => { const url = 'https://example.com'; - const config = new Config({ + const config = await Config.fromJson({ passes: [{ recordTrace: true, gatherers: [], @@ -374,7 +372,7 @@ describe('Runner', () => { }); it('finds correct timings for multiple gather/audit pairs run separately', async () => { - const config = new Config({ + const config = await Config.fromJson({ passes: [{ gatherers: ['viewport-dimensions'], }], @@ -402,8 +400,8 @@ describe('Runner', () => { }); describe('Bad required artifact handling', () => { - it('outputs an error audit result when trace required but not provided', () => { - const config = new Config({ + it('outputs an error audit result when trace required but not provided', async () => { + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/empty-artifacts/', }, @@ -413,16 +411,15 @@ describe('Runner', () => { ], }); - return runGatherAndAudit({}, {config}).then(results => { - const auditResult = results.lhr.audits['user-timings']; - assert.strictEqual(auditResult.score, null); - assert.strictEqual(auditResult.scoreDisplayMode, 'error'); - assert.ok(auditResult.errorMessage.includes('traces')); - }); + const results = await runGatherAndAudit({}, {config}); + const auditResult = results.lhr.audits['user-timings']; + assert.strictEqual(auditResult.score, null); + assert.strictEqual(auditResult.scoreDisplayMode, 'error'); + assert.ok(auditResult.errorMessage.includes('traces')); }); it('outputs an error audit result when devtoolsLog required but not provided', async () => { - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/empty-artifacts/', }, @@ -439,8 +436,8 @@ describe('Runner', () => { assert.strictEqual(auditResult.errorMessage, 'Required devtoolsLogs gatherer did not run.'); }); - it('outputs an error audit result when missing a required artifact', () => { - const config = new Config({ + it('outputs an error audit result when missing a required artifact', async () => { + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/empty-artifacts/', }, @@ -474,7 +471,7 @@ describe('Runner', () => { await assetSaver.saveArtifacts(artifacts, resolvedPath); // Load artifacts via auditMode. - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: resolvedPath, }, @@ -507,7 +504,7 @@ describe('Runner', () => { } const auditMockFn = SimpleAudit.audit = jest.fn().mockReturnValue({score: 1}); - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/alphabet-artifacts/', }, @@ -540,7 +537,7 @@ describe('Runner', () => { } const auditMockFn = SimpleAudit.audit = jest.fn().mockReturnValue({score: 1}); - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/alphabet-artifacts/', }, @@ -569,9 +566,9 @@ describe('Runner', () => { requiredArtifacts: [], }; - it('produces an error audit result when an audit throws an Error', () => { + it('produces an error audit result when an audit throws an Error', async () => { const errorMessage = 'Audit yourself'; - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/empty-artifacts/', }, @@ -596,8 +593,8 @@ describe('Runner', () => { }); }); - it('accepts devtoolsLog in artifacts', () => { - const config = new Config({ + it('accepts devtoolsLog in artifacts', async () => { + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/perflog/', }, @@ -613,9 +610,9 @@ describe('Runner', () => { }); }); - it('rejects when not given audits to run (and not -G)', () => { + it('rejects when not given audits to run (and not -G)', async () => { const url = 'https://example.com'; - const config = new Config({ + const config = await Config.fromJson({ passes: [{ gatherers: ['viewport-dimensions'], }], @@ -629,9 +626,9 @@ describe('Runner', () => { }); }); - it('returns data even if no config categories are provided', () => { + it('returns data even if no config categories are provided', async () => { const url = 'https://example.com/'; - const config = new Config({ + const config = await Config.fromJson({ passes: [{ gatherers: ['viewport-dimensions'], }], @@ -649,10 +646,9 @@ describe('Runner', () => { }); }); - - it('returns categories', () => { + it('returns categories', async () => { const url = 'https://example.com/'; - const config = new Config({ + const config = await Config.fromJson({ passes: [{ gatherers: ['viewport-dimensions'], }], @@ -692,8 +688,8 @@ describe('Runner', () => { }); }); - it('results include artifacts when given artifacts and audits', () => { - const config = new Config({ + it('results include artifacts when given artifacts and audits', async () => { + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/perflog/', }, @@ -708,9 +704,9 @@ describe('Runner', () => { }); }); - it('results include artifacts when given passes and audits', () => { + it('results include artifacts when given passes and audits', async () => { const url = 'https://example.com'; - const config = new Config({ + const config = await Config.fromJson({ passes: [{ passName: 'firstPass', gatherers: ['meta-elements', 'viewport-dimensions'], @@ -732,8 +728,8 @@ describe('Runner', () => { }); }); - it('includes any LighthouseRunWarnings from artifacts in output', () => { - const config = new Config({ + it('includes any LighthouseRunWarnings from artifacts in output', async () => { + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/perflog/', }, @@ -748,10 +744,10 @@ describe('Runner', () => { }); }); - it('includes any LighthouseRunWarnings from audits in LHR', () => { + it('includes any LighthouseRunWarnings from audits in LHR', async () => { const warningString = 'Really important audit warning!'; - const config = new Config({ + const config = await Config.fromJson({ settings: { auditMode: moduleDir + '/fixtures/artifacts/empty-artifacts/', }, @@ -812,7 +808,7 @@ describe('Runner', () => { }; it('includes a top-level runtimeError when a gatherer throws one', async () => { - const config = new Config(configJson); + const config = await Config.fromJson(configJson); const {lhr} = await runGatherAndAudit(createGatherFn('https://example.com/'), {config, driverMock}); // Audit error included the runtimeError @@ -843,7 +839,7 @@ describe('Runner', () => { } }); - const config = new Config(configJson); + const config = await Config.fromJson(configJson); const {lhr} = await runGatherAndAudit( createGatherFn(url), {config, driverMock: errorDriverMock} @@ -871,7 +867,7 @@ describe('Runner', () => { }; try { - await runGatherAndAudit(createGatherFn('https://example.com/'), {driverMock: erroringDriver, config: new Config()}); + await runGatherAndAudit(createGatherFn('https://example.com/'), {driverMock: erroringDriver, config: await Config.fromJson()}); assert.fail('should have thrown'); } catch (err) { assert.equal(err.code, LHError.errors.PROTOCOL_TIMEOUT.code); @@ -882,7 +878,7 @@ describe('Runner', () => { it('can handle array of outputs', async () => { const url = 'https://example.com'; - const config = new Config({ + const config = await Config.fromJson({ extends: 'lighthouse:default', settings: { onlyCategories: ['performance'], diff --git a/tsconfig-base.json b/tsconfig-base.json index 844b909432bf..e61e358647a3 100644 --- a/tsconfig-base.json +++ b/tsconfig-base.json @@ -12,7 +12,7 @@ "rootDir": ".", "target": "es2020", - "module": "es2020", + "module": "es2022", "moduleResolution": "node", "esModuleInterop": true,