Custom Shared Steps

Shared steps are Gherkin step definitions available to every feature file. Use them when an action or assertion appears in multiple modules and you don't want to duplicate the implementation.

Where shared steps live

e2e-tests/features/playwright-bdd/shared/
├── auth.steps.js         — login/logout actions
├── navigation.steps.js   — page navigation, link clicks
├── common.steps.js       — headings, tabs, storage, page title
└── global-hooks.js       — Before/After hooks, predata loading

Anything in shared/ is automatically available in all features via playwright-bdd's directory scoping.

Writing a new shared step

1. Choose the right file

CategoryFile
Auth actions (login, logout, session)auth.steps.js
Navigation (go to page, click nav link)navigation.steps.js
Common UI assertions (heading, title, tab)common.steps.js
Hooks or cross-feature dataglobal-hooks.js
Completely new categoryCreate a new {category}.steps.js

2. Write the step definition

// e2e-tests/features/playwright-bdd/shared/common.steps.js
import { When, Then } from '../../../playwright/fixtures.js';

Then('the toast message shows {string}', async ({ page }, message) => {
  await expect(
    page.getByRole('alert').filter({ hasText: message })
  ).toBeVisible({ timeout: 5000 });
});

Import rule: Always import Given, When, Then from fixtures.js — never from playwright-bdd or @playwright/test directly. The import path depth depends on where your file is:

File locationImport path
shared/../../playwright/fixtures.js

3. Register it with the AI

Add the step to your knowledge base or a project-specific rules file so the AI knows it exists and uses it instead of generating a duplicate:

<!-- In .claude/rules/shared-steps.md or knowledge-base.md -->

## Shared Steps Available

### common.steps.js
- `Then the toast message shows {string}` — checks role="alert" text

4. Reference it in the plan file

When the bdd-generator agent creates the plan, it lists which shared steps to use. If your step isn't discovered automatically, add it to the instructions string so the agent includes it:

instructions: [
  'After saving, verify the toast message "Record saved successfully"',
]

The agent will match this intent to Then the toast message shows "Record saved successfully".

Step scoping rules

playwright-bdd v8+ automatically scopes steps based on directory structure:

DirectoryScope
shared/All features
@Modules/@LoginPage/steps.jsOnly features tagged @Modules AND @LoginPage
@Workflows/@BookingWorkflow/@0-Precondition/steps.jsOnly that specific phase

Steps defined inside @-prefixed directories are not visible to other modules. If a step is needed in multiple modules, it must live in shared/.

Example: custom navigation step

Your app has a sidebar with module-specific links. Instead of adding navigation logic to every module's steps, add one shared step:

// shared/navigation.steps.js
When('I click the sidebar link {string}', async ({ page }, linkName) => {
  await page.getByRole('navigation')
    .getByRole('link', { name: linkName })
    .click();
  await page.waitForLoadState('networkidle');
});

Now every module can use:

When I click the sidebar link "Reports"

Example: cross-feature predata step

The global-hooks.js file already contains Given I load predata from "{scopeName}". If you need a variant that loads and also asserts count, add it to common.steps.js:

Then('I see {int} items matching the predata criteria', async ({ page, testData }, count) => {
  const items = await page.getByRole('row').count();
  expect(items).toBe(count);
});

Checking for existing steps before adding

Always check the existing shared files before writing a new step — the AI may have already generated something similar. Run a quick search:

grep -r "step pattern" e2e-tests/features/playwright-bdd/shared/

Duplicate step patterns cause playwright-bdd to throw a Duplicate step error at runtime.