Skip to content

Commit

Permalink
feat: add config path to collect (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
BioPhoton authored Dec 17, 2022
1 parent e9625b3 commit a7e9ea2
Show file tree
Hide file tree
Showing 17 changed files with 221 additions and 22 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ This command executes a set of user-flow definitions against the target URL and
| **`-a`**, **`--awaitServeStdout`** | `string` | `.user-flowrc` setting | Waits for stdout from the serve command to start collecting user-flows |
| **`-f`**, **`--format`** | `string` | `html`, `json` setting | Format of the creates reports |
| **`-e`**, **`--openReport`** | `boolean` | `true` | Opens browser automatically after the user-flow is captured |
| **`-b`**, **`--budget-path`** | `string` | `./budget.json` | Path to the lighthouse `budget.json` file |
| **`-b`**, **`--budget-path`** | `string` | n/a | Path to the lighthouse `budget.json` file |
| **`-b`**, **`--config-path`** | `string` | n/a | Path to the lighthouse `config.json` file |
| **`-d`**, **`--dryRun`** | `boolean` | `false` | When true the user-flow test will get executed without measures (for fast development) |

> **💡 Pro Tip:**
Expand All @@ -224,6 +225,21 @@ This command executes a set of user-flow definitions against the target URL and

The CLI supports the official [user-flow/lighthouse configuration](https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md).

This configuration options can be used globally with the `configPath` option of the [`collect` command](#collect-command),
or per user flow in the [`UserFlowProvider#UserFlowOptions#config`](https://github.com/push-based/user-flow/blob/main/packages/cli/src/lib/commands/collect/utils/user-flow/types.ts#L39) option.

### Overwrites

As configuration can be placed on multiple levels at the same time we the overwrite order is defined as follows:

There are 2 levels:
- **global** - can be configured in the `.user-flowrc.json` or as CLI parameter
- **local** - per file configuration located in `<user-flow-name>.uf.ts`

- **local** `configPath` settings overwrite **global** settings
- **local** `budgets` or `budgetsPath` overwrite **global** settings form `configPath`


### Executing user-flows (`ufPath`)

To execute a single user-flow pass the user set the ufPath to user-flow file. You and set this ether in the config json file:
Expand Down
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "push-based-user-flow-workspace",
"version": "0.0.0",
"engines": {
"node": "16.14.3"
},
"license": "MIT",
"scripts": {
"nx": "nx",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/lib/commands/collect/options/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type CollectRcOptions = {
// @TODO get better typing for if serveCommand is given await is required
serveCommand?: string,
awaitServeStdout?: string;
configPath?: string;
}
export type CollectCliOnlyOptions = {
dryRun?: boolean;
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/src/lib/commands/collect/processes/collect-reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { get as dryRun } from '../../../commands/collect/options/dryRun';
import { collectFlow, loadFlow, openFlowReport, persistFlow } from '../utils/user-flow';
import { AssertRcOptions } from '../../assert/options/types';
import { RcJson } from '../../../types';
import { CollectArgvOptions } from '../options/types';
import { readFile } from '../../../core/file';
import { readConfig } from '../utils/config';

export async function collectReports(cfg: RcJson): Promise<RcJson> {

Expand All @@ -18,6 +21,7 @@ export async function collectReports(cfg: RcJson): Promise<RcJson> {
(_: any) => {

provider = normalizeProviderObject(provider);
provider = addConfigIfGiven(provider, collect);
provider = addBudgetsIfGiven(provider, assert);

return collectFlow({ ...collect, dryRun: dryRun() }, { ...provider, path })
Expand All @@ -41,6 +45,19 @@ function normalizeProviderObject(provider: UserFlowProvider): UserFlowProvider {
return provider;
}

function addConfigIfGiven(provider: UserFlowProvider, collectOptions: CollectArgvOptions = {} as CollectArgvOptions): UserFlowProvider {
const { configPath } = collectOptions;

if (configPath) {
logVerbose(`Configuration ${configPath} is used instead of a potential configuration in the user-flow.uf.ts`);

// @ts-ignore
provider.flowOptions.config = readConfig(configPath);
}

return provider;
}

function addBudgetsIfGiven(provider: UserFlowProvider, assertOptions: AssertRcOptions = {} as AssertRcOptions): UserFlowProvider {
const { budgetPath, budgets } = assertOptions;

Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/lib/commands/collect/utils/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { readFile, writeFile } from '../../../../core/file';
import { logVerbose } from '../../../../core/loggin';
import { DEFAULT_COLLECT_CONFIG_PATH } from '../../options/configPath.constant';
import { LhConfigJson } from '../../../../hacky-things/lighthouse';


export function readConfig(configPath: string = DEFAULT_COLLECT_CONFIG_PATH): LhConfigJson {
const configJson = JSON.parse(readFile(configPath) || '{}');
return configJson;
}

export function writeConfig(config: LhConfigJson, configPath: string = DEFAULT_COLLECT_CONFIG_PATH): void {
logVerbose(`Update config under ${configPath}`);

if (JSON.stringify(readConfig()) !== JSON.stringify(config)) {
writeFile(configPath, JSON.stringify(config));
logVerbose(`New config ${JSON.stringify(config)}`);
} else {
logVerbose(`No updates for ${configPath} to save.`);
}
}
3 changes: 2 additions & 1 deletion packages/cli/src/lib/commands/collect/utils/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function getCollectCommandOptionsFromArgv(argv: RcJsonAsArgv): CollectCom

const {
url, ufPath, serveCommand, awaitServeStdout, dryRun, openReport,
outPath, format, budgetPath, budgets
outPath, format, budgetPath, budgets, configPath
} = (argv || {}) as any as (keyof CollectRcOptions & keyof PersistRcOptions);

let collect = {} as CollectArgvOptions;
Expand All @@ -26,6 +26,7 @@ export function getCollectCommandOptionsFromArgv(argv: RcJsonAsArgv): CollectCom
// optional
serveCommand && (collect.serveCommand = serveCommand);
awaitServeStdout && (collect.awaitServeStdout = awaitServeStdout);
configPath && (collect.configPath = configPath);
// cli only
dryRun !== undefined && (collect.dryRun = Boolean(dryRun));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { logVerbose } from '../../../../core/loggin';
import * as puppeteer from 'puppeteer';
import { Browser, Page } from 'puppeteer';
import { normalize } from 'path';
import { startFlow, UserFlow } from '../../../../hacky-things/lighthouse';
import { LhConfigJson, startFlow, UserFlow } from '../../../../hacky-things/lighthouse';
import { get as dryRun } from '../../../../commands/collect/options/dryRun';
import { UserFlowMock } from './user-flow.mock';
import * as Config from 'lighthouse/types/config';
import Budget from 'lighthouse/types/lhr/budget';
import { readBudgets } from '../../../assert/utils/budgets';
import { detectCliMode } from '../../../../global/cli-mode/cli-mode';
import { CollectArgvOptions } from '../../options/types';
import { readConfig } from '../config';

export async function collectFlow(
cliOption: CollectArgvOptions,
Expand All @@ -24,8 +25,13 @@ export async function collectFlow(
launchOptions
} = userFlowProvider;

const { config, ...rest } = providerFlowOptions;
const flowOptions = { ...rest, config: parseUserFlowOptionsConfig(providerFlowOptions.config) };
let { config, ...rest } = providerFlowOptions;
let mergedLhConfig = { ...config }
if(cliOption?.configPath) {
mergedLhConfig = {...mergedLhConfig, ...readConfig(cliOption.configPath)};
}

const flowOptions = { ...rest, config: parseUserFlowOptionsConfig(mergedLhConfig) };

// object containing the options for puppeteer/chromium
launchOptions = launchOptions || {
Expand Down Expand Up @@ -59,14 +65,14 @@ export async function collectFlow(
}


function parseUserFlowOptionsConfig(flowOptionsConfig?: UserFlowProvider['flowOptions']['config']): Config.default.Json {
function parseUserFlowOptionsConfig(flowOptionsConfig?: LhConfigJson): Config.default.Json {
flowOptionsConfig = flowOptionsConfig || {} as any;
// @ts-ignore
flowOptionsConfig?.extends || (flowOptionsConfig.extends = 'lighthouse:default');

// if budgets are given
if (flowOptionsConfig?.settings?.budgets) {
logVerbose('format given budgets')
logVerbose('Use budgets from UserFlowProvider objects under the flowOptions.settings.budgets property');
let budgets: Budget[] = flowOptionsConfig?.settings?.budgets;
budgets && (budgets = Array.isArray(budgets) ? budgets : readBudgets(budgets));
flowOptionsConfig.settings.budgets = budgets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import FlowResult from 'lighthouse/types/lhr/flow';
import { StepOptions, UserFlowOptions } from './types';

const dummyFlowResult: (cfg: UserFlowOptions) => FlowResult = (cfg: UserFlowOptions): FlowResult => {
const budgets = cfg?.config?.settings?.budgets;
const config = cfg?.config || { };
logVerbose('dummy config used:', config)
const report = {
name: cfg.name,
steps: [
{
name: 'Navigation report (127.0.0.1/)',
lhr: {
fetchTime: new Date().toISOString(),
configSettings: {
// "budgets": [] // budgets from configurations
},
configSettings: config,
audits: {
// "performance-budget": {},
// "timing-budget": {}
Expand All @@ -23,6 +22,11 @@ const dummyFlowResult: (cfg: UserFlowOptions) => FlowResult = (cfg: UserFlowOpti
}
]
};
if (config) {
report.steps[0].lhr.configSettings = config;
}

const budgets = config?.settings?.budgets;
if (budgets) {
report.steps[0].lhr.configSettings.budgets = budgets;
report.steps[0].lhr.audits['performance-budget'] = {};
Expand Down Expand Up @@ -67,6 +71,7 @@ export class UserFlowMock {
logVerbose(`flow#navigate: ${stepName || requestor}`);
return this.page.goto(requestor);
}

constructor(page: Page, cfg: UserFlowOptions) {
this.page = page;
this.cfg = cfg;
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/lib/global/rc-json/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import { RcJson } from '../../types';
import { globalOptions } from '../options';

export function readRcConfig(rcPath: string = ''): RcJson {
const configPath = rcPath || globalOptions.getRcPath();
const repoConfigJson = readFile<RcJson>(configPath, { ext: 'json' }) || {};
rcPath = rcPath || globalOptions.getRcPath();
const repoConfigJson = readFile<RcJson>(rcPath, { ext: 'json' }) || {};
return repoConfigJson;
}

export function updateRcConfig(config: RcJson, rcPath: string = ''): void {
const configPath = rcPath || globalOptions.getRcPath();
rcPath = rcPath || globalOptions.getRcPath();
// NOTICE: this is needed for better git flow.
// Touch a file only if needed
if (JSON.stringify(readRcConfig()) !== JSON.stringify(config)) {
writeFile(configPath, JSON.stringify(config));
logVerbose(`Update config under ${configPath} to`, config);
writeFile(rcPath, JSON.stringify(config));
logVerbose(`Update config under ${rcPath} to`, config);
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/lib/hacky-things/lighthouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
*
* */

// @ts-ignore
import { default as LhConfig } from 'lighthouse/types/config';
export type LhConfigJson = LhConfig.Json;

// @ts-ignore
export {Util} from 'lighthouse/lighthouse-core/util-commonjs';
// @ts-ignore
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/tests/commands/collect/collect.budgets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('$collect() sandbox+NO-assets with RC()', () => {
afterEach(async () => await staticPrj.teardown());

it('should NOT log budgets info', async () => {
const { exitCode, stdout, stderr } = await staticPrj.$collect({});
const { exitCode, stdout, stderr } = await staticPrj.$collect();

expect(stderr).toBe('');
expectNoBudgetsFileExistLog(stdout);
Expand Down Expand Up @@ -85,7 +85,8 @@ let staticWBudgetPathPrjCfg: UserFlowProjectConfig = {
create: {
...STATIC_PRJ_CFG.create,
[LH_NAVIGATION_BUDGETS_NAME]: LH_NAVIGATION_BUDGETS
}
},
delete: (STATIC_PRJ_CFG?.delete || []).concat([LH_NAVIGATION_BUDGETS_NAME])
};

describe('$collect() sandbox+assets with RC({budgetPath}))', () => {
Expand Down
78 changes: 78 additions & 0 deletions packages/cli/tests/commands/collect/collect.configPath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { UserFlowCliProject, UserFlowCliProjectFactory } from '../../user-flow-cli-project/user-flow-cli';
import { STATIC_PRJ_CFG } from '../../fixtures/sandbox/static';
import { UserFlowProjectConfig } from '../../user-flow-cli-project/types';
import { STATIC_JSON_REPORT_NAME, STATIC_RC_JSON } from '../../fixtures/rc-files/static';
import { DEFAULT_RC_NAME } from '../../../src/lib/constants';
import {
expectConfigPathUsageLog,
expectNoConfigFileExistLog,
expectResultsToIncludeConfig
} from '../../user-flow-cli-project/expect';
import { LH_CONFIG, LH_CONFIG_NAME } from '../../fixtures/config/lh-config';
import { expectCollectCfgToContain } from '../../utils/cli-expectations';

let staticPrj: UserFlowCliProject;

describe('$collect() sandbox+NO-assets with RC()', () => {
beforeEach(async () => {
if (!staticPrj) {
staticPrj = await UserFlowCliProjectFactory.create(STATIC_PRJ_CFG);
}
await staticPrj.setup();
});
afterEach(async () => await staticPrj.teardown());

it('should NOT log configPath info', async () => {
const { exitCode, stdout, stderr } = await staticPrj.$collect();

expect(stderr).toBe('');
expectNoConfigFileExistLog(stdout);
expect(exitCode).toBe(0);

});

});

let staticWConfigAssetsPrj: UserFlowCliProject;
let staticWConfigPathPrjCfg: UserFlowProjectConfig = {
...STATIC_PRJ_CFG,
rcFile: {
[DEFAULT_RC_NAME]: {
...STATIC_RC_JSON,
collect: {
...STATIC_RC_JSON.collect,
configPath: LH_CONFIG_NAME
}
}
},
create: {
...STATIC_PRJ_CFG.create,
[LH_CONFIG_NAME]: LH_CONFIG
},
delete: [LH_CONFIG_NAME].concat(STATIC_PRJ_CFG?.delete || [])
};

describe('$collect() sandbox+assets with RC({configPath}))', () => {
beforeEach(async () => {
if (!staticWConfigAssetsPrj) {
staticWConfigAssetsPrj = await UserFlowCliProjectFactory.create(staticWConfigPathPrjCfg);
}
await staticWConfigAssetsPrj.setup();
});
afterEach(async () => await staticWConfigAssetsPrj.teardown());

it('should load configPath from RC file', async () => {
const { exitCode, stdout, stderr } = await staticWConfigAssetsPrj.$collect({
configPath: LH_CONFIG_NAME
});


expect(stderr).toBe('');
expectCollectCfgToContain(stdout, {configPath: LH_CONFIG_NAME})
expectConfigPathUsageLog(stdout, LH_CONFIG_NAME);
expectResultsToIncludeConfig(staticWConfigAssetsPrj, STATIC_JSON_REPORT_NAME.split('.json').pop()+'');
expect(exitCode).toBe(0);

}, 60_000);

});
12 changes: 12 additions & 0 deletions packages/cli/tests/fixtures/config/lh-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LhConfigJson } from '../../../src/lib/hacky-things/lighthouse';

export const LH_CONFIG_NAME = 'config.json';
export const LH_CONFIG: LhConfigJson = {
extends: 'lighthouse:default',
settings: {
/** If present, the run should only conduct this list of audits. */
onlyAudits: ['lcp-lazy-loaded']
/** If present, the run should only conduct this list of categories. */
//'only-categories': ['performance']
}
};
Loading

0 comments on commit a7e9ea2

Please sign in to comment.