How Cypress Captures Screenshots and Videos During Test Execution: The Complete Technical Guide

Cypress captures screenshots by extracting base64 data-URLs from the browser automation layer and processing them through Jimp for validation and stitching, while videos are recorded via MediaRecorder streams and transcoded to MP4 using FFmpeg.

Cypress records visual artifacts through a tightly-coupled stack that starts in the test runner, passes through the automation bridge, and ends in the server’s filesystem helpers. Understanding how Cypress captures screenshots and videos requires examining the interaction between the driver-level automation APIs and the server-side processing modules in the cypress-io/cypress repository.

The Screenshot Capture Pipeline

When you invoke cy.screenshot(), the request travels through three architectural layers before becoming a PNG file on disk.

Browser Automation and Data Extraction

The process begins in the test driver where cy.screenshot([options]) queues a command that reaches the automation layer at @packages/driver/lib/automation/*. This layer invokes the browser’s native screenshot API and returns a data-URL string formatted as data:image/png;base64,….

The server module at packages/server/lib/screenshots.ts receives this data through its capture method. This implementation decodes the URL using data-uri-to-buffer and loads the image into Jimp (Jimp.read) for server-side manipulation.

Pixel Validation and Multipart Assembly

For full-page captures requiring scroll-and-stitch behavior, Cypress implements sophisticated validation logic. The pixelConditionFn checks for helper pixels that Cypress injects to determine when the UI is hidden during capture.

When multipart mode activates:

  1. Each scroll slice is stored individually
  2. Consecutive slices are compared using lastImagesAreDifferent to detect motion
  3. Final assembly occurs through stitchScreenshots, which composites the slices into a single coherent image

Cropping and File Persistence

Before saving, the image undergoes optional cropping based on user specifications. If data.clip (element-specific) or data.userClip (custom region) parameters exist, the crop method adjusts the Jimp image accordingly.

The save method in screenshots.ts constructs the final path using getPathToScreenshot and the configured screenshotsFolder. It determines the MIME type via mime.getExtension, writes the buffer using fs.outputFile, and returns metadata including size, dimensions, and timestamp.

After persistence, the optional after:screenshot plugin hook fires via plugins.execute('after:screenshot', …), allowing asynchronous modification of the final artifact.

Video Recording Architecture

Video capture follows a parallel but distinct path optimized for real-time encoding.

Screen Capture Initialization

When video: true is configured or cy.recordVideo() is invoked, the automation layer at @packages/driver/lib/automation/video initiates a screen-capture stream using the browser’s MediaRecorder API. On Chromium browsers, this may utilize Chrome DevTools Protocol commands for direct frame access.

FFmpeg Processing and Compression

The server-side implementation in packages/server/lib/video_capture.ts receives raw webm fragments from the browser stream. Rather than storing raw WebM, Cypress pipes these fragments to FFmpeg via a child process to produce optimized MP4 files.

Key implementation details:

  • The capture() function manages the FFmpeg process lifecycle
  • getCodecData() constructs the appropriate encoding commands
  • The videoCompression configuration maps directly to FFmpeg’s CRF (Constant Rate Factor) value, defaulting to approximately 32 for balanced quality

When a spec finishes, the server closes the FFmpeg pipe, finalizes the MP4 container, and writes the output to the configured videosFolder. Errors during this process are logged but do not abort the test run.

The optional after:video hook allows post-processing, such as uploading to CI artifact stores.

Configuration Interface

Both artifacts respect the user’s Cypress configuration defined in packages/types/src/config.ts:

export interface ResolvedConfigOptions {
  screenshotsFolder: string          // default: <projectRoot>/cypress/screenshots
  video: boolean                     // enable/disable video recording
  videoCompression: number | true   // CRF value or true → default CRF (32)
  videosFolder: string               // default: <projectRoot>/cypress/videos
}

These settings are read at startup and passed to the respective capture modules.

Practical Implementation Examples

The following patterns demonstrate how to interact with the capture system:

// Capture a specific element
cy.get('.card').screenshot('card-snapshot')

// Full-page capture with automatic multipart stitching
cy.screenshot({ capture: 'fullPage' })

Execute video recording via CLI with custom compression:

cypress run --config video=true,videoCompression=20

Access screenshot metadata through the plugin API:

// cypress.config.ts or plugins file
module.exports = (on, config) => {
  on('after:screenshot', (details) => {
    // Upload to S3 and return modified path
    return uploadToS3(details.path).then(() => ({ path: details.path }))
  })
}

Summary

  • Cypress captures screenshots by extracting base64 data-URLs from browser automation, processing them through Jimp in screenshots.ts, and optionally stitching multiple scroll slices using stitchScreenshots.
  • Video recording utilizes MediaRecorder streams or Chrome DevTools Protocol, processed through FFmpeg in video_capture.ts with configurable CRF compression.
  • Configuration is controlled via screenshotsFolder, videosFolder, video, and videoCompression options in the resolved config.
  • Plugin hooks (after:screenshot, after:video) allow asynchronous post-processing of artifacts before the run completes.
  • Pixel validation (pixelConditionFn) ensures UI elements are hidden before capture, preventing toolbars from appearing in final images.

Frequently Asked Questions

How does Cypress handle full-page screenshots that exceed viewport height?

Cypress implements multipart screenshot stitching in packages/server/lib/screenshots.ts. The multipartConditionFn detects when a capture requires scrolling, then stores individual viewport slices. The lastImagesAreDifferent function compares consecutive slices to detect page motion, while stitchScreenshots composites the valid slices into a single coherent image using Jimp.

What video format does Cypress use and why?

Cypress captures raw video using the browser's MediaRecorder API (producing WebM fragments) but immediately transcodes to MP4 using FFmpeg. This occurs in packages/server/lib/video_capture.ts to ensure cross-platform compatibility and manageable file sizes. The videoCompression setting maps to FFmpeg's CRF value, with a default of 32 providing balance between quality and file size.

Can I modify screenshots after they are taken but before they are saved?

Yes. The after:screenshot plugin hook fires after the file is written to disk but before the test runner proceeds. Registered in packages/server/lib/plugins.ts, this hook receives the file metadata and can return a modified path or perform asynchronous operations like uploading to cloud storage. The modified path is then reported in the test results.

Where are the core screenshot and video processing functions located?

The screenshot processing logic resides in packages/server/lib/screenshots.ts, containing the capture, stitchScreenshots, and save functions. Video processing lives in packages/server/lib/video_capture.ts, managing the FFmpeg integration. Configuration types are defined in packages/types/src/config.ts, specifying the screenshotsFolder, video, and videoCompression options.

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 →