<!-- https://stagewright.ai/guides/playwright-walkthrough-videos -->

**Guide**

# Record product walkthrough videos from your Playwright tests.

Your Playwright tests already click through the flows customers are taught. This guide turns them into narrated walkthrough videos without moving your tests, then records them on every pull request with a check that says what changed.

Checked against `stagewright` and `@stagewright-ai/playwright` on 2026-10-11. The examples use a sample app, Acme CRM. Commands use `npx stagewright@latest`; run `npx stagewright <command> --help` for every option.

## What Playwright records on its own

Playwright can already record a video of each test:

```
// playwright.config.ts
export default defineConfig({
  use: { video: "on" }, // or "retain-on-failure", "on-first-retry"
});
```

Each test gets a `.webm` in `test-results/`. That’s built for debugging a failure: it plays at test speed, has no visible cursor, no audio and no narration, and nothing tells you whether the flow still matches what your help center teaches. For a quick look at what a test did, it’s enough.

A walkthrough someone can follow needs more: actions paced so a person can see them, a cursor, a screenshot per step, narration, and a check that it still works after the next release. The rest of this guide adds those, in three parts: record a test as it runs, import a test as a walkthrough file, then run it on every pull request.

## Before you start

-   Node.js 22.18 or newer, and `@playwright/test` 1.50 or newer.
-   ffmpeg on your `PATH` to render video. `npx stagewright@latest doctor` checks the machine; `--install` adds Chromium.
-   No account for parts 1 and 2. Uploading runs, frames on the PR and publishing need a Stagewright API key, which is in private beta.

## Your tests stay where they are

Stagewright never asks you to move, rename or merge tests. Tests are written to check behaviour; walkthroughs are written to show it. You choose, per test:

|  | Record in place (the fixture) | Import as a walkthrough file |
| --- | --- | --- |
| What changes in your repo | The test’s import, its steps and a `@walkthrough` tag | A new file in `.walkthroughs/`; the spec is untouched |
| Source of truth | The spec | The walkthrough YAML |
| Who edits it | Developers, in code | Anyone, in the file or the dashboard |
| Narration | Written later, in Stagewright | Written in the file |
| Checked on every PR by `stagewright ci` | No | Yes |
| Best for | A test that’s already a good story | Flows you publish as video and help articles |

## 1\. Record a test as it runs

Install the fixture:

```
npm install -D @stagewright-ai/playwright
```

Import `test` and `expect` from it instead of `@playwright/test`, wrap the parts of the test that a viewer should see as steps, and tag the test:

```
// tests/contacts.spec.ts
import { test, expect } from "@stagewright-ai/playwright";

test("add a contact @walkthrough", async ({ page, walkthrough }) => {
  await walkthrough.step("Open Contacts", async () => {
    await page.goto("/contacts");
    await page.getByRole("button", { name: "New contact" }).click();
  });
  await walkthrough.step("Fill in the details", async () => {
    await page.getByLabel("Name").fill("Ada Lovelace");
    await page.getByLabel("Email").fill("ada@example.com");
  });
  await walkthrough.step("Save", async () => {
    await page.getByRole("button", { name: "Save" }).click();
    await expect(page.getByRole("heading", { name: "Ada Lovelace" })).toBeVisible();
  });
});
```

Normal runs are unchanged: `walkthrough.step` is `test.step` and nothing else is installed. To record, set `STAGEWRIGHT=1`:

```
STAGEWRIGHT=1 npx playwright test --grep @walkthrough
```

With it, the fixture:

-   draws a cursor and records video for every test (it overrides `use.video`);
-   paces locator actions (`click`, `fill`, `check`, `selectOption`, `press` and the rest): the cursor glides to the element and waits, and `fill` is typed character by character;
-   ends each step with a short pause and a screenshot, and writes `timeline.json` and `screenshots/<step>.png` (plus `<step>.failure.png` when a step fails) under the test’s output folder, in `test-results/…/stagewright/`;
-   multiplies the test timeout by 3.

Pacing is tunable under `use.stagewright` in `playwright.config.ts`: `actionDelayMs` (400), `settleMs` (700), `typingDelayMs` (45), `typeFills` (true), `maxTypedLength` (60), `glide` (true), `glideTimeoutMs` (2000), `timeoutMultiplier` (3).

Know the limits before you rely on it. Pacing is new. Expect roughly half a second more per action. Expectations and your own waits are never slowed, but `page.click(selector)`\-style calls and raw `page.mouse` or `page.keyboard` input aren’t paced. Typing instead of filling can set off debounced or autocomplete handlers (turn it off with `typeFills: false`), and a pause can let a short-lived toast disappear before a later assertion sees it.

### Upload the recordings with the reporter

To keep the recordings in Stagewright, add the reporter:

```
// playwright.config.ts
import { defineConfig } from "@playwright/test";
import type { StagewrightOptions } from "@stagewright-ai/playwright";

export default defineConfig<StagewrightOptions>({
  use: { baseURL: "http://localhost:3000", stagewright: { actionDelayMs: 600 } },
  reporter: [["list"], ["@stagewright-ai/playwright/reporter", { product: "acme-crm" }]],
});
```

```
STAGEWRIGHT=1 STAGEWRIGHT_API_KEY=ak_... npx playwright test --grep @walkthrough
```

The reporter does nothing unless `STAGEWRIGHT=1`. When it’s on, it uploads every recorded test as a run, with its video, screenshots and timeline, and the commit and branch when it runs in GitHub Actions, GitLab CI or Buildkite. Tests tagged `@walkthrough` are also synced as walkthroughs: `Contacts › add a contact @walkthrough` becomes `contacts-add-a-contact`. Their narration starts as TODO for you to write in Stagewright, and the render fits it to the recorded steps.

One thing to know: in this mode the test performs the actions, so the synced walkthrough is a record of the test, not something Stagewright can run on its own. For a check on every pull request, import the test as a walkthrough file.

## 2\. Import a test as a walkthrough file

Set the app’s address once, then convert the spec. The spec stays exactly where it is:

```
npx stagewright@latest init --base-url http://localhost:3000
npx stagewright@latest import playwright tests/contacts.spec.ts --out .walkthroughs/
```

For the same test written as a plain Playwright test, without the fixture or `test.step()`, this is the converter’s real output:

```
# Converted from tests/contacts.spec.ts › Contacts › add a contact by `stagewright import playwright` (best effort).
# Needs a person:
#   - narration: every step has a TODO placeholder; write what a customer should hear.
#   - step grouping: steps follow test.step() calls, otherwise they're split at page.goto, after
#     expectations and every few actions; merge or split them so each step is one idea.
id: contacts-add-a-contact
title: "add a contact"
steps:
  - id: open-contacts
    narration: "TODO: say what this step shows and why"
    actions:
      - goto: "/contacts"
      - click: 'role=button[name="New contact"]'
      - fill: { target: 'internal:label="Name"i', value: "Ada Lovelace" }
      - fill: { target: 'internal:label="Email"i', value: "ada@example.com" }
  - id: click-save
    narration: "TODO: say what this step shows and why"
    actions:
      - click: 'role=button[name="Save"]'
    expect:
      - visible: 'role=heading[name="Ada Lovelace"]'
```

The converter reads `test()`, `test.describe()`, `test.beforeEach()` (as a hidden setup step) and `test.step()`. It follows locator variables and converts `getByRole`, `getByText`, `getByLabel`, `getByPlaceholder`, `getByTestId` and CSS locators; `goto`, `click`, `fill`, `press`, `check`, `selectOption`, `hover` and `dragTo`; and `toBeVisible`, `toBeHidden`, `toHaveText`, `toContainText` and `toHaveURL`. Anything else becomes a `# TODO: couldn't convert` line. Without `test.step()`, it starts a new step at each `goto`, after expectations and every 4 actions (`--max-actions`). `--grep` picks tests by title. It also accepts `npx playwright codegen` output.

What it can’t do is write what a customer should hear. Write the narration, give the walkthrough a customer-facing title, split steps so each is one idea, and name the feature and the source files it covers, so pull requests that touch them re-record it:

```
# .walkthroughs/contacts-add-a-contact.yaml
id: contacts-add-a-contact
title: "Add a contact"
features: [contacts]
anchors: [src/contacts/**]
steps:
  - id: open-contacts
    narration: Open Contacts and start a new contact.
    actions:
      - goto: "/contacts"
      - click: 'role=button[name="New contact"]'
  - id: fill-in-details
    narration: Type their name and email.
    actions:
      - fill: { target: 'internal:label="Name"i', value: "Ada Lovelace" }
      - fill: { target: 'internal:label="Email"i', value: "ada@example.com" }
  - id: click-save
    narration: Save, and the contact's page opens.
    actions:
      - click: 'role=button[name="Save"]'
    expect:
      - visible: 'role=heading[name="Ada Lovelace"]'
```

Then check it and record it on your machine:

```
npx stagewright@latest validate .walkthroughs/contacts-add-a-contact.yaml
npx stagewright@latest record .walkthroughs/contacts-add-a-contact.yaml
```

`record` renders `.stagewright/out/contacts-add-a-contact/final.mp4`, with a screenshot per step and a timeline. The voice is a local OpenAI-compatible speech server if one answers (for example Kokoro on port 8880), ElevenLabs with `--voice` and an `ELEVENLABS_API_KEY`, or captions only. Signed-in flows: if your suite already saves a signed-in `storageState` in a setup project, point `storageState` in `stagewright.config` at the same file, or run `npx stagewright@latest auth` to sign in once by hand.

The imported file doesn’t follow later edits to the spec. When the test changes in a way the walkthrough should too, import it again and keep your narration. A notice on the pull request when a source spec changes is planned, not built.

## 3\. Run it on every pull request

`stagewright ci` syncs the walkthrough files, records the ones the pull request can affect (from the changed files and each walkthrough’s `features` and `anchors`), and posts a **Walkthroughs** check and one sticky comment on the PR. It exits 1 only when a walkthrough fails the same way twice (a regression, or a walkthrough the change broke); changed, new and flaky results never block.

```
# .github/workflows/walkthroughs.yml
name: Walkthroughs
on: pull_request
permissions:
  contents: read
  pull-requests: write
  checks: write
  deployments: write # the Stagewright Preview deployment
jobs:
  walkthroughs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 } # to see what the PR changed
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: npx playwright install --with-deps chromium && sudo apt-get install -y ffmpeg
      # Start the app here, or use your preview deployment's URL instead.
      - run: npm run build && (npm start &) && npx wait-on http://localhost:3000
      - run: npx stagewright@latest ci --base-url http://localhost:3000
        env:
          STAGEWRIGHT_API_KEY: ${{ secrets.STAGEWRIGHT_API_KEY }}
          GITHUB_TOKEN: ${{ github.token }}
```

With an API key, the comment shows each affected walkthrough’s verdict with before and after frames and clips of the steps that changed. An intended change is approved in Stagewright or with `stagewright approve`, and becomes the new baseline. A regression is filed in Linear, Jira or GitHub Issues with the clip and steps to reproduce.

Without an API key, `stagewright ci` still records the affected walkthroughs and posts the comment and check, with pass, regression or flaky and no frames, baselines or video. Nothing is sent to Stagewright.

| Exit code | Meaning |
| --- | --- |
| 0 | Clean, or only results that don’t block: changed, new, flaky |
| 1 | A regression, or a walkthrough this change broke, that failed the same way on the automatic second run |
| 2 | A usage or configuration error |
| 4 | A run couldn’t complete; the check is neutral |

GitLab, Azure DevOps and Bitbucket work the same way: see [every pull request, in any CI](https://stagewright.ai/developers#ci). Keep your existing Playwright job as it is. If you also use the fixture, a `STAGEWRIGHT=1` run of the tagged tests in CI uploads those recordings with the commit and branch; the PR check comes from `stagewright ci`.

## Let your coding agent do it

These are the product’s own prompts. The first gets a coding agent to set up the CLI and record a first walkthrough on your machine; once it’s done, ask it to import the Playwright specs you choose and write their narration. The second gives every teammate’s agent the Stagewright skills and MCP server.

\[Illustration\] Copy prompt “Set up Stagewright and record a first walkthrough”. Hand this prompt to your coding agent to install the Stagewright CLI in your repo, prove how the app starts and signs in, and record a first walkthrough on your machine. Get the full text with: npx stagewright@latest prompt setup

\[Illustration\] Copy prompt “Give your coding agent Stagewright”. Hand this prompt to your coding agent to add the Stagewright skills and MCP server to this repo, so every teammate's agent can write and fix walkthroughs. Get the full text with: npx stagewright@latest prompt agent-setup

## FAQ

**Will this slow down my test suite?** Only runs with `STAGEWRIGHT=1` are paced, and `--grep @walkthrough` limits them to tagged tests. Your normal CI run doesn’t change.

**Can Playwright record video with audio?** Its videos have no audio track. Stagewright adds narration when it renders the walkthrough, from the narration you wrote.

**Do I need the fixture to import a test?** No. `import playwright` reads plain `@playwright/test` specs, and specs that use the fixture.

**What about Cypress or Selenium?** Not yet. Playwright comes first.

Why walkthroughs belong in the repo at all: [Docs as code, for product walkthroughs](https://stagewright.ai/docs-as-code). How the verdict works: [Method](https://stagewright.ai/method).

## Try it on your own product

Record a first walkthrough on your machine with no account. PR checks, approvals and publishing are in private beta: join the waitlist and we'll set you up by hand.

npx stagewright init

[Join the waitlist](https://app.stagewright.ai/?waitlist)[Developer docs](https://stagewright.ai/developers)
