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

> Discover how Cypress captures screenshots and videos during test execution. Learn about the technical process involving base64 data-URLs, Jimp, MediaRecorder, and FFmpeg.

- Repository: [Cypress.io/cypress](https://github.com/cypress-io/cypress)
- Tags: deep-dive
- Published: 2026-07-12

---

**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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/packages/types/src/config.ts)**:

```typescript
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:

```typescript
// 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:

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

```

Access screenshot metadata through the plugin API:

```javascript
// 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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/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`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/screenshots.ts)**, containing the `capture`, `stitchScreenshots`, and `save` functions. Video processing lives in **[`packages/server/lib/video_capture.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/video_capture.ts)**, managing the FFmpeg integration. Configuration types are defined in **[`packages/types/src/config.ts`](https://github.com/cypress-io/cypress/blob/main/packages/types/src/config.ts)**, specifying the `screenshotsFolder`, `video`, and `videoCompression` options.