skip to content

In Cypress, which files can cy.readFile() reach that cy.fixture() cannot?

level: middleimportance: should knowfreq 46%

answer

  1. Two commands, two different starting folders
  2. One is capped at the fixtures folder
  3. The other counts from the config file
  4. Only one of them retries
  5. Query semantics allow not.exist assertions

basics

~20 s

Anything under the project root. cy.readFile() resolves its path from the folder holding the Cypress config, so it reads a downloaded reimbursement CSV or a generated report, while cy.fixture() is confined to fixturesFolder, by default cypress/fixtures.

solid answer

~40 s

`cy.fixture()` only ever looks inside `fixturesFolder` — `cypress/fixtures` by default — so it can read canned inputs and nothing else. `cy.readFile()` resolves its path against the **project root**, the directory containing the Cypress configuration file, so it reaches a receipt PDF the app dropped in `cypress/downloads`, an export your test just produced, or any file in the repository. The behaviours differ too: `cy.readFile()` is a query, so it re-reads and retries until the chained assertions pass, and by default it asserts that the file exists — `cy.readFile('missing.csv').should('not.exist')` is how you assert absence. `cy.fixture()` neither retries nor appears in the Command Log. `cy.writeFile()` uses the same project-root path rule in the other direction, creating missing directories and overwriting by default.

go deeper

for a junior

Be ready to say that cy.fixture() paths start at cypress/fixtures while cy.readFile() and cy.writeFile() paths start at the project root, and to read a downloaded file with the right one.

for a middle

Explain the mechanics that follow from that: cy.readFile() is a query that retries and asserts existence, cy.fixture() runs once, and cy.writeFile() overwrites by default while creating any missing directories.

for a senior

Show judgment about tests that touch the working tree: which files a suite is allowed to write, why refreshing a checked-in fixture on every run is a trap, and how you wait for a downloaded export without a fixed sleep.

for a principal

Own the boundary between the suite and the repository: where generated artefacts live, what is cleaned up between runs, and whether tests may write anywhere other than a scratch directory the pipeline discards.

## Two different roots The single most useful fact here is that these commands do not measure paths from the same place. | | `cy.fixture()` | `cy.readFile()` / `cy.writeFile()` | |---|---|---| | Path is relative to | `fixturesFolder` (default `cypress/fixtures`) | the project root — the folder holding the Cypress config file | | Reads | canned inputs only | any file in the project | | JSON handling | parsed, unless an encoding is passed | parsed by `cy.readFile()`, unless the encoding is `null` | | Retries | no | yes, `cy.readFile()` is a query | | Command Log | not logged | logged, unless `log: false` | | Caching | file read once per path and encoding | read fresh on every attempt | So in an expense-report suite, `cy.fixture('receipts.json')` reads `cypress/fixtures/receipts.json`, while `cy.readFile('cypress/fixtures/receipts.json')` reads the very same file by spelling out the whole path from the project root. The second form is not a mistake, just a different door — and it is the only door to files the fixtures folder does not hold: - `cy.readFile('cypress/downloads/reimbursements.csv')` for a file the application under test downloaded during the run. - `cy.readFile('package.json').its('version')` for project metadata. - `cy.readFile('tmp/expense-export.txt')` for something a `cy.writeFile()` earlier in the spec produced. ## `cy.readFile()` retries; `cy.fixture()` does not `cy.readFile()` is a **query**, which is why waiting for a file works at all. It re-runs against the disk until every chained assertion and command passes or `defaultCommandTimeout` expires, so this is a legitimate way to wait for an export the app writes asynchronously: ```js cy.get('[data-testid="export-reimbursements"]').click() cy.readFile('cypress/downloads/reimbursements.csv').should('contain', 'R-1001') ``` By default the command also asserts that the file **exists**, retrying past the initial file-not-found rather than failing at once. To assert the opposite — that a file was never written, or was cleaned up — chain `.should('not.exist')`, which is the documented way to invert it. `cy.fixture()` has none of that. It runs once, resolves once, and only evaluates assertions you chained once. It uses `responseTimeout` rather than `defaultCommandTimeout`, and the docs are blunt that it should never realistically time out. ## Writing files back `cy.writeFile()` is the mirror image and shares the project-root rule: - Missing directories in the path are **created** for you. - The default file-system flag is `w`, so an existing file is **overwritten**, not appended; `{ flag: 'a+' }` appends instead. - An object or array is stringified to JSON with two-space indentation before it is written; a string is written as-is. - The command yields `null`, so chain the next step off `cy.readFile()` rather than expecting the written contents back. - Appending assumes plain text — to merge into a JSON file you read it, edit the object, and write the whole thing back. A common shape in an expense-report suite is capturing a real response once and keeping it: ```js cy.request('/api/receipts').then((response) => { cy.writeFile('cypress/fixtures/receipts.json', response.body) }) ``` That is fine as a one-off refresh. Leaving it in the suite is not: it makes every run mutate the repository, and the next run then tests against whatever the last run happened to see. ## What each one shows you when it fails The three commands are not equally visible when something goes wrong, which matters when you are reading a CI failure rather than watching the run: - `cy.readFile()` writes an entry into the Command Log with the file name, so a failed read is visible in the run and clickable in time-travel; passing `{ log: false }` suppresses it. - `cy.fixture()` logs nothing at all, so a failure surfaces only as the error message — there is no greyed-out command entry to hover over. - `cy.writeFile()` logs the resolved path and the contents it wrote, which is the quickest way to confirm that a test wrote where you thought it did rather than one directory up. ## Choosing between them 1. Canned input that ships with the suite and never changes during a run → `cy.fixture()`, for the short path and the automatic parsing. 2. A file that appears, changes, or disappears while the test runs → `cy.readFile()`, for the retrying query semantics. 3. A file your test needs to produce → `cy.writeFile()`, ideally under a scratch or downloads folder rather than into the checked-in fixtures. ## One shared constraint Both commands do their work in the Cypress Node process and hand the contents to the browser, so neither is a way to reach a file on a remote machine, and neither belongs in the middle of a tight assertion loop. They are also spec-only commands: they must be invoked from a running spec rather than from arbitrary code loaded alongside it.

  • How do you assert in Cypress that an expense export file was never written?
    Chain `.should('not.exist')` onto `cy.readFile()`. By default the command asserts existence and retries past a missing file, so the negative assertion is the documented way to invert it: `cy.readFile('cypress/downloads/export.csv').should('not.exist')` passes only while the file is absent for the whole retry window.
  • What does cy.writeFile() do when the target file already exists?
    It overwrites it. The default file-system flag is `w`, so the previous contents are gone; pass `{ flag: 'a+' }` to append instead. Missing directories along the path are created automatically, objects are stringified to JSON with two-space indentation, and the command yields `null`.

saying these in an interview costs you the question

  • Thinks cy.readFile() also starts at the fixtures folder
  • Expects cy.fixture() to retry until a file appears
  • Says cy.writeFile() appends to an existing file
  • Uses cy.fixture() to read a downloaded export
  • Believes cy.writeFile() yields the written contents