How to Record Screencasts in ego-browser Using `page.screencast.start()`

The ego-browser library exposes a Playwright-style page facade that provides page.screencast.start() to capture the viewport as a silent VP8 WebM video, returning a disposable object that automatically stops recording when disposed.

ego-browser, part of the citrolabs/ego-lite repository, is a lightweight browser automation library that wraps Chrome DevTools Protocol (CDP) interactions. Unlike standalone recording tools, it integrates screencast recording directly into the page execution context, allowing you to capture user journeys as video files without external dependencies.

Understanding the Screencast API Architecture

The screencast functionality is implemented across two primary modules that bridge the CDP with a convenient JavaScript API.

Core Implementation in src/driver/screencast.ts

The low-level recording logic lives in src/driver/screencast.ts. This module exports two primary functions:

  • startScreencast(options): Validates the output path, creates a VideoRecorder instance, subscribes to Page.screencastFrame events, and sends the Page.startScreencast CDP command.
  • stopScreencast(): Signals the end of recording via Page.stopScreencast, fetches a final screenshot if no frames were captured, and finalizes the WebM container.

Page Facade Binding in src/helpers.ts

These core functions are exposed to script authors through the page object in src/helpers.ts:

export const helpers = {
  screencast: {
    start: startScreencast,   // → page.screencast.start()
    stop: stopScreencast,      // → page.screencast.stop()
  },
};

This binding mirrors Playwright's API conventions, making page.screencast.start() and page.screencast.stop() available on every page instance.

Using page.screencast.start()

The page.screencast.start() method accepts an options object and returns a disposable handle that manages the recording lifecycle.

Method Signature and Parameters

await page.screencast.start({
  path: string;        // Required: Output file path, must end with .webm
  size?: {             // Optional: Viewport dimensions
    width: number;     // Min 2px, rounded to even
    height: number;    // Min 2px, rounded to even
  };
  quality?: number;    // Optional: JPEG compression (0-100), defaults to 90
});

Validation rules enforced by the API:

  • Path requirement: The path argument must end with .webm. Providing a different extension throws the error: page.screencast.start path must end with .webm.
  • Quality constraints: Must be an integer between 0 and 100. Higher values preserve more detail but increase file size.
  • Size defaults: If omitted, the viewport scales to a maximum of 800 pixels on the longest side while maintaining aspect ratio. Custom sizes must be at least 2 pixels in each dimension and are rounded to even numbers.

The Disposable Return Value

The method returns an object implementing the AsyncDisposable pattern:

{
  dispose: () => Promise<void>;
  [Symbol.asyncDispose]: () => Promise<void>;
}

Calling await recording.dispose() triggers stopScreencast() internally, ensuring the WebM file is properly finalized even if your script throws an exception.

Practical Code Examples

Basic Screencast Recording

Record the full viewport and save to a WebM file:

await page.goto('https://example.com');

const recording = await page.screencast.start({
  path: 'output.webm'
});

// Perform actions while recording
await page.click('button.submit');
await page.waitForLoadState('networkidle');

// Stop and save
await recording.dispose();

Recording with Custom Dimensions and Quality

Capture a specific resolution with reduced quality for smaller file sizes:

await page.screencast.start({
  path: 'small.webm',
  size: { width: 640, height: 360 },
  quality: 70
});

Using Async Disposal Patterns

Leverage explicit resource management for automatic cleanup:

await (async () => {
  const recording = await page.screencast.start({ path: 'demo.webm' });
  
  await page.goto('https://example.com');
  await page.click('a.expand');
  
  // Recording automatically stops when the block exits
})();

Key Behaviors and Constraints

Understanding the runtime characteristics prevents common errors:

  • Single concurrent recording: Only one screencast can run per CDP session. Attempting to call page.screencast.start() while another recording is active throws: Screencast is already started.
  • VP8 WebM output: The VideoRecorder class in src/video-recorder.ts uses FFmpeg to encode frames as a silent VP8 video stream.
  • Frame capture: The subscription to Page.screencastFrame events captures JPEG screenshots at the configured quality level, which are then piped to the video encoder in real-time.
  • Finalization: If no frames were received during the recording session, stopScreencast() fetches a final screenshot to ensure the video file contains at least one frame.

Summary

  • page.screencast.start() is exposed via the page facade in src/helpers.ts and implemented in src/driver/screencast.ts.
  • The API requires a .webm output path and supports optional size and quality parameters.
  • It returns a disposable object that ensures Page.stopScreencast is called and the video file is finalized when disposed.
  • Only one recording can be active per page instance; subsequent calls trigger an error.
  • Recordings are captured through CDP's Page.screencastFrame events and encoded as VP8 WebM videos.

Frequently Asked Questions

What video format does ego-browser use for screencasts?

ego-browser records screencasts as VP8-encoded WebM videos without audio. The VideoRecorder class handles the containerization in src/video-recorder.ts, ensuring compatibility with modern browsers and video players.

Can I record multiple pages simultaneously?

No. The CDP session allows only one active screencast per page instance. If you need to record multiple browser contexts simultaneously, you must create separate page instances and manage their recordings independently, as each page.screencast.start() call validates that no other recording is currently active on that specific page.

Why does my script throw a path validation error?

The screencast API strictly requires the output path to end with the .webm extension. If you provide paths like output.mp4 or recording, the startScreencast function in src/driver/screencast.ts throws: page.screencast.start path must end with .webm. Always ensure your filename includes the correct extension before calling the method.

How do I ensure the recording stops if my script crashes?

The disposable object returned by page.screencast.start() implements [Symbol.asyncDispose], allowing you to use it within async blocks or explicitly call await recording.dispose(). If you're using Node.js 14+ with --experimental-abort-controller or modern async disposal patterns, the recording finalizes automatically when the scope exits, even if an unhandled exception occurs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →