Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config path to collect #168

Merged
merged 12 commits into from
Dec 17, 2022
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