Recording

Screen Recording

Record screen activity with configurable FPS, buffer mode, continuous streaming, and cursor visibility.

Overview

The @nut-tree/plugin-screenrecording plugin adds screen recording capabilities to nut.js. It supports two modes: continuous streaming that writes directly to a video file, and buffer recording that keeps the last N seconds in memory so you can save or discard on demand.

Streaming

Stream screen activity directly to a video file

screen.startStreaming({ outputPath })

Buffer Mode

Keep the last N seconds in memory, save or discard

screen.startRecording({ bufferSeconds: 30 })

Discard

Discard buffered recordings on success

screen.discardRecording()

Installation

typescript
npm install @nut-tree/plugin-screenrecording

Subscription Required

This package is included in Solo and Team subscription plans.

FFmpeg Required

This plugin requires FFmpeg to be installed and available on your system PATH.

Quick Reference

useScreenRecorder

useScreenRecorder()
void

Activate the screen recording plugin

screen.startStreaming

screen.startStreaming(options?: StreamingOptions)
Promise<void>

Start streaming the screen directly to a video file

screen.stopStreaming

screen.stopStreaming()
Promise<void>

Stop the current stream and finalize the video file

screen.startRecording

screen.startRecording(options?: RecordingOptions)
Promise<void>

Start recording the screen into an in-memory buffer

screen.stopRecording

screen.stopRecording(options?: StopRecordingOptions)
Promise<void>

Stop the current recording and encode the buffer to a video file

screen.discardRecording

screen.discardRecording()
Promise<void>

Discard the current recording without saving


Continuous Streaming

Streaming writes frames directly to a video file via FFmpeg as they are captured. Use this mode when you want a complete recording of the entire session.

Basic Streaming

typescript
import { screen } from "@nut-tree/nut-js";
import { useScreenRecorder } from "@nut-tree/plugin-screenrecording";

useScreenRecorder();

// Start streaming — output path is required upfront
await screen.startStreaming({ outputPath: "./recording.mp4" });

// ... perform automation ...

// Stop streaming and finalize the video
await screen.stopStreaming();

Streaming with Options

typescript
import { screen } from "@nut-tree/nut-js";
import { useScreenRecorder } from "@nut-tree/plugin-screenrecording";

useScreenRecorder();

await screen.startStreaming({
    outputPath: "./session-recording.mp4",
    fps: 30,
    showCursor: true,
    overwrite: true,
});

// ... perform automation ...

await screen.stopStreaming();

Streaming vs. Recording

In streaming mode, the output path is provided when starting because FFmpeg begins writing immediately. In buffer/recording mode, the output path is provided when stopping because frames are kept in memory until then.

Buffer Recording

Buffer mode keeps the last N seconds of screen activity in memory. When the recording is stopped, only the buffered portion is written to disk. If the test succeeds, you can discard the buffer entirely with screen.discardRecording():

typescript
import { screen } from "@nut-tree/nut-js";
import { useScreenRecorder } from "@nut-tree/plugin-screenrecording";

useScreenRecorder();

// Start recording with a 30-second buffer
await screen.startRecording({
    bufferSeconds: 30,
    fps: 30,
    showCursor: true,
});

try {
    // ... perform automation ...

    // Test passed — discard the recording
    await screen.discardRecording();
} catch (error) {
    // Test failed — save the last 30 seconds
    await screen.stopRecording({ outputPath: "./failure-capture.mp4" });
    throw error;
}

CI/CD Use Case

Buffer mode is particularly useful in CI/CD pipelines where you only want to keep recordings of failed test runs. Start the buffer at the beginning of each test and only save the recording if the test fails.

Test Framework Integration

Vitest Error Videos

Capture error videos automatically for failing tests using Vitest lifecycle hooks. The buffer records continuously and is either saved on failure or discarded on success:

typescript
import { afterEach, beforeAll, beforeEach, describe, it } from "vitest";
import { down, left, mouse, right, screen, up } from "@nut-tree/nut-js";
import type { RecordingOptions } from "@nut-tree/plugin-screenrecording";
import { useScreenRecorder } from "@nut-tree/plugin-screenrecording";

const recordingOptions: RecordingOptions = {
    bufferSeconds: 30,
    fps: 30,
    showCursor: true,
};

beforeAll(() => {
    useScreenRecorder();
});

beforeEach(async () => {
    await screen.startRecording(recordingOptions);
});

afterEach(async (context) => {
    const failed = context.task.result?.state === "fail";

    if (failed) {
        const safeName = context.task.name.replace(/[^a-zA-Z0-9]+/g, "_");
        await screen.stopRecording({
            outputPath: `./${safeName}_error_recording.mp4`,
        });
    } else {
        await screen.discardRecording();
    }
});

describe("Screen recording", () => {
    it("should move the mouse in a square", async () => {
        await mouse.move(right(500));
        await mouse.move(down(500));
        await mouse.move(left(500));
        await mouse.move(up(500));
    }, 30_000);

    it.fails("should detect a simulated failure", async () => {
        await mouse.move(right(200));
        await mouse.move(down(200));

        throw new Error("Simulated failure");
    }, 30_000);
});

Options Reference

StreamingOptions

Options for screen.startStreaming(). The output path is required because FFmpeg starts writing from the beginning.

outputPath

outputPath: string
required

Output video path. Required — FFmpeg needs it from the start.

fps

fps?: number
optional

Frames per second. Default: 30.

showCursor

showCursor?: boolean
optional

Whether to draw the cursor on the recording. Default: true.

width

width?: number
optional

Frame width. Uses first frame's width if not specified.

height

height?: number
optional

Frame height. Uses first frame's height if not specified.

captureRegion

captureRegion?: Region
optional

Capture only a specific region of the screen.

resolutionMode

resolutionMode?: "drop" | "pad-crop"
optional

How to handle frames with mismatched dimensions. "drop" skips them (default), "pad-crop" normalizes to target dimensions.

overwrite

overwrite?: boolean
optional

Allow overwriting an existing file at outputPath. Default: false.

ffmpegCloseTimeoutMs

ffmpegCloseTimeoutMs?: number
optional

Maximum time (ms) to wait for FFmpeg to close during stopStreaming. If exceeded, FFmpeg is killed. Default: 30000. Set to 0 to wait indefinitely.

onError

onError?: (error: unknown) => void
optional

Called when an error occurs during frame capture.

RecordingOptions

Options for screen.startRecording(). Frames are kept in an in-memory buffer and encoded to a file when stopRecording() is called.

bufferSeconds

bufferSeconds?: number
optional

Keep last N seconds in memory instead of streaming to file. Maximum 30 seconds. Memory usage depends on resolution and fps — each frame uses width * height * 4 bytes (e.g. ~8 MB at 1080p). Default: 30.

skipMemoryCheck

skipMemoryCheck?: boolean
optional

Skip the buffer memory cap check. Use this when recording high-resolution screens (e.g. 6K) where you know the system has enough memory.

fps

fps?: number
optional

Frames per second. Default: 30.

showCursor

showCursor?: boolean
optional

Whether to draw the cursor on the recording. Default: true.

width

width?: number
optional

Frame width. Uses first frame's width if not specified.

height

height?: number
optional

Frame height. Uses first frame's height if not specified.

captureRegion

captureRegion?: Region
optional

Capture only a specific region of the screen.

resolutionMode

resolutionMode?: "drop" | "pad-crop"
optional

How to handle frames with mismatched dimensions. "drop" skips them (default), "pad-crop" normalizes to target dimensions.

onError

onError?: (error: unknown) => void
optional

Called when an error occurs during frame capture.

StopRecordingOptions

Options for screen.stopRecording(). The output path is provided here because in buffer mode the file is only created when stopping.

outputPath

outputPath: string
required

Output video path. Required — FFmpeg is spawned here to encode the buffer.

ffmpegCloseTimeoutMs

ffmpegCloseTimeoutMs?: number
optional

Maximum time (ms) to wait for FFmpeg to close. If exceeded, FFmpeg is killed. Default: 30000. Set to 0 to wait indefinitely.

overwrite

overwrite?: boolean
optional

Allow overwriting an existing file at outputPath. Default: false.

Best Practices

Recording Tips

  • Use streaming when you want a full, continuous recording of your session
  • Use buffer mode in CI/CD to capture only failures without filling up disk space
  • Call discardRecording() in your test's afterEach on success to avoid accumulating recordings
  • Sanitize test names for file paths when using dynamic output names
  • Enable showCursor: true for debugging to see where clicks happen

FFmpeg

Make sure FFmpeg is installed and on your PATH. On macOS: brew install ffmpeg. On Ubuntu/Debian: apt-get install ffmpeg. On Windows: download from ffmpeg.org and add to PATH.

Was this page helpful?