Skip to content

Commit

Permalink
merge main into beta
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalets committed Apr 11, 2024
2 parents 6981d7c + cbfd9b7 commit c0aa39e
Show file tree
Hide file tree
Showing 25 changed files with 189 additions and 124 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## dev
* feature: add `$step` fixture ([#133](https://github.com/vitalets/playwright-bdd/issues/133))
* feature: add experimental support of `steps` option (related to [#94](https://github.com/vitalets/playwright-bdd/issues/94))

## 6.1.1
Expand Down
8 changes: 8 additions & 0 deletions docs/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ Quotes style in generated test files.

[Tags expression](https://cucumber.io/docs/cucumber/api/?lang=javascript#tag-expressions) to filter scenarios for generation. Can be also defined by CLI option `--tags`.

Example:
```ts
const testDir = defineBddConfig({
tags: '@desktop and not @slow',
// ...
});
```

## verbose

- Type: `boolean`
Expand Down
19 changes: 11 additions & 8 deletions docs/writing-steps/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The benefits of using `auth` fixture:

> Consider using [fixtures](#fixtures) instead of hooks.
`playwright-bdd` supports worker-level `BeforeAll / AfterAll` hooks similar to [Cucumber hooks](https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/hooks.md#beforeall--afterall). See the [API reference](api.md#beforealloptions-hookfn) for the full specification.
`playwright-bdd` supports worker-level `BeforeAll / AfterAll` hooks similar to [Cucumber hooks](https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/hooks.md#beforeall--afterall). Notice that these hooks are imported from `createBdd()`, not from `@cucumber/cucumber`.

Usage:
```ts
Expand Down Expand Up @@ -116,9 +116,11 @@ Imagine you have defined custom worker fixture `myWorkerFixture`:
```ts
import { test as base } from 'playwright-bdd';

export const test = base.extend<{}, { myWorkerFixture: string }>({
myWorkerFixture: [async ({ page }, use) => {
await use('myWorkerFixture');
export const test = base.extend<{}, { myWorkerFixture: MyWorkerFixture }>({
myWorkerFixture: [async ({ browser }, use) => {
const fixture = new MyWorkerFixture(browser);
await fixture.setup();
await use(fixture);
}, { scope: 'worker' }]
});
```
Expand All @@ -141,7 +143,7 @@ BeforeAll(async function ({ myWorkerFixture }) {

> Consider using [fixtures](#fixtures) instead of hooks.
`playwright-bdd` supports scenario-level `Before / After` hooks similar to [Cucumber hooks](https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/hooks.md#hooks). See the [API reference](api.md#beforeoptions-hookfn) for the full specification.
`playwright-bdd` supports scenario-level `Before / After` hooks similar to [Cucumber hooks](https://github.com/cucumber/cucumber-js/blob/main/docs/support_files/hooks.md#hooks). Notice that these hooks are imported from `createBdd()`, not from `@cucumber/cucumber`.

Usage:
```ts
Expand Down Expand Up @@ -187,9 +189,10 @@ Imagine you have defined custom fixture `myFixture`:
```ts
import { test as base } from 'playwright-bdd';

export const test = base.extend<{ myFixture: string }>({
export const test = base.extend<{ myFixture: MyFixture }>({
myFixture: async ({ page }, use) => {
await use('myFixture');
const fixture = new MyFixture(page);
await use(fixture);
}
});
```
Expand All @@ -206,7 +209,7 @@ Before(async function ({ myFixture }) {
});
```

> Note that you can access built-in fixtures via `this` as well, e.g. `this.testInfo`.
> Note that you can access built-in fixtures via `this` context, e.g. `this.testInfo`.
### Custom World

Expand Down
24 changes: 22 additions & 2 deletions docs/writing-steps/playwright-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Playwright-style highlights:

* use `Given`, `When`, `Then` from `createBdd()` call (see example below)
* use arrow functions for step definitions
* don't use `World` and `before/after` hooks (use fixtures instead)
* don't use `World` / `this` inside steps

Example:

Expand All @@ -26,7 +26,7 @@ When('I click link {string}', async ({ page }, name: string) => {

> Usually step functions are async, but it is not required
?> Calling `const { Given, When, Then } = createBdd()` at the top of each step file is totally ok, because it returns lightweight wrappers without heavy operations
?> Calling `const { Given, When, Then } = createBdd()` at the top of each step file is normal, because it returns lightweight wrappers without heavy operations

## Fixtures
To use [fixtures](https://playwright.dev/docs/test-fixtures#with-fixtures) in step definitions:
Expand Down Expand Up @@ -96,6 +96,26 @@ Given('I do something', async ({ browserName, $test }) => {
});
```

## Accessing `$step`
You can access current step info by special `$step` fixture.
Currently, it contains only step title, but can be extended in the future.

One of the use-cases - additional matching in the step title.
Imagine you have a step that checks element visibility:
`Then('element with text {string} should( not) be displayed', ...)`.
As optional matches are [not a part](https://github.com/cucumber/cucumber-expressions/issues/125) of Cucumber expression result,
the easiest way to check for `( not)` is to use step title:
```ts
Then('element with text {string} should( not) be displayed', async ({ page, $step }, text: string) => {
const negate = /should not/.test($step.title);
if (negate) {
await expect(page.getByText(text)).toBeHidden();
} else {
await expect(page.getByText(text)).toBeVisible();
}
});
```

## Tags
You can access tags in step definitions by special `$tags` fixture:

Expand Down
13 changes: 6 additions & 7 deletions eslint.config.js → eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const globals = require('globals');
const js = require('@eslint/js');
const tsParser = require('@typescript-eslint/parser');
const tsPlugin = require('@typescript-eslint/eslint-plugin');
const playwright = require('eslint-plugin-playwright');
import globals from 'globals';
import js from '@eslint/js';
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import playwright from 'eslint-plugin-playwright';

module.exports = [
export default [
{
ignores: ['examples', 'dist', '*.config.js', 'cucumber.js', 'test/**/.cache'],
},
Expand Down Expand Up @@ -43,7 +43,6 @@ module.exports = [
'error',
{ anonymous: 'always', named: 'never', asyncArrow: 'always' },
],
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/triple-slash-reference': 0,
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-undef': 0,
Expand Down
2 changes: 1 addition & 1 deletion prettier.config.js → prettier.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
tabWidth: 2,
useTabs: false,
singleQuote: true,
Expand Down
3 changes: 2 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
# Exit on any error
set -euo pipefail

rm -rf ./dist ./test/node_modules/playwright-bdd
rm -rf ./dist
npx tsc -p tsconfig.build.json

# copy dist to 'test/node_modules/playwright-bdd' to allow import from 'playwright-bdd' in tests
rm -rf ./test/node_modules/playwright-bdd
mkdir -p ./test/node_modules/playwright-bdd
cp -R ./dist ./test/node_modules/playwright-bdd/
cp ./package.json ./test/node_modules/playwright-bdd/package.json
2 changes: 1 addition & 1 deletion src/gen/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */

import { fixtureParameterNames } from '../playwright/fixtureParameterNames';
import { isBddAutoInjectFixture } from '../run/bddFixtures';
import { isBddAutoInjectFixture } from '../run/bddFixtures/autoInject';
import { exit } from '../utils/exit';

const bodyFixturesSymbol = Symbol('bodyFixtures');
Expand Down
3 changes: 3 additions & 0 deletions src/gen/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export class Formatter {
];
}

/**
* These fixtures are added only in Cucumber-style
*/
bddWorldFixtures() {
const fixturesObj: Record<keyof BddWorldFixtures, null> = {
page: null,
Expand Down
3 changes: 2 additions & 1 deletion src/playwright/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
TestInfo,
TestType,
} from '@playwright/test';
import { BddFixtures } from '../run/bddFixtures';

import { Location as PlaywrightLocation } from '@playwright/test/reporter';
import { BddFixtures } from '../run/bddFixtures/types';

export type KeyValue = { [key: string]: any };

Expand Down
1 change: 1 addition & 0 deletions src/run/StepInvoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class StepInvoker {
);

this.world.$internal.bddDataManager?.registerStep(stepDefinition, stepText, location);
this.world.step.title = stepText;

await runStepWithCustomLocation(this.world.test, stepTitle, location, () =>
code.apply(this.world, parameters),
Expand Down
35 changes: 35 additions & 0 deletions src/run/bddFixtures/autoInject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Auto-inject fixtures are automatically injected into every step call.
*/

import { TestInfo } from '@playwright/test';
import { BddFixtures } from './types';
import { BddWorld } from '../bddWorld';

// without explicitly passing them in the last argument of Given() / When() / Then()
export type BddAutoInjectFixtures = Pick<BddFixtures, '$test' | '$tags' | '$step'> & {
$testInfo: TestInfo;
};

const BDD_AUTO_INJECT_FIXTURES: Record<keyof BddAutoInjectFixtures, null> = {
$tags: null,
$test: null,
$step: null,
$testInfo: null,
};

export function isBddAutoInjectFixture(name: string) {
return Object.prototype.hasOwnProperty.call(
BDD_AUTO_INJECT_FIXTURES,
name as keyof BddAutoInjectFixtures,
);
}

export function getBddAutoInjectsFixtures(bddWorld: BddWorld): BddAutoInjectFixtures {
return {
$testInfo: bddWorld.testInfo,
$tags: bddWorld.tags,
$test: bddWorld.test,
$step: bddWorld.step,
};
}
85 changes: 21 additions & 64 deletions src/run/bddFixtures.ts → src/run/bddFixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,23 @@
import { TestInfo, test as base } from '@playwright/test';
import { loadConfig as loadCucumberConfig } from '../cucumber/loadConfig';
import { loadSteps } from '../cucumber/loadSteps';
import { BddWorld, BddWorldFixtures, getWorldConstructor } from './bddWorld';
import { BDDConfig, extractCucumberConfig } from '../config';
import { getConfigFromEnv } from '../config/env';
import { TestTypeCommon } from '../playwright/types';
import { appendDecoratorSteps } from '../steps/decorators/steps';
import { getPlaywrightConfigDir } from '../config/configDir';
import { runScenarioHooks } from '../hooks/scenario';
import { runWorkerHooks } from '../hooks/worker';
import { IRunConfiguration } from '@cucumber/cucumber/api';
import { StepInvoker } from './StepInvoker';
import { ISupportCodeLibrary } from '../cucumber/types';
import { TestMeta, TestMetaMap, getTestMeta } from '../gen/testMeta';
import { logger } from '../utils/logger';
import { getEnrichReporterData } from '../config/enrichReporterData';
import { BddDataManager } from './bddData';
import { loadStepsOwn } from '../cucumber/loadStepsOwn';
import { test as base } from '@playwright/test';
import { loadConfig as loadCucumberConfig } from '../../cucumber/loadConfig';
import { loadSteps } from '../../cucumber/loadSteps';
import { BddWorldFixtures, getWorldConstructor } from '../bddWorld';
import { extractCucumberConfig } from '../../config';
import { getConfigFromEnv } from '../../config/env';
import { appendDecoratorSteps } from '../../steps/decorators/steps';
import { getPlaywrightConfigDir } from '../../config/configDir';
import { runScenarioHooks } from '../../hooks/scenario';
import { runWorkerHooks } from '../../hooks/worker';
import { StepInvoker } from '../StepInvoker';
import { getTestMeta } from '../../gen/testMeta';
import { logger } from '../../utils/logger';
import { getEnrichReporterData } from '../../config/enrichReporterData';
import { BddDataManager } from '../bddData';
import { BddFixtures, BddFixturesWorker } from './types';
import { loadStepsOwn } from '../../cucumber/loadStepsOwn';

// BDD fixtures prefixed with '$' to avoid collision with user's fixtures.

export type BddFixtures = {
// fixtures injected into BddWorld:
// empty object for pw-style, builtin fixtures for cucumber-style
$bddWorldFixtures: BddWorldFixtures;
$bddWorld: BddWorld;
Given: StepInvoker['invoke'];
When: StepInvoker['invoke'];
Then: StepInvoker['invoke'];
And: StepInvoker['invoke'];
But: StepInvoker['invoke'];
$testMetaMap: TestMetaMap;
$testMeta: TestMeta;
$tags: string[];
$test: TestTypeCommon;
$uri: string;
$scenarioHookFixtures: Record<string, unknown>;
$before: void;
$after: void;
$lang: string;
};

type BddFixturesWorker = {
$cucumber: {
runConfiguration: IRunConfiguration;
supportCodeLibrary: ISupportCodeLibrary;
World: typeof BddWorld;
config: BDDConfig;
};
$workerHookFixtures: Record<string, unknown>;
$beforeAll: void;
$afterAll: void;
};

export const test = base.extend<BddFixtures, BddFixturesWorker>({
// load cucumber once per worker (auto-fixture)
// todo: maybe remove caching in cucumber/loadConfig.ts and cucumber/loadSteps.ts
Expand Down Expand Up @@ -84,7 +49,7 @@ export const test = base.extend<BddFixtures, BddFixturesWorker>({
// init $bddWorldFixtures with empty object, will be owerwritten in test file for cucumber-style
$bddWorldFixtures: ({}, use) => use({} as BddWorldFixtures),
$bddWorld: async (
{ $tags, $test, $bddWorldFixtures, $cucumber, $lang, $testMeta, $uri },
{ $tags, $test, $step, $bddWorldFixtures, $cucumber, $lang, $testMeta, $uri },
use,
testInfo,
) => {
Expand All @@ -94,6 +59,7 @@ export const test = base.extend<BddFixtures, BddFixturesWorker>({
supportCodeLibrary,
$tags,
$test,
$step,
$bddWorldFixtures,
lang: $lang,
parameters: runConfiguration.runtime.worldParameters || {},
Expand Down Expand Up @@ -126,6 +92,8 @@ export const test = base.extend<BddFixtures, BddFixturesWorker>({
// init $test with base test, but it will be overwritten in test file
$test: ({}, use) => use(base),

$step: ({}, use) => use({ title: '' }),

// feature file uri, relative to configDir, will be overwritten in test file
$uri: ({}, use) => use(''),

Expand Down Expand Up @@ -175,14 +143,3 @@ export const test = base.extend<BddFixtures, BddFixturesWorker>({
{ auto: true, scope: 'worker' },
],
});

/** these fixtures automatically injected into every step call */
export type BddAutoInjectFixtures = Pick<BddFixtures, '$test' | '$tags'> & {
$testInfo: TestInfo;
};

const BDD_AUTO_INJECT_FIXTURES: (keyof BddAutoInjectFixtures)[] = ['$testInfo', '$test', '$tags'];

export function isBddAutoInjectFixture(name: string) {
return BDD_AUTO_INJECT_FIXTURES.includes(name as keyof BddAutoInjectFixtures);
}
Loading

0 comments on commit c0aa39e

Please sign in to comment.