Skip to content

Commit

Permalink
core(fr): add navigations to config (#11957)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce authored Jan 19, 2021
1 parent 1a7b852 commit ef387d8
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 13 deletions.
16 changes: 16 additions & 0 deletions lighthouse-core/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ const defaultPassConfig = {
gatherers: [],
};

/** @type {Required<LH.Config.NavigationJson>} */
const defaultNavigationConfig = {
id: 'default',
loadFailureMode: 'fatal',
disableThrottling: false,
disableStorageReset: false,
pauseAfterFcpMs: 0,
pauseAfterLoadMs: 0,
networkQuietThresholdMs: 0,
cpuQuietThresholdMs: 0,
blockedUrlPatterns: [],
blankPage: 'about:blank',
artifacts: [],
};

const nonSimulatedPassConfigOverrides = {
pauseAfterFcpMs: 5250,
pauseAfterLoadMs: 5250,
Expand All @@ -149,5 +164,6 @@ module.exports = {
userAgents,
defaultSettings,
defaultPassConfig,
defaultNavigationConfig,
nonSimulatedPassConfigOverrides,
};
37 changes: 36 additions & 1 deletion lighthouse-core/fraggle-rock/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const path = require('path');
const log = require('lighthouse-logger');
const Runner = require('../../runner.js');
const defaultConfig = require('./default-config.js');
const {defaultNavigationConfig} = require('../../config/constants.js');
const {isFRGathererDefn} = require('./validation.js');
const {filterConfigByGatherMode} = require('./filters.js');
const {
Expand Down Expand Up @@ -75,6 +76,39 @@ function resolveArtifactsToDefns(artifacts, configDir) {
return artifactDefns;
}

/**
*
* @param {LH.Config.NavigationJson[]|null|undefined} navigations
* @param {LH.Config.ArtifactDefn[]|null|undefined} artifactDefns
* @return {LH.Config.NavigationDefn[] | null}
*/
function resolveNavigationsToDefns(navigations, artifactDefns) {
if (!navigations) return null;
if (!artifactDefns) throw new Error('Cannot use navigations without defining artifacts');

const status = {msg: 'Resolve navigation definitions', id: 'lh:config:resolveNavigationsToDefns'};
log.time(status, 'verbose');

const artifactsById = new Map(artifactDefns.map(defn => [defn.id, defn]));

const navigationDefns = navigations.map(navigation => {
const navigationWithDefaults = {...defaultNavigationConfig, ...navigation};
const navId = navigationWithDefaults.id;
const artifacts = navigationWithDefaults.artifacts.map(id => {
const artifact = artifactsById.get(id);
if (!artifact) throw new Error(`Unrecognized artifact "${id}" in navigation "${navId}"`);
return artifact;
});

// TODO(FR-COMPAT): enforce navigation throttling invariants

return {...navigationWithDefaults, artifacts};
});

log.timeEnd(status);
return navigationDefns;
}

/**
* @param {LH.Config.Json|undefined} configJSON
* @param {{gatherMode: LH.Gatherer.GatherMode, configPath?: string, settingsOverrides?: LH.SharedFlagsSettings}} context
Expand All @@ -88,14 +122,15 @@ function initializeConfig(configJSON, context) {

// TODO(FR-COMPAT): handle config extension
// TODO(FR-COMPAT): handle config plugins
// TODO(FR-COMPAT): enforce navigation invariants

const settings = resolveSettings(configWorkingCopy.settings || {}, context.settingsOverrides);
const artifacts = resolveArtifactsToDefns(configWorkingCopy.artifacts, configDir);
const navigations = resolveNavigationsToDefns(configWorkingCopy.navigations, artifacts);

/** @type {LH.Config.FRConfig} */
let config = {
artifacts,
navigations,
audits: resolveAuditsToDefns(configWorkingCopy.audits, configDir),
categories: configWorkingCopy.categories || null,
groups: configWorkingCopy.groups || null,
Expand Down
6 changes: 6 additions & 0 deletions lighthouse-core/fraggle-rock/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const defaultConfig = {
{id: 'Accessibility', gatherer: 'accessibility'},
{id: 'ConsoleMessages', gatherer: 'console-messages'},
],
navigations: [
{
id: 'default',
artifacts: ['Accessibility', 'ConsoleMessages'],
},
],
settings: legacyDefaultConfig.settings,
audits: legacyDefaultConfig.audits,
categories: legacyDefaultConfig.categories,
Expand Down
65 changes: 61 additions & 4 deletions lighthouse-core/test/fraggle-rock/config/config-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ const {initializeConfig} = require('../../../fraggle-rock/config/config.js');
/* eslint-env jest */

describe('Fraggle Rock Config', () => {
const gatherMode = 'snapshot';
/** @type {LH.Gatherer.GatherMode} */
let gatherMode = 'snapshot';

beforeEach(() => {
gatherMode = 'snapshot';
});

it('should throw if the config path is not absolute', () => {
const configFn = () =>
Expand Down Expand Up @@ -62,6 +67,61 @@ describe('Fraggle Rock Config', () => {
expect(() => initializeConfig(configJson, {gatherMode})).toThrow(/ImageElements gatherer/);
});

it('should resolve navigation definitions', () => {
gatherMode = 'navigation';
const configJson = {
artifacts: [{id: 'Accessibility', gatherer: 'accessibility'}],
navigations: [{id: 'default', artifacts: ['Accessibility']}],
};
const {config} = initializeConfig(configJson, {gatherMode});

expect(config).toMatchObject({
artifacts: [{id: 'Accessibility', gatherer: {path: 'accessibility'}}],
navigations: [
{id: 'default', artifacts: [{id: 'Accessibility', gatherer: {path: 'accessibility'}}]},
],
});
});

it('should throw when navigations are defined without artifacts', () => {
const configJson = {
navigations: [{id: 'default', artifacts: ['Accessibility']}],
};

expect(() => initializeConfig(configJson, {gatherMode})).toThrow(/Cannot use navigations/);
});

it('should throw when navigations use unrecognized artifacts', () => {
const configJson = {
artifacts: [],
navigations: [{id: 'default', artifacts: ['Accessibility']}],
};

expect(() => initializeConfig(configJson, {gatherMode})).toThrow(/Unrecognized artifact/);
});

it('should set default properties on navigations', () => {
gatherMode = 'navigation';
const configJson = {
artifacts: [],
navigations: [{id: 'default'}],
};
const {config} = initializeConfig(configJson, {gatherMode});

expect(config).toMatchObject({
navigations: [
{
id: 'default',
blankPage: 'about:blank',
artifacts: [],
disableThrottling: false,
networkQuietThresholdMs: 0,
cpuQuietThresholdMs: 0,
},
],
});
});

it('should filter configuration by gatherMode', () => {
const timespanGatherer = new BaseGatherer();
timespanGatherer.meta = {supportedModes: ['timespan']};
Expand All @@ -83,10 +143,7 @@ describe('Fraggle Rock Config', () => {

it.todo('should support extension');
it.todo('should support plugins');
it.todo('should set default properties on navigations');
it.todo('should adjust default pass options for throttling method');
it.todo('should normalize gatherer inputs');
it.todo('should require gatherers from their paths');
it.todo('should filter configuration by inclusive settings');
it.todo('should filter configuration by exclusive settings');
it.todo('should validate audit/gatherer interdependencies');
Expand Down
9 changes: 8 additions & 1 deletion lighthouse-core/test/fraggle-rock/config/filters-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,14 @@ describe('Fraggle Rock Config Filtering', () => {

describe('filterConfigByGatherMode', () => {
it('should filter the entire config', () => {
const config = {artifacts, audits, categories, groups: null, settings: defaultSettings};
const config = {
artifacts,
navigations: null,
audits,
categories,
groups: null,
settings: defaultSettings,
};
expect(filters.filterConfigByGatherMode(config, 'snapshot')).toMatchObject({
artifacts: [{id: 'Snapshot'}],
audits: [{implementation: SnapshotAudit}],
Expand Down
54 changes: 47 additions & 7 deletions types/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ declare global {
export interface Json {
extends?: 'lighthouse:default' | string;
settings?: SharedFlagsSettings;
artifacts?: ArtifactJson[] | null;
passes?: PassJson[] | null;
audits?: Config.AuditJson[] | null;
categories?: Record<string, CategoryJson> | null;
groups?: Record<string, Config.GroupJson> | null;
plugins?: Array<string>,
plugins?: Array<string>;

// Fraggle Rock Only
artifacts?: ArtifactJson[] | null;
navigations?: NavigationJson[] | null;

// Legacy Only
passes?: PassJson[] | null;
}

/**
Expand All @@ -45,25 +50,56 @@ declare global {
export interface FRConfig {
settings: Settings;
artifacts: ArtifactDefn[] | null;
navigations: NavigationDefn[] | null;
audits: AuditDefn[] | null;
categories: Record<string, Category> | null;
groups: Record<string, Group> | null;
}

export interface PassJson {
passName: string;
interface SharedPassNavigationJson {
/**
* Controls the behavior when the navigation fails to complete (due to server error, no FCP, etc).
* Fatal means Lighthouse will exit immediately and return a runtimeError / non-zero exit code.
* Warn means a toplevel warning will appear in the report, but the run will complete with success.
* Ignore means a failure is expected and no action should be taken.
*/
loadFailureMode?: 'fatal'|'warn'|'ignore';
recordTrace?: boolean;
useThrottling?: boolean;
/** The number of milliseconds to wait after FCP until the page should be considered loaded. */
pauseAfterFcpMs?: number;
/** The number of milliseconds to wait after the load event until the page should be considered loaded. */
pauseAfterLoadMs?: number;
/** The number of milliseconds to wait between high priority network requests or 3 simulataneous requests before the page should be considered loaded. */
networkQuietThresholdMs?: number;
/** The number of milliseconds to wait between long tasks until the page should be considered loaded. */
cpuQuietThresholdMs?: number;
/** Substring patterns of network resources to block during this navigation, '*' wildcards supported though unnecessary as prefix or suffix (due to substring matching). */
blockedUrlPatterns?: string[];
/** The URL to use for the "blank" neutral page in between navigations. Defaults to `about:blank`. */
blankPage?: string;
}

export interface PassJson extends SharedPassNavigationJson {
/** The identifier for the pass. Config extension will deduplicate passes with the same passName. */
passName: string;
/** Whether a trace and devtoolsLog should be recorded for the pass. */
recordTrace?: boolean;
/** Whether throttling settings should be used for the pass. */
useThrottling?: boolean;
/** The array of gatherers to run during the pass. */
gatherers?: GathererJson[];
}

export interface NavigationJson extends SharedPassNavigationJson {
/** The identifier for the navigation. Config extension will deduplicate navigations with the same id. */
id: string;
/** Whether throttling settings should be skipped for the pass. */
disableThrottling?: boolean;
/** Whether storage clearing (service workers, cache storage) should be skipped for the pass. A run-wide setting of `true` takes precedence over this value. */
disableStorageReset?: boolean;
/** The array of artifacts to collect during the navigation. */
artifacts?: Array<string>;
}

export interface ArtifactJson {
id: string;
gatherer: GathererJson;
Expand Down Expand Up @@ -120,6 +156,10 @@ declare global {
gatherers: GathererDefn[];
}

export interface NavigationDefn extends Omit<Required<NavigationJson>, 'artifacts'> {
artifacts: ArtifactDefn[];
}

export interface ArtifactDefn {
id: string;
gatherer: FRGathererDefn;
Expand Down

0 comments on commit ef387d8

Please sign in to comment.