Skip to content

Converting existing Playwright tests

This guide teaches executable-stories patterns one concept at a time. We start from plain Playwright tests and add story structure and generated report output without throwing away existing tests.

Your existing Playwright tests might look like this:

import { expect, test } from '@playwright/test';
import { add, subtract } from './calculator.js';
test('addition works', async () => {
expect(add(2, 3)).toBe(5);
});
test('subtraction works', async () => {
expect(subtract(10, 4)).toBe(6);
});

Why this is the baseline: Tests pass and give confidence, but no user-story output is generated and stakeholders don’t see readable Given/When/Then. The next steps add both.

Keep your native test.describe / test. At the start of each test that should appear in the report, call story.init(testInfo) (pass testInfo from the test callback). Then use story.given, story.when, story.then to mark steps. The scenario title in Markdown output is the test name. Your test still receives Playwright fixtures (e.g. { page }) for browser actions.

import { expect, test } from '@playwright/test';
import { story } from 'executable-stories-playwright';
import { add } from './calculator.js';
test.describe('Calculator', () => {
test('Calculator adds two numbers', async ({}, testInfo) => {
story.init(testInfo);
story.given('two numbers 2 and 3');
const a = 2,
b = 3;
story.when('they are added');
const result = add(a, b);
story.then('the result is 5');
expect(result).toBe(5);
});
});

Note: Playwright’s test callback is async ({ page }, testInfo) => { ... } — the second argument is testInfo. After running playwright test, the reporter writes Markdown with the scenario title and Given/When/Then.

You can add story.init(testInfo) to any existing test so it appears in the story report even without step markers. The scenario will show the test name and no steps.

import { expect, test } from '@playwright/test';
import { story } from 'executable-stories-playwright';
import { subtract } from './calculator.js';
test('Calculator subtracts two numbers', async ({}, testInfo) => {
story.init(testInfo);
expect(subtract(10, 4)).toBe(6);
});

Why this helps: No need to add given/when/then to every test. The test still runs as one Playwright test; the reporter adds it to the story report with the scenario title from the test name.

Use story.note, story.json, story.table, etc. after steps for rich details in Markdown output. In E2E tests, use fixtures in the test body, or initialize with story.init({ page }, testInfo) when step callbacks need them.

import { expect, test } from '@playwright/test';
import { story } from 'executable-stories-playwright';
import { add, multiply } from './calculator.js';
test.describe('Calculator', () => {
test('Calculator multiplies two numbers', async ({}, testInfo) => {
story.init(testInfo);
story.given('two numbers 7 and 6');
story.when('they are multiplied');
const result = multiply(7, 6);
story.then('the result is 42');
expect(result).toBe(42);
});
test('Calculator adds with a note', async ({}, testInfo) => {
story.init(testInfo);
story.given('two numbers 1 and 2');
story.note(
'Using small numbers; the note appears in the Markdown output.',
);
story.when('they are added');
story.then('the result is 3');
expect(add(1, 2)).toBe(3);
});
});

The full refactor guide lives in the example app. Run it and open the generated output: