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
npm install @nut-tree/plugin-screenrecordingSubscription Required
FFmpeg Required
Quick Reference
useScreenRecorder
useScreenRecorder()Activate the screen recording plugin
screen.startStreaming
screen.startStreaming(options?: StreamingOptions)Start streaming the screen directly to a video file
screen.stopStreaming
screen.stopStreaming()Stop the current stream and finalize the video file
screen.startRecording
screen.startRecording(options?: RecordingOptions)Start recording the screen into an in-memory buffer
screen.stopRecording
screen.stopRecording(options?: StopRecordingOptions)Stop the current recording and encode the buffer to a video file
screen.discardRecording
screen.discardRecording()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
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
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
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():
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
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:
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: stringOutput video path. Required — FFmpeg needs it from the start.
fps
fps?: numberFrames per second. Default: 30.
showCursor
showCursor?: booleanWhether to draw the cursor on the recording. Default: true.
width
width?: numberFrame width. Uses first frame's width if not specified.
height
height?: numberFrame height. Uses first frame's height if not specified.
captureRegion
captureRegion?: RegionCapture only a specific region of the screen.
resolutionMode
resolutionMode?: "drop" | "pad-crop"How to handle frames with mismatched dimensions. "drop" skips them (default), "pad-crop" normalizes to target dimensions.
overwrite
overwrite?: booleanAllow overwriting an existing file at outputPath. Default: false.
ffmpegCloseTimeoutMs
ffmpegCloseTimeoutMs?: numberMaximum 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) => voidCalled 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?: numberKeep 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?: booleanSkip 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?: numberFrames per second. Default: 30.
showCursor
showCursor?: booleanWhether to draw the cursor on the recording. Default: true.
width
width?: numberFrame width. Uses first frame's width if not specified.
height
height?: numberFrame height. Uses first frame's height if not specified.
captureRegion
captureRegion?: RegionCapture only a specific region of the screen.
resolutionMode
resolutionMode?: "drop" | "pad-crop"How to handle frames with mismatched dimensions. "drop" skips them (default), "pad-crop" normalizes to target dimensions.
onError
onError?: (error: unknown) => voidCalled 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: stringOutput video path. Required — FFmpeg is spawned here to encode the buffer.
ffmpegCloseTimeoutMs
ffmpegCloseTimeoutMs?: numberMaximum time (ms) to wait for FFmpeg to close. If exceeded, FFmpeg is killed. Default: 30000. Set to 0 to wait indefinitely.
overwrite
overwrite?: booleanAllow 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'safterEachon success to avoid accumulating recordings - Sanitize test names for file paths when using dynamic output names
- Enable
showCursor: truefor debugging to see where clicks happen
FFmpeg
brew install ffmpeg. On Ubuntu/Debian: apt-get install ffmpeg. On Windows: download from ffmpeg.org and add to PATH.