# How Cypress Handles File Downloads in Headless and Headed Modes

> Learn how Cypress manages file downloads across headed and headless modes. Discover its interception methods and customizable downloads folder for consistent testing.

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

---

**Cypress handles file downloads by intercepting native browser events—via the `will‑download` listener in Electron, the Chrome DevTools Protocol `Page.setDownloadBehavior` in Chromium, and WebDriver preferences in Firefox—and routing files to a configurable `downloadsFolder`, while emitting standardized automation events (`create:download`, `complete:download`, `canceled:download`) that behave identically in both headed and headless modes.**

The `cypress-io/cypress` repository implements a browser‑agnostic download management layer that abstracts platform‑specific mechanisms into a unified automation API. Whether you run tests with a visible browser window or in a headless CI environment, Cypress configures the browser to write downloads directly to the project’s `downloadsFolder` and monitors completion through native events, eliminating the need for manual UI interaction.

## Browser‑Specific Download Implementations

### Electron: The `will‑download` Event

In [`packages/server/lib/browsers/electron.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/electron.ts), Cypress attaches a listener to the Electron `session` object’s `will‑download` event. When a download initiates, the handler constructs the absolute destination path using `path.join(dir, downloadItem.getFilename())` and emits a **`create:download`** event to the automation layer. Completion is tracked via `downloadItem.once('done')`, which fires either **`complete:download`** or **`canceled:download`** depending on the outcome.

To ensure downloads proceed without user confirmation, Cypress also sends the CDP command `Page.setDownloadBehavior` with `{behavior: 'allow', downloadPath: dir}`.

Source: [electron.ts lines 99‑108](https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/browsers/electron.ts#L99-L108)

### Chrome and Chromium: CDP Protocol

For Chromium‑based browsers, [`packages/server/lib/browsers/chrome.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/chrome.ts) establishes a Chrome DevTools Protocol (CDP) session and issues `Page.setDownloadBehavior` with the `downloadsFolder` path. This command is essential for **headless** mode, where no download UI exists, allowing the browser to write files directly to disk. The implementation monitors CDP events to translate browser‑native download notifications into the standard Cypress automation events.

Source: [chrome.ts lines 260‑332](https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/browsers/chrome.ts#L260-L332)

### Firefox: WebDriver Preferences

In [`packages/server/lib/browsers/firefox.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/firefox.ts), Cypress configures Firefox through the WebDriver protocol by setting the preference **`browser.download.dir`** to the resolved `downloadsFolder` and **`browser.download.folderList`** to `2` (use custom folder). This forces Firefox to bypass its default download manager and write files directly to the Cypress‑controlled directory, regardless of headed or headless state.

Source: [firefox.ts lines 475‑485](https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/browsers/firefox.ts#L475-L485)

### WebKit: Native Download Events

The WebKit implementation in [`packages/server/lib/browsers/webkit.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/webkit.ts) listens for the browser’s native `download` event, mapping it to the same **`create:download`** and **`complete:download`** event structure used by Electron and Chrome.

Source: [webkit.ts lines 44‑55](https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/browsers/webkit.ts#L44-L55)

## Headless vs Headed Mode Differences

The primary distinction between modes is UI visibility, not download handling:

- **Headed mode**: The browser window is visible (`show: true`). Electron windows render at the configured width/height, and Chrome shows the native download bar. Cypress still intercepts events via the automation layer defined in [`packages/driver/src/cypress/downloads.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/downloads.ts).
- **Headless mode**: No window is displayed (`show: false`). Chrome relies entirely on the CDP `Page.setDownloadBehavior` command because the download UI is unavailable. Electron continues to use the same `will‑download` listener, simply without rendering the window.

Both modes write files to the same `downloadsFolder` and emit identical automation events, ensuring test portability across environments.

## Configuration and Automation Events

The download system centers on three automation events defined in the driver layer:

- **`create:download`**: Fires when a download starts
- **`complete:download`**: Fires when a download finishes successfully  
- **`canceled:download`**: Fires when a download is aborted

Configure the destination folder in your Cypress configuration file:

```javascript
// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    downloadsFolder: 'cypress/downloads',
    // Downloads work identically in headed and headless modes
  }
})

```

## Practical Code Examples

### Basic Download Verification

```javascript
// cypress/e2e/downloads.cy.ts
describe('File Downloads', () => {
  it('downloads and verifies a CSV file', () => {
    cy.visit('/data-export')
    cy.get('[data-cy=download-csv]').click()
    
    // Cypress waits for the file to appear in downloadsFolder
    cy.readFile(`${Cypress.config('downloadsFolder')}/report.csv`)
      .should('contain', 'Date,Revenue,Expenses')
  })
})

```

### Handling Binary Files

```javascript
// Read a downloaded PDF as binary
cy.readFile(`${Cypress.config('downloadsFolder')}/invoice.pdf`, null)
  .then((file) => {
    expect(file.length).to.be.greaterThan(1024)
    expect(file).to.be.an.instanceof(Buffer)
  })

```

### Advanced: Listening to Download Events

```javascript
// cypress/support/e2e.ts
Cypress.on('task', {
  onDownloadCreated({ id, filePath, mime }) {
    cy.log(`Download started: ${filePath} (${mime})`)
    return null
  }
})

```

## Summary

- **Cypress intercepts downloads** at the browser level using Electron’s `will‑download` event, Chrome’s CDP `Page.setDownloadBehavior`, or Firefox’s WebDriver preferences.
- **File paths** resolve to `Cypress.config('downloadsFolder')`, defaulting to `cypress/downloads` relative to the project root.
- **Automation events** (`create:download`, `complete:download`, `canceled:download`) provide a cross‑browser API that functions identically in headed and headless modes.
- **Headless Chrome** requires CDP configuration to enable downloads since the native UI is unavailable, while **headed Electron** shows the download bar, but both use the same underlying event system.

## Frequently Asked Questions

### How does Cypress handle file downloads differently in headless Chrome versus headed Chrome?

In headed Chrome, the browser displays the native download UI while Cypress monitors the download via CDP. In headless Chrome, no UI exists, so Cypress relies exclusively on the `Page.setDownloadBehavior` CDP command to allow writes to the `downloadsFolder`. Both modes emit the same `complete:download` automation events when the file is written.

### Can I change the download folder location in Cypress?

Yes. Set the `downloadsFolder` property in your [`cypress.config.js`](https://github.com/cypress-io/cypress/blob/main/cypress.config.js) file. Cypress resolves this path relative to the project root and passes it to each browser implementation—Electron, Chrome, Firefox, and WebKit—ensuring all downloads route to your specified directory regardless of mode.

### Why does my download test work locally (headed) but fail in CI (headless)?

This typically occurs when tests rely on UI interactions that don't exist in headless mode, or when the download trigger requires specific headers. Ensure you use `cy.readFile()` to assert against the `downloadsFolder` rather than checking UI elements, and verify that your application triggers the download via a standard anchor tag or API call that Cypress can intercept in both modes.

### Does Cypress support download progress monitoring?

Yes. The automation layer emits `create:download` when the download starts and `complete:download` when it finishes. While the public API primarily exposes file existence via `cy.readFile()`, advanced users can tap into the underlying automation events through Cypress event listeners to track download progress or filenames dynamically.