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.

Vitest Playwright
Init story.init(task) story.init(testInfo)
Import import { story } from 'executable-stories-vitest' import { story } from 'executable-stories-playwright'
Test structure describe / it test.describe / test
Fixtures { task } { page }, { context }, { browser }, testInfo
Top-level helpers not exported given, when, then, and, but exported
Failure modifier it.fails test.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.

Scenario Pattern See Vitest recipe
User logs in successfully Multiple Given, single When, single Then Link
User updates profile details Single Given, multiple When, single Then Link
Checkout calculates totals Single Given, single When, multiple Then Link
Password reset flow Multiple Given/When/Then Link
Login blocked for suspended user Use of But Link
Login works (tags) Story tags Link
Login errors (outline) Scenario outline with loop Link
Many login attempts (outline) Scenario outline, multiple outcomes Link
Bulk user creation doc.table Link
Create users from table Scenario outline with doc.table Link
Calculate shipping options DataTable, multiple Then Link
Shipping eligibility Scenario outline by country Link
Tax calculation by region Scenario outline with multiple rows Link
API accepts JSON payload doc.json (DocString) Link
Post JSON payload (outline) Scenario outline with doc.json Link
Import XML invoice doc.code (XML) Link
Import users + welcome email doc.table + doc.code Link
Render markdown doc.code (markdown) Link
Change email address Shared background Link
Change password Shared background, different When/Then Link
Eligible customer gets discount Rule block, positive path Link
Ineligible customer no discount Rule block, negative path Link
Two step checkout Multiple When groups Link
Payment declined Negative path with But Link
Guest checkout allowed doc.note for But Link
Logout clears session Repeated Then steps Link
Document status changes Explicit state transition Link
Update preferences DataTable as key-value pairs Link
Configure feature flags Complex DataTable Link
Create order Background and tags Link
Search results show highlights And after Then Link
Report shows fields in order And in middle of Then Link

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