Skip to content

Commit

Permalink
feature: show locations for unused steps
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalets committed Apr 12, 2024
1 parent a4ce9f3 commit 1816bcc
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 28 deletions.
31 changes: 20 additions & 11 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"release": "bash scripts/release.sh"
},
"dependencies": {
"cli-table3": "0.6.4",
"commander": "^11.1.0",
"fast-glob": "^3.3.2",
"supports-color": "8.1.1"
Expand Down
32 changes: 26 additions & 6 deletions src/cli/commands/export.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import path from 'node:path';
import { Command } from 'commander';
import StepDefinition from '@cucumber/cucumber/lib/models/step_definition';
import Table from 'cli-table3';
import { ConfigOption, configOption } from '../options';
import { loadConfig as loadPlaywrightConfig } from '../../playwright/loadConfig';
import { Logger } from '../../utils/logger';
import { getEnvConfigs } from '../../config/env';
import { assertConfigsCount } from './test';
import { BDDConfig } from '../../config';
import { TestFilesGenerator } from '../../gen';
import { getStepConfig } from '../../steps/stepConfig';

const logger = new Logger({ verbose: true });

Expand Down Expand Up @@ -37,7 +39,7 @@ async function showStepsForConfigs(configs: BDDConfig[]) {
const steps = new Set<string>();
const tasks = configs.map(async (config) => {
const stepDefinitions = await new TestFilesGenerator(config).extractSteps();
stepDefinitions.forEach((s) => steps.add(`* ${getStepText(s)}`));
stepDefinitions.forEach((s) => steps.add(`* ${formatStepText(s)}`));
});

await Promise.all(tasks);
Expand All @@ -47,21 +49,39 @@ async function showStepsForConfigs(configs: BDDConfig[]) {
}

async function showUnusedStepsForConfigs(configs: BDDConfig[]) {
const steps = new Set<string>();
const steps = new Set<StepDefinition>();
const tasks = configs.map(async (config) => {
const stepDefinitions = await new TestFilesGenerator(config).extractUnusedSteps();
stepDefinitions.forEach((s) => steps.add(`* ${getStepText(s)}`));
stepDefinitions.forEach((step) => steps.add(step));
});

await Promise.all(tasks);

logger.log(`List of unused steps (${steps.size}):`);
steps.forEach((stepText) => logger.log(stepText));
logger.log(`Unused steps (${steps.size}):`);
logger.log(formatUnusedStepsTable(steps));
}

function formatUnusedStepsTable(steps: Set<StepDefinition>) {
const table = new Table({
head: ['Pattern / Text', /*'Duration', */ 'Location'],
style: { border: [], head: [] }, // disable colors
});
steps.forEach((step) => {
table.push([formatStepText(step), formatStepLocation(step)]);
});
return table.toString();
}

function getStepText({ pattern, keyword }: StepDefinition) {
function formatStepText({ pattern, keyword }: StepDefinition) {
// for Unknown return When as it looks the most suitable
const keywordText = keyword === 'Unknown' ? 'When' : keyword;
const patternText = typeof pattern === 'string' ? pattern : pattern.source;
return `${keywordText} ${patternText}`;
}

function formatStepLocation(step: StepDefinition) {
const { location } = getStepConfig(step) || {};
if (!location) return '';
const relativeFile = path.relative(process.cwd(), location.file);
return `${relativeFile}:${location.line}`;
}
11 changes: 7 additions & 4 deletions src/playwright/getLocationInFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ import { PlaywrightLocation } from './types';
*
* Returned value: { file: 'file-3.js', line: 5, column: 6 }
*/
export function getLocationInFile(filePath: string) {
const filePathUrl = url.pathToFileURL(filePath).toString();
export function getLocationInFile(absFilePath: string) {
const absFileUrl = url.pathToFileURL(absFilePath).toString();
return getLocationBy((stackFrames) => {
return stackFrames.find((frame) => {
const frameFile = frame.getFileName();
return frameFile === filePath || frameFile === filePathUrl;
return frameFile === absFilePath || frameFile === absFileUrl;
});
});
}

/**
* Returns location of function call <offset> stack frames upper.
*/
export function getLocationByOffset(offset: number) {
return getLocationBy((stackFrames) => stackFrames[offset]);
}
Expand All @@ -42,7 +45,7 @@ function getLocationBy(findFrame: (stackFrame: NodeJS.CallSite[]) => NodeJS.Call
const { sourceMapSupport } = requirePlaywrightModule('lib/utilsBundle.js');
const oldPrepareStackTrace = Error.prepareStackTrace;
// modify prepareStackTrace to return Location object instead of string
Error.prepareStackTrace = (error, stackFrames) => {
Error.prepareStackTrace = (_error, stackFrames) => {
const foundFrame = findFrame(stackFrames);
if (!foundFrame) return { file: '', line: 0, column: 0 };
const frame: NodeJS.CallSite = sourceMapSupport.wrapCallSite(foundFrame);
Expand Down
2 changes: 2 additions & 0 deletions src/steps/createBdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { scenarioHookFactory } from '../hooks/scenario';
import { workerHookFactory } from '../hooks/worker';
import { fixtureParameterNames } from '../playwright/fixtureParameterNames';
import { BddAutoInjectFixtures } from '../run/bddFixtures/autoInject';
import { getLocationByOffset } from '../playwright/getLocationInFile';

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types */

Expand Down Expand Up @@ -63,6 +64,7 @@ function defineStepCtor<T extends KeyValue, W extends KeyValue = {}>(
pattern,
fn,
hasCustomTest,
location: getLocationByOffset(3),
});
return (fixtures: Partial<StepFunctionFixturesArg<T, W>>, ...args: Args) => {
assertStepIsCalledWithRequiredFixtures(pattern, fn, fixtures);
Expand Down
8 changes: 6 additions & 2 deletions src/steps/decorators/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { buildCucumberStepFn } from '../defineStep';
import { PomNode } from './class';
import { ISupportCodeLibrary } from '../../cucumber/types';
import { isBddAutoInjectFixture } from '../../run/bddFixtures/autoInject';
import { getLocationByOffset } from '../../playwright/getLocationInFile';

// initially we sotre step data inside method,
// and then extract it in @Fixture decorator call
Expand All @@ -26,11 +27,13 @@ const decoratedSteps = new Set<StepConfig>();
*/
export function createStepDecorator(keyword: GherkinStepKeyword) {
return (pattern: DefineStepPattern) => {
const location = getLocationByOffset(3);
// context parameter is required for decorator by TS even though it's not used
return (method: Function, _context: ClassMethodDecoratorContext) => {
saveStepConfigToMethod(method, {
keyword,
pattern,
location,
fn: method,
hasCustomTest: true,
});
Expand Down Expand Up @@ -60,14 +63,15 @@ export function appendDecoratorSteps(supportCodeLibrary: ISupportCodeLibrary) {
return fn.call(fixture, ...args);
};
const code = buildCucumberStepFn(stepConfig);
const { file: uri, line } = stepConfig.location;
const stepDefinition = buildStepDefinition(
{
keyword,
pattern,
code,
line: 0, // not used in playwright-bdd
uri,
line,
options: {}, // not used in playwright-bdd
uri: '', // not used in playwright-bdd
},
supportCodeLibrary,
);
Expand Down
2 changes: 2 additions & 0 deletions src/steps/stepConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {
import StepDefinition from '@cucumber/cucumber/lib/models/step_definition';
import { BddWorld } from '../run/bddWorld';
import { PomNode } from './decorators/class';
import { PlaywrightLocation } from '../playwright/types';

export type StepConfig = {
keyword: GherkinStepKeyword;
pattern: DefineStepPattern;
// eslint-disable-next-line @typescript-eslint/ban-types
fn: Function;
hasCustomTest: boolean;
location: PlaywrightLocation;
pomNode?: PomNode; // for decorator steps
};

Expand Down
1 change: 1 addition & 0 deletions test/cli-command-export/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default defineConfig({
name: 'project1',
testDir: defineBddConfig({
outputDir: '.features-gen/one',
importTestFrom: 'steps/fixtures.ts',
paths: ['features'],
require: ['steps/steps.ts'],
}),
Expand Down
9 changes: 9 additions & 0 deletions test/cli-command-export/steps/TodoPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Fixture, Given } from 'playwright-bdd/decorators';
import { test } from './fixtures';

export
@Fixture<typeof test>('todoPage')
class TodoPage {
@Given('TodoPage: step')
async step() {}
}
6 changes: 6 additions & 0 deletions test/cli-command-export/steps/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { test as base } from 'playwright-bdd';
import { TodoPage } from './TodoPage';

export const test = base.extend<{ todoPage: TodoPage }>({
todoPage: ({}, use) => use(new TodoPage()),
});
19 changes: 14 additions & 5 deletions test/cli-command-export/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@ const testDir = new TestDir(import.meta);
test(`${testDir.name} (all steps)`, () => {
const stdout = execPlaywrightTest(testDir.name, `${BDDGEN_CMD} export`);
expect(stdout).toContain('Using config: playwright.config.ts');
expect(stdout).toContain('List of all steps (5):');
expect(stdout).toContain('List of all steps (6):');

expect(stdout).toContain('* Given I am on todo page');
expect(stdout).toContain('* When I add todo {string}');
expect(stdout).toContain('* Then visible todos count is (\\d+)');
expect(stdout).toContain('* When some step');
expect(stdout).toContain('* Given I am on another todo page');
expect(stdout).toContain('* Given TodoPage: step');
});

test(`${testDir.name} (unused steps)`, () => {
const stdout = execPlaywrightTest(testDir.name, `${BDDGEN_CMD} export --unused-steps`);
expect(stdout).toContain('Using config: playwright.config.ts');
expect(stdout).toContain(`List of unused steps (3):`);
expect(stdout).toContain(`Unused steps (5):`);

expect(stdout).toContain('* When I add todo {string}');
expect(stdout).toContain('* When some step');
expect(stdout).toContain('* Given I am on another todo page');
expect(stdout).toContain('When I add todo {string}'); // twice
expect(stdout).toContain('When some step');
expect(stdout).toContain('Given I am on another todo page');
expect(stdout).toContain('Given TodoPage: step');

// locations
expect(stdout).toContain('steps/steps.ts:9');
expect(stdout).toContain('steps/steps.ts:17');
expect(stdout).toContain('steps/steps2.ts:5');
expect(stdout).toContain('steps/steps2.ts:10');
expect(stdout).toContain('steps/TodoPage.ts:7');
});

0 comments on commit 1816bcc

Please sign in to comment.