skip to content

When a Cypress test downloads a PDF, where does the file land and why is there no dialog?

level: middleimportance: must knowfreq 52%

answer

  1. The file leaves the browser entirely
  2. It lands on the runner's own disk
  3. A dialog would stall an unattended run
  4. A config key names the target folder
  5. cypress/downloads is the default path

basics

~20 s

It lands in Cypress's downloadsFolder, cypress/downloads by default, on the machine running Cypress. At launch Cypress tells the browser to save downloads there silently, so no native Save As dialog can block an unattended run.

solid answer

~40 s

The file leaves the browser and lands on the **file system of the machine running Cypress**, in the `downloadsFolder` — `cypress/downloads` unless you change it. Cypress arranges this when it launches the browser, over the same channel it drives the browser with: for Chromium-based browsers it sets the download behaviour and target directory through the DevTools Protocol, and for Firefox it bakes the folder and the "never ask" list into the profile it launches. That is why no Save As prompt or download shelf appears — a native dialog would block an unattended run and no command could dismiss it. Because the file is on disk rather than in the page, you assert on it from Node: `cy.readFile('cypress/downloads/statement-2026-08.pdf', 'binary')`, which retries until the file exists and the assertions pass.

code

javascript · 9 lines
javascript
cy.visit('/statements')
cy.contains('[data-cy=statement-row]', 'August 2026')
  .find('[data-cy=download-pdf]')
  .click()

cy.readFile('cypress/downloads/statement-2026-08.pdf', 'binary', { timeout: 15000 })
  .should((pdf) => {
    expect(pdf.slice(0, 5)).to.equal('%PDF-')
  })

go deeper

for a junior

Know the default location, cypress/downloads, and that the file is a real file on the machine running Cypress rather than something held inside the browser.

for a middle

Explain why no Save As dialog appears — Cypress configures the download behaviour when it launches the browser — and how you assert on the file once it is on disk.

for a senior

Be ready to debug an empty downloads folder in CI: whether a download started at all, what the file was named, what the run trashed before it began, and what a previous spec left behind.

for a principal

Decide how far download testing should go for the suite: existence versus content, which artefacts CI keeps, and whether verifying a rendered document belongs in this suite at all.

A download is one of the few things in a browser test that ends up *outside* the browser. Everything else a Cypress spec touches — elements, cookies in the page, network responses — lives in a world the spec can inspect directly. A downloaded bank statement is a file on a disk, and getting it there predictably is arranged by Cypress before the first test runs. ## Where the file actually goes Cypress writes downloads to the `downloadsFolder`, which defaults to `cypress/downloads` inside the project. That folder is on the machine running Cypress — the same machine as the Node process, not some sandbox inside the browser. In CI that means the container or runner's file system, which is where you have to look, and what you have to preserve if you want the artefact after the job ends. ## How the browser is told The instruction is issued once, at launch, over the channel Cypress drives the browser with: | Browser family | How the folder is set | Effect | |---|---|---| | Chrome, Chromium, Edge, Electron | a DevTools Protocol call setting download behaviour and path | downloads are allowed and written to `downloadsFolder` | | Firefox | preferences baked into the launched profile | folder is chosen explicitly, and a long "never ask" MIME list suppresses the prompt | The instruction is issued **once per browser session, before your first test runs** — not per download and not per spec. That is deliberate: by the time a click has started a download it is far too late to negotiate where the bytes go, and a native prompt would already be on screen. It also means the setting is a property of the launched browser, so a spec cannot move the folder mid-run; `downloadsFolder` is read from configuration when Cypress starts the browser. Both paths exist for the same reason: **a native Save As dialog is unautomatable**. It is browser chrome, not page content, so no `cy.get()` can reach it and no click can dismiss it. An unattended run that produced one would simply hang until it timed out. Suppressing the dialog is what makes downloads testable at all. ## Asserting on the download Because the file is on disk, the assertion happens in Node, not in the page: ```javascript cy.contains('[data-cy=statement-row]', 'August 2026') .find('[data-cy=download-pdf]') .click() cy.readFile('cypress/downloads/statement-2026-08.pdf', 'binary', { timeout: 15000 }) .should((pdf) => { expect(pdf.slice(0, 5)).to.equal('%PDF-') }) ``` `cy.readFile()` retries: if the file does not exist yet it keeps looking until it appears and the chained assertions pass, or the timeout expires. That built-in retry is what saves you from racing the browser's writer, and it is why a fixed wait after the click is unnecessary. ## Why the folder can still be empty Most "my download test is broken" reports are not about the folder at all. Work through these in order: 1. **Nothing was downloaded.** If the response has no `Content-Disposition: attachment`, the browser may render the file rather than save it. On a statement app that embeds a third-party PDF viewer this is the usual cause — the click opened the statement in the viewer, and no download ever started. 2. **The filename is not what you assumed.** The browser, not Cypress, picks the saved name from the response, so a generated statement can arrive as something other than the tidy name in your test. Match on a pattern, or list the folder and pick the newest entry, rather than hard-coding one string. 3. **The run cleared it.** With `trashAssetsBeforeRuns` at its default of `true`, `cypress run` empties `downloadsFolder`, `screenshotsFolder` and `videosFolder` — their entire contents, nested folders included — before the run starts. It never does this in `cypress open`. 4. **A previous spec's file is still there.** The clear happens once per run, not between specs, so a stale file from an earlier spec can satisfy an assertion you meant to be about this one. ## Practical habits - **Assert on content, not just existence.** Checking a `%PDF-` header or a non-trivial byte length catches an error page saved with a `.pdf` name. - **Give the read a real timeout.** A large statement takes longer than the default command timeout on a slow runner. - **Treat the folder as run-scoped state.** If two specs download a statement for the same month, make one of them use a different account or clear the folder deliberately, rather than assuming isolation you do not have. - **Keep the folder out of version control**, and publish it as a CI artefact when a download failure needs investigating after the fact.

  • In Cypress, why might cypress/downloads be empty even though the click clearly worked?
    Because no download ever started. If the response carries no `Content-Disposition: attachment`, the browser can render the file instead — on a statement app with an embedded PDF viewer that is the common outcome. Check the response the click triggers before suspecting the folder, the configuration or the browser.
  • What happens to Cypress's downloadsFolder between runs and between specs?
    With `trashAssetsBeforeRuns` at its default of `true`, `cypress run` empties `downloadsFolder`, `screenshotsFolder` and `videosFolder` completely before the run begins, nested folders included. It never trashes assets in `cypress open`. The clear happens once per run, not between specs, so an earlier spec's file can still be sitting there.

saying these in an interview costs you the question

  • Expects a Save As dialog the test must dismiss
  • Looks for the file in the OS Downloads folder
  • Thinks the downloaded file stays inside the browser
  • Assumes every link click produces a download
  • Believes the folder is cleared between specs