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.
Key difference from Vitest
Section titled “Key difference from Vitest”| 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 |
Example: User logs in successfully
Section titled “Example: User logs in successfully”Generated output
Section titled “Generated output”### 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 dashboardPlaywright code
Section titled “Playwright code”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.click('button[type=submit]'); });
story.then('the user sees the dashboard'); await expect(page).toHaveURL(/dashboard/);});Full recipe list
Section titled “Full recipe list”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.