Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Implementation:DevExpress Testcafe VideoRecorderProcess

From Leeroopedia
Knowledge Sources
Domains Video Recording, FFmpeg Integration
Last Updated 2026-02-12 12:00 GMT

Overview

VideoRecorderProcess orchestrates FFmpeg-based video recording of test executions, spanning three classes: a low-level FFmpeg process wrapper, a per-browser-job recorder coordinator, and a per-test-run recorder that manages temp files and timecodes.

Description

The video recording subsystem consists of three cooperating classes:

VideoRecorderProcess (process.js) wraps a spawned FFmpeg child process. It pipes raw frame data (PNG images from the browser provider) into FFmpeg's stdin, which encodes them as H.264 video. It manages the FFmpeg lifecycle -- spawning, frame capture loop, and graceful shutdown -- with configurable encoding options merged over sensible defaults (image2pipe input, libx264 codec, ultrafast preset, yuv420p pixel format, 30fps output).

VideoRecorder (recorder.js) extends EventEmitter and coordinates recording across an entire BrowserJob. It subscribes to browser job lifecycle events (start, done, test-run-create, test-run-ready, test-run-before-done, test-run-restart), creates TestRunVideoRecorder instances per test, manages a temp directory for intermediate files, handles single-file concatenation via FFmpeg, and copies final video files to the target path. It supports failedOnly mode to discard recordings of passing tests.

TestRunVideoRecorder (test-run-video-recorder.js) manages video recording for a single test run. It creates a VideoRecorderProcess, generates temp file names, tracks start times and timecodes for quarantine restarts, and exposes capability checks (isVideoSupported, isVideoEnabled).

Usage

The Videos class (instantiated by Task when opts.videoPath is set) creates one VideoRecorder per BrowserJob. The recorder automatically manages the full recording lifecycle through event subscriptions. Test authors configure video recording via CLI options or API (--video, --video-options, --video-encoding-options).

Code Reference

Source Location

Signature (VideoRecorderProcess)

const DEFAULT_OPTIONS = {
    'f':                          'image2pipe',
    'y':                          true,
    'use_wallclock_as_timestamps': 1,
    'i':                          'pipe:0',
    'c:v':                        'libx264',
    'preset':                     'ultrafast',
    'pix_fmt':                    'yuv420p',
    'vf':                         'scale=trunc(iw/2)*2:trunc(ih/2)*2',
    'r':                          30,
};

export default class VideoRecorderProcess extends AsyncEmitter {
    constructor (basePath, ffmpegPath, connection, customOptions) {
        this.customOptions = customOptions;
        this.videoPath     = basePath;
        this.connection    = connection;
        this.ffmpegPath    = ffmpegPath;
        this.ffmpegProcess = null;
        this.disposed      = false;
        this.closed        = false;
        // ...
    }

    get active();
    async init();
    async dispose();
    async startCapturing();
    async finishCapturing();
}

Signature (VideoRecorder)

export default class VideoRecorder extends EventEmitter {
    constructor (browserJob, basePath, opts, encodingOpts, warningLog) {
        this.browserJob        = browserJob;
        this.basePath          = basePath;
        this.failedOnly        = opts.failedOnly;
        this.singleFile        = opts.singleFile;
        this.ffmpegPath        = opts.ffmpegPath;
        this.customPathPattern = opts.pathPattern;
        this.timeStamp         = opts.timeStamp;
        this.encodingOptions   = encodingOpts;
        this.warningLog        = warningLog;
        this.tempDirectory     = new TempDirectory(TEMP_DIR_PREFIX);
        this.testRunVideoRecorders = {};
        // ...
    }
}

Signature (TestRunVideoRecorder)

export default class TestRunVideoRecorder {
    constructor ({ testRun, test, index }, { path, ffmpegPath, encodingOptions }) {
        this.testRun    = testRun;
        this.test       = test;
        this.index      = index;
        this.tempFiles     = null;
        this.videoRecorder = null;
        this.path            = path;
        this.ffmpegPath      = ffmpegPath;
        this.encodingOptions = encodingOptions;
        this.start     = null;
        this.timecodes = null;
    }

    get testRunInfo();
    get hasErrors();
    async startCapturing();
    async finishCapturing();
    async init();
    async isVideoSupported();
    async isVideoEnabled();
    onTestRunRestart();
}

Import

import VideoRecorderProcess from '../video-recorder/process';
import VideoRecorder from '../video-recorder/recorder';
import TestRunVideoRecorder from '../video-recorder/test-run-video-recorder';

I/O Contract

Inputs (VideoRecorderProcess)

Name Type Required Description
basePath string Yes Output video file path
ffmpegPath string Yes Path to the FFmpeg binary
connection BrowserConnection Yes Browser connection providing frame data via its provider
customOptions object No Custom FFmpeg options merged over defaults

Inputs (VideoRecorder)

Name Type Required Description
browserJob BrowserJob Yes The browser job whose test runs are being recorded
basePath string Yes Base output path for video files
opts object Yes Options: failedOnly, singleFile, ffmpegPath, pathPattern, timeStamp
encodingOpts object Yes FFmpeg encoding options passed to the process
warningLog WarningLog Yes Warning log for unsupported browsers and path issues

Inputs (TestRunVideoRecorder)

Name Type Required Description
testRun TestRun Yes The test run instance being recorded
test Test Yes The test definition
index number Yes Test index for file naming and recorder lookup
path string Yes Temp directory path for intermediate video files
ffmpegPath string Yes Path to the FFmpeg binary
encodingOptions object Yes FFmpeg encoding options

Outputs

Name Type Description
Video files .mp4 files H.264 encoded video files at the configured output path
testRunInfo object Test metadata: { testIndex, fixture, test, timecodes, alias, parsedUserAgent }
hasErrors boolean Whether the associated test run had errors (used for failedOnly filtering)
test-run-video-saved event { testRun, videoPath, singleFile, timecodes? } Emitted by VideoRecorder when a video file is saved

Usage Examples

// VideoRecorder is created internally by the Videos class
const recorder = new VideoRecorder(
    browserJob,
    videoPath,
    {
        failedOnly:  false,
        singleFile:  false,
        ffmpegPath:  '/usr/bin/ffmpeg',
        pathPattern: '${TEST_INDEX}/${USERAGENT}/${FILE_INDEX}.mp4',
        timeStamp:   moment(),
    },
    { crf: 18 },  // encoding options
    warningLog
);

// Events are auto-subscribed; recorder manages lifecycle via browserJob events

// TestRunVideoRecorder is created by VideoRecorder._onTestRunCreate
const testRunRecorder = new TestRunVideoRecorder(
    { testRun, test, index },
    { path: tempDir, ffmpegPath: '/usr/bin/ffmpeg', encodingOptions: {} }
);

await testRunRecorder.init();           // Spawns FFmpeg process
await testRunRecorder.startCapturing(); // Begins frame capture loop
// ... test executes ...
await testRunRecorder.finishCapturing(); // Stops capture and disposes FFmpeg

Internal Mechanics

FFmpeg Default Configuration

The process spawns FFmpeg with these defaults (overridable via customOptions):

  • Input format: image2pipe (reads PNG frames from stdin)
  • Codec: libx264 (H.264)
  • Preset: ultrafast (fastest encoding, larger files)
  • Pixel format: yuv420p (maximum compatibility)
  • Scaling filter: Ensures frame dimensions are divisible by 2 (yuv420p requirement)
  • Frame rate: 30fps output
  • Overwrite: Enabled (-y)

Frame Capture Loop

VideoRecorderProcess._capture runs a continuous loop while the process is active:

  1. Calls connection.provider.getVideoFrameData(connectionId) to get a PNG frame
  2. If frame data exists, writes it to FFmpeg's stdin via _addFrame
  3. If no frame data, waits 20ms before retrying
  4. Errors are caught and logged without breaking the loop

Single-File Concatenation

When singleFile mode is enabled, the VideoRecorder concatenates video files across test runs using FFmpeg's concat demuxer. A temporary merge config file lists the files to concatenate, and spawnSync runs FFmpeg in concat mode.

Temp File Management

TestRunVideoRecorder._generateTempNames creates three temp file paths per test run:

  • tmp-video-{connectionId}.mp4 -- the raw video output
  • config-{connectionId}.txt -- concat config for single-file mode
  • tmp-video-merge-{connectionId}.mp4 -- intermediate merge output

Timecodes

In quarantine mode, onTestRunRestart records timecodes (milliseconds from start) at each restart boundary. These are included in the test-run-video-saved event for reporter integration.

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment