playwright-bdd
Version:
BDD Testing with Playwright runner
142 lines (120 loc) • 3.94 kB
text/typescript
/**
* Step definition class.
*/
import { CucumberExpression, RegularExpression, Expression } from '@cucumber/cucumber-expressions';
import { PickleStepType } from '@cucumber/messages';
import { parameterTypeRegistry } from './parameterTypes';
import { PlaywrightLocation, TestTypeCommon } from '../playwright/types';
import { PomNode } from './decorators/pomGraph';
import { MatchedStepDefinition } from './matchedStepDefinition';
import { buildTagsExpression, extractTagsFromPath, TagsExpression } from './tags';
import { relativeToCwd } from '../utils/paths';
export type GherkinStepKeyword = 'Unknown' | 'Given' | 'When' | 'Then';
export type StepPattern = string | RegExp;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AnyFunction = (...args: any[]) => unknown;
export type ProvidedStepOptions = {
tags?: string;
};
export type StepDefinitionOptions = {
keyword: GherkinStepKeyword;
pattern: StepPattern;
fn: AnyFunction;
location: PlaywrightLocation;
customTest?: TestTypeCommon;
pomNode?: PomNode; // for decorator steps
worldFixture?: string; // for new cucumber-style steps
providedOptions?: ProvidedStepOptions; // options passed as second argument
defaultTags?: string; // default tags from createBdd() or @Fixture
};
export class StepDefinition {
#expression?: Expression;
tagsExpression?: TagsExpression;
constructor(private options: StepDefinitionOptions) {
this.buildTagsExpression();
}
get keyword() {
return this.options.keyword;
}
get pattern() {
return this.options.pattern;
}
get fn() {
return this.options.fn;
}
get uri() {
return this.options.location.file;
}
get line() {
return this.options.location.line;
}
get customTest() {
return this.options.customTest;
}
get pomNode() {
return this.options.pomNode;
}
get worldFixture() {
return this.options.worldFixture;
}
get expression() {
// create expression lazily b/c we need all parameter types to be loaded
if (!this.#expression) {
this.#expression =
typeof this.pattern === 'string'
? new CucumberExpression(this.pattern, parameterTypeRegistry)
: new RegularExpression(this.pattern, parameterTypeRegistry);
}
return this.#expression;
}
get patternString() {
return typeof this.pattern === 'string' ? this.pattern : this.pattern.source;
}
/**
* Decorator steps have pom node.
*/
isDecorator(): this is this & { pomNode: PomNode } {
return Boolean(this.options.pomNode);
}
/**
* New cucumber-style steps have worldFixture in step config.
*/
isCucumberStyle(): this is this & { worldFixture: string } {
return Boolean(this.options.worldFixture);
}
/**
* Tries to match step text.
* Returns MatchedStepDefinition in case of success.
*/
matchStepText(stepText: string) {
const result = this.expression.match(stepText);
if (result) {
return new MatchedStepDefinition(this, stepText, result);
}
}
// eslint-disable-next-line visual/complexity
matchesKeywordType(keywordType: PickleStepType | undefined) {
if (!keywordType || keywordType === PickleStepType.UNKNOWN) return true;
switch (this.keyword) {
case 'Given':
return keywordType === PickleStepType.CONTEXT;
case 'When':
return keywordType === PickleStepType.ACTION;
case 'Then':
return keywordType === PickleStepType.OUTCOME;
default: // Unknown
return true;
}
}
private buildTagsExpression() {
const { defaultTags, providedOptions, location } = this.options;
// Possibly, we should use relative to configDir
const relFilePath = relativeToCwd(location.file);
const tagsFromPath = extractTagsFromPath(relFilePath);
this.tagsExpression = buildTagsExpression([
...tagsFromPath,
defaultTags,
providedOptions?.tags,
]);
}
}