Step Helpers

narrate

Attach voiceover narration text to a Playwright test step.

What it does

narrate(text) writes a marker-prefixed test.step() directly into the Playwright trace zip and attaches a voiceover annotation to testInfo. During video generation, subtitlesFromTrace() recovers each narration span from the recorded trace step — no separate report file or pipeline call is required.

Each narrate span starts at the helper's call site and runs until the next narrate() call (or the end of the trace). This is what subtitlesFromTrace turns into a SubtitleEntry.

Usage

import { narrate } from 'playwright-recast'

// With narration text
await narrate('The user opens the dashboard to review analytics.')

// Pad the test with the estimated time it takes to speak the line —
// useful when running without TTS so the video has visual time for it.
await narrate('Welcome to the new dashboard.', { autoWait: true })

// No-op — pass undefined when a step has no narration.
await narrate(undefined)

Parameters

ParameterTypeDescription
textstring | undefinedVoiceover text for this step. Pass undefined to no-op.
opts.hiddenbooleanMark the step as hidden (excluded from subtitles). Detected automatically if text contains @hidden.
opts.autoWaitboolean | number | objectPause after recording the marker step for the approximate time the line takes to speak. When omitted, the global default from setupRecast({ narrateAutoWait }) applies. See below.

autoWait — visual time without TTS

When running without TTS (e.g., live demos or local previews), the video needs enough visual time for each line. autoWait waits for an estimate based on text length:

With TTS you usually don't need autoWait. The voiceover() stage already stretches the video to fit each line — see Voiceover-driven freezes and waitForNarration(). Reach for autoWait mainly when rendering without TTS, where there is no audio to size the pause.

FormBehavior
trueEstimate from non-whitespace characters / NARRATE_DEFAULT_CPS (14 ch/s ≈ 150 wpm).
numberWait exactly this many milliseconds.
{ charactersPerSecond, minMs, maxMs }Tune the estimate.
await narrate('Welcome to the new dashboard.', {
  autoWait: { charactersPerSecond: 16, minMs: 1500, maxMs: 6000 },
})

Setting a global default

To pad every line without repeating autoWait on each call, set it once via setupRecast({ narrateAutoWait }). Any narrate() that omits autoWait falls back to that default; a per-call autoWait still overrides it, and autoWait: false opts a single call out of the wait entirely.

Voiceover-driven freezes

When the pipeline runs voiceover() and the synthesized audio for a narration is longer than the visual window between this narrate and the next, the renderer holds the current frame until the audio finishes. The frozen frame includes any active highlight/zoom overlay, and clickEffect sounds shift along with the timeline. You get audio-perfect sync without having to hand-tune pace() calls.

To force a hold at a deliberate point — e.g. before a click that must not be talked over, or at the end of a scenario — call waitForNarration(). It ends the preceding narration's window there so the renderer holds until that line is fully spoken.

Hiding steps

Steps that should not appear in the final video (e.g., login, setup) can be marked as hidden:

narrate(undefined, { hidden: true })

You can also embed @hidden in the doc string text itself:

Given the admin is logged in
  """
  @hidden
  """

Hidden steps are excluded from the SRT output and are not recorded in the demo video.

Example with playwright-bdd

import { Given, When, Then } from './fixtures'
import { narrate, pace } from 'playwright-recast'

Given('the user opens the dashboard', async ({ page }, docString?: string) => {
  await narrate(docString)
  await page.goto('/dashboard')
  await pace(page, 4000)
})

When('the user clicks the revenue chart', async ({ page }, docString?: string) => {
  await narrate(docString)
  await page.click('.revenue-chart')
})
Feature: Dashboard demo

  Scenario: View analytics
    Given the user opens the dashboard
      """
      Let's open the analytics dashboard to see real-time metrics.
      """
    When the user clicks the revenue chart
      """
      Clicking on the revenue chart reveals a detailed breakdown.
      """

The doc string text becomes the voiceover narration and subtitle for each step in the generated video.

On this page