playwright-bdd
Version:
BDD Testing with Playwright runner
103 lines (88 loc) • 3.3 kB
text/typescript
/**
* Define steps via decorators.
*/
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { PomNode } from './pomGraph';
import { isBddAutoInjectFixture } from '../../runtime/bddTestFixturesAuto';
import { getLocationByOffset } from '../../playwright/getLocationInFile';
import { registerStepDefinition } from '../stepRegistry';
import {
StepPattern,
GherkinStepKeyword,
StepDefinitionOptions,
ProvidedStepOptions,
} from '../stepDefinition';
// initially we store step data inside method,
// and then extract it in @Fixture decorator call
const decoratedStepSymbol = Symbol('decoratedStep');
type DecoratedMethod = Function & { [decoratedStepSymbol]: StepDefinitionOptions };
/**
* Creates @Given, @When, @Then decorators.
*/
export function createStepDecorator(keyword: GherkinStepKeyword) {
return (pattern: StepPattern, providedOptions?: ProvidedStepOptions) => {
// offset = 3 b/c this call is 3 steps below the user's code
const location = getLocationByOffset(3);
// context parameter is required for decorator by TS even though it's not used
return (method: StepDefinitionOptions['fn'], _context: ClassMethodDecoratorContext) => {
saveStepConfigToMethod(method, {
keyword,
pattern,
location,
providedOptions,
fn: method,
});
};
};
}
export function linkStepsWithPomNode(Ctor: Function, pomNode: PomNode) {
if (!Ctor?.prototype) return;
const propertyDescriptors = Object.getOwnPropertyDescriptors(Ctor.prototype);
return Object.values(propertyDescriptors).forEach((descriptor) => {
const stepOptions = getStepOptionsFromMethod(descriptor);
if (!stepOptions) return;
stepOptions.pomNode = pomNode;
stepOptions.defaultTags = pomNode.fixtureTags;
registerDecoratorStep(stepOptions);
});
}
// todo: link decorator steps with customTest!
function registerDecoratorStep(stepOptions: StepDefinitionOptions) {
const { fn } = stepOptions;
registerStepDefinition({
...stepOptions,
fn: (fixturesArg: Record<string, unknown>, ...args: unknown[]) => {
const pomFixture = getFirstNonAutoInjectFixture(fixturesArg, stepOptions);
return fn.call(pomFixture, ...args);
},
});
}
function getFirstNonAutoInjectFixture(
fixturesArg: Record<string, unknown>,
{ pattern }: StepDefinitionOptions,
) {
// there should be exactly one suitable fixture in fixturesArg
const fixtureNames = Object.keys(fixturesArg).filter(
(fixtureName) => !isBddAutoInjectFixture(fixtureName),
);
const firstFixtureName = fixtureNames[0];
if (!firstFixtureName) {
throw new Error(`No suitable fixtures found for decorator step "${pattern}"`);
}
if (fixtureNames.length > 1) {
throw new Error(`Several suitable fixtures found for decorator step "${pattern}"`);
}
return fixturesArg[firstFixtureName];
}
function saveStepConfigToMethod(
method: StepDefinitionOptions['fn'],
stepConfig: StepDefinitionOptions,
) {
(method as unknown as DecoratedMethod)[decoratedStepSymbol] = stepConfig;
}
function getStepOptionsFromMethod(descriptor: PropertyDescriptor) {
// filter out getters / setters
return typeof descriptor.value === 'function'
? (descriptor.value as DecoratedMethod)[decoratedStepSymbol]
: undefined;
}