Skip to content

Commit

Permalink
fix default world in Playwright-style steps (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalets committed Dec 18, 2024
1 parent 19af171 commit 3819441
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Dev
* fix default world in Playwright-style steps ([#255](https://github.com/vitalets/playwright-bdd/issues/255))

## 8.0.0
* support tags from path ([#217](https://github.com/vitalets/playwright-bdd/issues/217))
* support scoped step definitions by tags ([#205](https://github.com/vitalets/playwright-bdd/issues/205))
Expand Down
18 changes: 14 additions & 4 deletions src/steps/styles/playwrightStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,27 @@ export function playwrightStepCtor(
* See: https://github.com/vitalets/playwright-bdd/issues/110
*/
function getCallableStepFn<StepFn extends AnyFunction>(pattern: StepPattern, fn: StepFn) {
// need Partial<...> here, otherwise TS requires all Playwright fixtures to be passed
return (fixtures: Partial<Parameters<StepFn>[0]>, ...args: ParametersExceptFirst<StepFn>) => {
// need Partial<...> here, otherwise TS requires _all_ Playwright fixtures to be passed
type StepFnArguments =
Parameters<StepFn> extends []
? []
: [Partial<Parameters<StepFn>[0]>, ...ParametersExceptFirst<StepFn>];

// todo: move default world type somewhere
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type DefaultWorld = any;

return function (this: DefaultWorld, ...args: StepFnArguments) {
const [fixtures, ...params] = args;
assertStepIsCalledWithRequiredFixtures(pattern, fn, fixtures);
return fn(fixtures, ...args);
return fn.call(this, fixtures, ...params);
};
}

function assertStepIsCalledWithRequiredFixtures<StepFn extends AnyFunction>(
pattern: StepPattern,
fn: StepFn,
passedFixtures: Partial<Parameters<StepFn>[0]>,
passedFixtures: Record<string, unknown> = {},
) {
const requiredFixtures = fixtureParameterNames(fn);
const missingFixtures = requiredFixtures.filter(
Expand Down
25 changes: 14 additions & 11 deletions test/reuse-step-fn/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import { defineConfig } from '@playwright/test';
import { defineBddConfig } from 'playwright-bdd';

const TAG = process.env.TAG; // @success, @error

export default defineConfig({
projects: [
{
name: 'pw-style-success',
name: 'pw-style',
testDir: defineBddConfig({
outputDir: '.features-gen/pw-style-success',
outputDir: `.features-gen/pw-style-${TAG}`,
paths: ['features/*.feature'],
require: ['steps-pw-style/*.ts'],
tags: '@success',
tags: TAG,
}),
},
{
name: 'pw-style-invalid-invocation',
name: 'cucumber-style',
testDir: defineBddConfig({
outputDir: '.features-gen/pw-style-invalid-invocation',
outputDir: `.features-gen/cucumber-style-${TAG}`,
paths: ['features/*.feature'],
require: ['steps-pw-style/*.ts'],
tags: '@error',
require: ['steps-cucumber-style/*.ts'],
tags: TAG,
}),
},
{
name: 'cucumber-style-success',
name: 'pw-style-world',
testDir: defineBddConfig({
outputDir: '.features-gen/cucumber-style-success',
outputDir: `.features-gen/pw-style-world-${TAG}`,
paths: ['features/*.feature'],
require: ['steps-cucumber-style/*.ts'],
tags: '@success',
require: ['steps-pw-style-world/*.ts'],
verbose: true,
tags: TAG,
}),
},
],
Expand Down
18 changes: 18 additions & 0 deletions test/reuse-step-fn/steps-cucumber-style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,21 @@ When('I create 2 todos {string} and {string}', async function (text1: string, te
Then('I see todos:', async function (data: DataTable) {
expect(this.todos).toEqual(data.rows().flat());
});

When(
'I incorrectly create 2 todos {string} and {string}',
async function (_text1: string, _text2: string) {
// todo: incorrect invocation: forget to pass world
// await createTodo(text1, text2);
},
);

// step without parameters - check typing

const fn = When('a step', async () => {});

expectTypeOf(fn).parameters.toEqualTypeOf<[]>();

Then('another step', async () => {
fn.call(this);
});
44 changes: 44 additions & 0 deletions test/reuse-step-fn/steps-pw-style-world/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createBdd, DataTable } from 'playwright-bdd';
import { expect } from '@playwright/test';
import { expectTypeOf } from 'expect-type';

const { When, Then } = createBdd();

const createTodo = When('I create todo {string}', async function ({ $testInfo }, text: string) {
this.todos = this.todos || [];
this.todos.push(`${$testInfo.title} - ${text}`);
});

expectTypeOf(createTodo).toBeFunction();
expectTypeOf(createTodo).parameter(0).toHaveProperty('$testInfo');
expectTypeOf(createTodo).parameter(1).toEqualTypeOf<string>();

When(
'I create 2 todos {string} and {string}',
async function ({ $testInfo }, text1: string, text2: string) {
await createTodo.call(this, { $testInfo }, text1);
await createTodo.call(this, { $testInfo }, text2);
},
);

Then('I see todos:', async function ({}, data: DataTable) {
expect(this.todos).toEqual(data.rows().flat());
});

When(
'I incorrectly create 2 todos {string} and {string}',
async function ({}, text1: string, _text2: string) {
// incorrect invocation, should pass $testInfo
await createTodo.call(this, {}, text1);
},
);

// step without parameters - check typing

const fn = When('a step', async function () {});

expectTypeOf(fn).parameters.toEqualTypeOf<[]>();

Then('another step', async function () {
fn.call(this);
});
10 changes: 10 additions & 0 deletions test/reuse-step-fn/steps-pw-style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ When(
Then('I see todos:', async ({ todos }, data: DataTable) => {
expect(todos).toEqual(data.rows().flat());
});

// step without parameters - check typing

const fn = When('a step', async () => {});

// expectTypeOf(fn).parameters.toEqualTypeOf<[]>();

Then('another step', async () => {
fn();
});
39 changes: 29 additions & 10 deletions test/reuse-step-fn/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,37 @@ import {

const testDir = new TestDir(import.meta);

test(`${testDir.name} (pw-style-success)`, () => {
execPlaywrightTest(testDir.name, `${DEFAULT_CMD} --project pw-style-success`);
test(`${testDir.name} (pw-style, success)`, () => {
execPlaywrightTest(testDir.name, {
cmd: `${DEFAULT_CMD} --project pw-style`,
env: { TAG: '@success' },
});
});

test(`${testDir.name} (cucumber-style-success)`, () => {
execPlaywrightTest(testDir.name, `${DEFAULT_CMD} --project cucumber-style-success`);
test(`${testDir.name} (pw-style, error)`, () => {
execPlaywrightTestWithError(testDir.name, ['Missing fixtures: todos, $testInfo'], {
cmd: `${DEFAULT_CMD} --project pw-style`,
env: { TAG: '@error' },
});
});

test(`${testDir.name} (pw-style-invalid-invocation)`, () => {
execPlaywrightTestWithError(
testDir.name,
['Missing fixtures: todos, $testInfo'],
`${DEFAULT_CMD} --project pw-style-invalid-invocation`,
);
test(`${testDir.name} (cucumber-style, success)`, () => {
execPlaywrightTest(testDir.name, {
cmd: `${DEFAULT_CMD} --project cucumber-style`,
env: { TAG: '@success' },
});
});

test(`${testDir.name} (pw-style-world, success)`, () => {
execPlaywrightTest(testDir.name, {
cmd: `${DEFAULT_CMD} --project pw-style-world`,
env: { TAG: '@success' },
});
});

test(`${testDir.name} (pw-style-world, error)`, () => {
execPlaywrightTestWithError(testDir.name, ['Missing fixtures: $testInfo'], {
cmd: `${DEFAULT_CMD} --project pw-style-world`,
env: { TAG: '@error' },
});
});

0 comments on commit 3819441

Please sign in to comment.