Skip to content

Recipes (Playwright)

Every Vitest recipe has a Playwright equivalent in apps/playwright-example. The generated output is identical across frameworks — only the test code differs.

You use native test.describe / test and the story object: call story.init(testInfo) at the start of each test (pass testInfo from the callback), then story.given, story.when, story.then. Your test still receives fixtures (e.g. { page }) for browser actions.

VitestPlaywright
Initstory.init(task)story.init(testInfo)
Importimport { story } from 'executable-stories-vitest'import { story } from 'executable-stories-playwright'
Test structuredescribe / ittest.describe / test
Fixtures{ task }{ page }, { context }, { browser }, testInfo
Top-level helpersnot exportedgiven, when, then, and, but exported
Failure modifierit.failstest.fail
### User logs in successfully
- **Given** the user account exists
- **And** the user is on the login page
- **And** the account is active
- **When** the user submits valid credentials
- **Then** the user should see the dashboard
import { expect, test } from '@playwright/test';
import { story } from 'executable-stories-playwright';
test.describe('Login', () => {
test('User logs in successfully', async ({ page }, testInfo) => {
story.init(testInfo);
story.given('the user account exists');
story.given('the user is on the login page');
story.given('the account is active');
story.when('the user submits valid credentials');
story.then('the user should see the dashboard');
await expect(page).toHaveURL(/dashboard/);
});
});

Example: Login blocked for suspended user (with But)

Section titled “Example: Login blocked for suspended user (with But)”
import { test } from '@playwright/test';
import { story } from 'executable-stories-playwright';
test.describe('Login', () => {
test('Login blocked for suspended user', async ({ page }, testInfo) => {
story.init(testInfo);
story.given('the user account exists');
story.given('the account is suspended');
story.when('the user submits valid credentials');
story.then('the user should see an error message');
story.but('the user should not be logged in');
});
});

Example: API accepts JSON payload (with doc.json)

Section titled “Example: API accepts JSON payload (with doc.json)”
import { story } from 'executable-stories-playwright';
import { test } from '@playwright/test';
test.describe('API', () => {
test('API accepts a JSON payload', async ({}, testInfo) => {
story.init(testInfo);
story.given('the client has the following JSON payload');
story.json({
label: 'Payload',
value: {
password: 'secret',
rememberMe: true,
},
});
story.when('the client sends the request');
story.then('the response status should be 200');
story.and('the response body should include "token"');
});
});

Using Playwright fixtures in step callbacks

Section titled “Using Playwright fixtures in step callbacks”

If you want step callbacks to receive Playwright fixtures, pass them as the first argument:

test('user interacts with page', async ({ page }, testInfo) => {
story.init({ page }, testInfo);
await story.given('the user is on the login page', async ({ page }) => {
await page.goto('/login');
});
await story.when('the user submits credentials', async ({ page }) => {
await page.fill('[name=email]', '[email protected]');
await page.click('button[type=submit]');
});
story.then('the user sees the dashboard');
await expect(page).toHaveURL(/dashboard/);
});

The same 32 scenarios as Vitest recipes are in apps/playwright-example/src/replicate.story.spec.ts. Generated docs: apps/playwright-example/src/replicate.docs.md.

ScenarioPatternSee Vitest recipe
User logs in successfullyMultiple Given, single When, single ThenLink
User updates profile detailsSingle Given, multiple When, single ThenLink
Checkout calculates totalsSingle Given, single When, multiple ThenLink
Password reset flowMultiple Given/When/ThenLink
Login blocked for suspended userUse of ButLink
Login works (tags)Story tagsLink
Login errors (outline)Scenario outline with loopLink
Many login attempts (outline)Scenario outline, multiple outcomesLink
Bulk user creationdoc.tableLink
Create users from tableScenario outline with doc.tableLink
Calculate shipping optionsDataTable, multiple ThenLink
Shipping eligibilityScenario outline by countryLink
Tax calculation by regionScenario outline with multiple rowsLink
API accepts JSON payloaddoc.json (DocString)Link
Post JSON payload (outline)Scenario outline with doc.jsonLink
Import XML invoicedoc.code (XML)Link
Import users + welcome emaildoc.table + doc.codeLink
Render markdowndoc.code (markdown)Link
Change email addressShared backgroundLink
Change passwordShared background, different When/ThenLink
Eligible customer gets discountRule block, positive pathLink
Ineligible customer no discountRule block, negative pathLink
Two step checkoutMultiple When groupsLink
Payment declinedNegative path with ButLink
Guest checkout alloweddoc.note for ButLink
Logout clears sessionRepeated Then stepsLink
Document status changesExplicit state transitionLink
Update preferencesDataTable as key-value pairsLink
Configure feature flagsComplex DataTableLink
Create orderBackground and tagsLink
Search results show highlightsAnd after ThenLink
Report shows fields in orderAnd in middle of ThenLink

Playwright story & doc API — steps, fixtures, and doc usage.