DEV Community

Cover image for Stop Debugging Blind: Playwright Screencast API for Richer Test Evidence
Vanessa Sastre
Vanessa Sastre

Posted on

Stop Debugging Blind: Playwright Screencast API for Richer Test Evidence

When an automated test fails, the error message alone is often insufficient to diagnose the root cause.

You're usually left asking:

  • What actions happened before the failure?
  • Which element was being interacted with?
  • Where exactly did the app start behaving differently than expected?
  • Can a teammate understand the failure without re-running the test themselves?

Playwright 1.59 introduced page.screencast, a programmatic recording API that answers these questions by turning a test run into a narrated, annotated video — instead of just a pass/fail line in a report.

With screencast-based reporting, a test can produce:

  • A recording of the full execution
  • Visual annotations on every interacted element
  • Chapter markers that break the run into named stages
  • Custom HTML overlays for context or metadata
  • Real-time JPEG frame capture for external tooling
  • "Video receipts" for AI-driven test and coding agents

The goal is simple: make failures faster to understand, and make passing runs easier to trust.

Screencast example showing annotated Playwright test execution


Why Screencasts Matter

Traditional artifacts already carry useful information:

Artifact What it tells you
Logs What the framework reported
Screenshots The app's state at one moment
Traces DOM snapshots, network activity, console output

A screencast adds a fourth layer that none of these provide well on their own: the full user journey, as a video.

Instead of staring at this:

Expected: "Order completed"
Received: "Payment failed"
Enter fullscreen mode Exit fullscreen mode

...you watch the actual sequence: checkout opens, a product is added, payment details are entered, submit is clicked, and the error appears, all annotated in place.

Screencast vs. Playwright Trace

These two tools solve different problems, and a mature framework should use both.

Trace Viewer is built for deep technical debugging:

  • Network activity
  • DOM snapshots
  • Console messages
  • Step-by-step execution inspection

Screencast is built for fast, visual understanding:

  • A timeline of what a user (or agent) actually did
  • Visible, annotated interactions
  • Context that non-technical stakeholders can follow without opening dev tools

In practice: reach for traces when you need to diagnose why something broke, and for screencasts when you need to show what happened quickly, and to a wider audience than just engineers.


Setting Up Screencast Recording

The cleanest way to roll this out across a suite is a shared fixture, so every test gets recording, annotations, and reporting for free — no boilerplate in individual test files.

// fixtures.ts
import { test as base } from '@playwright/test';

export const test = base.extend({
  page: async ({ page }, use, testInfo) => {
    const videoPath = testInfo.outputPath('screencast.webm');

    await page.screencast.start({ path: videoPath });
    await page.screencast.showActions({ position: 'top-right' });

    await use(page);

    await page.screencast.stop();

    await testInfo.attach('Screencast', {
      path: videoPath,
      contentType: 'video/webm',
    });
  },
});
Enter fullscreen mode Exit fullscreen mode

Every test that imports test from this file now automatically records a screencast and attaches it to the report, including in CI.

Adding Narration Inside a Test

Once the fixture is in place, use showChapter() to label the stages of a test as it runs:

import { test } from './fixtures';

test('checkout flow', async ({ page }) => {
  await page.screencast.showChapter('Login', {
    description: 'User authentication flow',
  });
  await page.goto('/login');
  // ...login steps

  await page.screencast.showChapter('Checkout', {
    description: 'Complete purchase flow',
  });
  // ...checkout steps
});
Enter fullscreen mode Exit fullscreen mode

The resulting video reads like a labeled walkthrough rather than raw, unexplained footage.


Screencast API Reference

page.screencast.start(options)

Starts a screencast session. Pass path to save a WebM recording, or onFrame to receive JPEG frames in real time — both can be used together.

await page.screencast.start({
  path: 'execution.webm',
  size: { width: 1280, height: 720 },
  quality: 90,
});
Enter fullscreen mode Exit fullscreen mode

Because it's called explicitly, you control exactly when recording starts and stops, unlike the older, always-on video config option, which records for the entire test lifecycle regardless of what you actually need.

page.screencast.stop()

Stops the active session and finalizes the video file at the path given to start(). Typically called right after the test body finishes:

await use(page);
await page.screencast.stop();
Enter fullscreen mode Exit fullscreen mode

page.screencast.showActions(options)

Annotates interacted elements as they're clicked, filled, or hovered, so a reviewer can see exactly which element was touched and when.

await page.screencast.showActions({
  position: 'top-right',
  duration: 1500,   // ms each annotation stays visible
  fontSize: 16,
  cursor: 'pointer', // animates a cursor between actions; use 'none' to disable
});
Enter fullscreen mode Exit fullscreen mode

Valid position values: top-left, top, top-right, bottom-left, bottom, bottom-right.

To turn annotations off mid-test:

await page.screencast.hideActions();
Enter fullscreen mode Exit fullscreen mode

Suite-wide alternative: if you want action annotations everywhere without touching a fixture, enable them directly in playwright.config.ts:

export default defineConfig({
  use: {
    video: {
      mode: 'on',
      show: {
        actions: { position: 'top-left' },
        test: { position: 'top-right' },
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

page.screencast.showChapter(title, options)

Displays a centered, blurred-backdrop overlay to mark a new stage of the recording. Disappears automatically after duration (defaults to a couple of seconds).

await page.screencast.showChapter('Payment validation', {
  description: 'Submitting card details and confirming charge',
  duration: 4000,
});
Enter fullscreen mode Exit fullscreen mode

Chaining a few of these gives you a scannable timeline:

Login → Search product → Add to cart → Checkout → Payment validation
Enter fullscreen mode Exit fullscreen mode

Reviewers can jump straight to the stage they care about instead of scrubbing a long recording blindly.

page.screencast.showOverlay(html, options) / showOverlays() / hideOverlays()

Adds arbitrary HTML on top of the recording — handy for environment info, build numbers, or debug messages.

await page.screencast.showOverlay(
  '<div style="color:red">Running checkout validation</div>',
  { duration: 3000 }
);
Enter fullscreen mode Exit fullscreen mode

Omit duration and the overlay stays until you explicitly hide or remove it. showOverlays() / hideOverlays() toggle visibility of overlays already on screen without discarding them.

Real-Time Frame Capture

Pass onFrame to start() to stream JPEG-encoded frames while the test runs, instead of (or alongside) writing a video file:

await page.screencast.start({
  onFrame: ({ data, timestamp, viewportWidth, viewportHeight }) => {
    processFrame({ data, timestamp, viewportWidth, viewportHeight });
  },
});
Enter fullscreen mode Exit fullscreen mode

This opens the door to live dashboards, vision-model analysis, or custom monitoring pipelines that react to what's on screen during execution, not after.


Agentic Video Receipts

Screencasts are also a good fit for AI-driven testing and coding agents, where a plain text summary ("verified checkout — passed") is much less convincing than a video showing exactly what the agent did.

await page.screencast.start({ path: 'receipt.webm' });
await page.screencast.showActions({ position: 'top-right' });

await page.screencast.showChapter('Verifying checkout flow', {
  description: 'Validating payment completion',
});

// Agent performs its verification steps here
await page.locator('#coupon').fill('SAVE20');
await page.locator('#apply-coupon').click();
await expect(page.locator('.discount')).toContainText('20%');

await page.screencast.showChapter('Done', {
  description: 'Checkout validation completed',
});
await page.screencast.stop();
Enter fullscreen mode Exit fullscreen mode

The output is a self-contained "receipt": chapter titles explain intent, action annotations show exactly what was clicked or filled, and a human can review the whole thing in the time it takes to watch a short clip, no re-running the agent required.


CI/CD Recommendations

Recording every single test run adds up fast in storage and CI time. A few practical guardrails:

  • Record failures, not everything. Attach the screencast only when a test doesn't match its expected status:
  if (testInfo.status !== testInfo.expectedStatus) {
    await testInfo.attach('Screencast', {
      path: videoPath,
      contentType: 'video/webm',
    });
  }
Enter fullscreen mode Exit fullscreen mode
  • Record critical paths deliberately. Reserve always-on recording for your highest-value flows (checkout, sign-up, payment) rather than every spec.
  • Clean up old artifacts on a schedule so recordings don't silently pile up in CI storage.

Official Resources

This post covers the common patterns, but the official docs are the source of truth for full option lists, edge cases, and future updates:

  • Screencast API reference — full method and options list
  • Playwright release notes — see the v1.59 entry for the original page.screencast announcement, and later releases for additions like the cursor option
  • Playwright documentation — general guides, including video/trace configuration If you hit something this post doesn't cover, those are the pages to check first.

Final Thoughts

Test automation is moving past a simple pass/fail line. Screencast-based reporting gives teams:

  • Faster failure diagnosis
  • A visual record of execution flow anyone can follow, technical or not
  • Better handoffs between QA and development
  • A foundation for reviewing AI-driven testing and coding agents

A mature framework shouldn't just report that a test failed, it should show what happened, where it happened, and give you what you need to fix it.

Top comments (0)