Q29 of 48 · Cypress
How do you handle file downloads in Cypress?
Short answer
Short answer: Cypress doesn't expose a download API. The pragmatic patterns: trigger the download and assert on the file in `cypress/downloads/` via `cy.readFile`, intercept the download URL and assert on the headers/body, or use the underlying `cy.request` to fetch the file directly and validate it.
Detail
Browser-driven downloads in Cypress land in cypress/downloads/ (configured via downloadsFolder). The simplest pattern asserts the file exists and inspects its contents:
cy.get('[data-test=export-csv]').click();
cy.readFile('cypress/downloads/orders.csv', { timeout: 10000 })
.should('include', 'order_id,customer,total');
Two gotchas:
- The folder accumulates between runs. Clear it in
beforeEachwithcy.task(Cypress doesn't auto-clear by default). - The browser may not actually trigger a real download in headless mode; it depends on the trigger (
<a download>works,window.openmay not).
For deterministic tests, bypass the click and fetch directly:
cy.request({ url: '/api/exports/orders.csv', encoding: 'binary' })
.its('body')
.should('include', 'order_id');
This is faster, more reliable, and tests the actual content. Use the click flow only when you specifically want to verify the trigger UI works.
A third option is to intercept the download request and assert on it without writing to disk:
cy.intercept('GET', '/api/exports/orders.csv').as('export');
cy.get('[data-test=export-csv]').click();
cy.wait('@export').its('response.statusCode').should('eq', 200);
Each pattern tests a different layer; pick the one closest to the contract you actually care about.
// EXAMPLE
download.cy.ts
// Pattern 1: assert on the saved file
it('downloads the orders CSV', () => {
cy.task('clearDownloads'); // custom task, see config
cy.visit('/orders');
cy.get('[data-test=export-csv]').click();
cy.readFile('cypress/downloads/orders.csv', { timeout: 10000 })
.should('include', 'order_id,customer,total');
});
// Pattern 2: bypass the UI, fetch the file directly
it('exports CSV with the right rows', () => {
cy.request({ url: '/api/exports/orders.csv', encoding: 'binary' })
.then((res) => {
expect(res.headers['content-type']).to.include('text/csv');
const lines = (res.body as string).split('\n');
expect(lines).to.have.length(101); // header + 100 rows
});
});
// Pattern 3: assert on the network call only
it('triggers the download endpoint', () => {
cy.intercept('GET', '/api/exports/orders.csv').as('export');
cy.visit('/orders');
cy.get('[data-test=export-csv]').click();
cy.wait('@export').its('response.statusCode').should('eq', 200);
});