skip to content

What does Cypress's cy.writeFile() yield, and whose disk does it write to?

level: middleimportance: nice to knowfreq 30%

answer

  1. The command hands back nothing useful
  2. Relative paths start at the project root
  3. The Node half owns the disk
  4. The default flag truncates the file
  5. Read it back to assert on it

basics

~20 s

It yields null, never the contents. The write happens in the Node process running Cypress, at a path resolved from the project root — the folder holding the Cypress config file — not in the browser and not beside the spec.

solid answer

~40 s

`cy.writeFile()` yields `null`, so there is nothing useful to assert on directly; to check what landed, read it back with `cy.readFile()`, which is a query and retries until the chained assertions pass. The path is relative to the project root — the directory containing the Cypress configuration file — and `cypress run --project` moves that root, which is the usual reason a path that works locally misses in a monorepo pipeline. Missing directories are created. The default `flag` is `w`, so an existing file is overwritten; `{ flag: 'a+' }` appends raw text, which corrupts a JSON file. Objects and arrays are stringified and formatted. Encoding defaults to `utf8`, and passing `null` writes a `Buffer` directly. The command's timeout comes from `defaultCommandTimeout`, not `taskTimeout`.

go deeper

for a junior

Remember that Cypress's cy.writeFile() yields null and resolves its path from the project root, so to check what landed you read the file back with cy.readFile().

for a middle

Explain the option defaults — flag w truncates, encoding is utf8, the timeout is defaultCommandTimeout — and why appending to a JSON file produces an invalid document.

for a senior

Be ready to explain why a file a spec wrote is missing on a CI machine, and why files written from specs make a fragile way to carry state through a suite.

for a principal

Own whether specs may write into the repository at all, and what the sanctioned alternative is when a run genuinely needs to leave an artefact behind.

## What the command yields `cy.writeFile()` yields `null`. Not the contents, not a byte count, not a path. That is deliberate — there is nothing meaningful for a browser-side command to hand back about a file that lives on another machine's disk — but it catches people out, because a chain like `cy.writeFile('out/tenants.json', rows).should('have.length', 3)` is asserting on `null` and will never pass. To check what was written, read it back: ```javascript cy.writeFile('cypress/reports/tenants.json', rows) cy.readFile('cypress/reports/tenants.json').should('have.length', 3) ``` `cy.readFile()` has been a query since Cypress 13, so it re-reads the file and keeps re-reading until every chained command and assertion passes or `defaultCommandTimeout` runs out. That makes it the right tool for reading a file some other part of the system is still producing. ## Where the path resolves A relative path is resolved from the **project root**: the directory that contains the Cypress configuration file. It is not resolved from the spec's own folder, and it is not resolved from the shell's working directory when you typed `cypress run`. Three consequences follow: - A spec at `cypress/e2e/tenant-settings.cy.js` that writes `'out/settings.json'` produces `<projectRoot>/out/settings.json`, not `cypress/e2e/out/settings.json`. - `cypress run --project packages/admin-console` changes the project root, so the same relative path lands somewhere else. In a monorepo this is the usual reason a file a spec wrote cannot be found by a later CI step. - The write happens on the machine running Cypress — the Node half. The browser never touches a disk, so nothing about the page's origin, storage or sandbox is involved. If the directories in the path do not exist, Cypress creates them along with the file. ## Overwrite, append and merge The `flag` option is passed through to Node's file-system flags and defaults to `w`, which truncates an existing file. `{ flag: 'a+' }` appends instead. Appending assumes a plain-text file. Appending to a JSON file produces two JSON documents concatenated into one invalid file, which is a mistake that surfaces later as a parse error in a completely different step. To add to structured data, read, mutate, write: 1. `cy.readFile('cypress/reports/tenants.json')` to get the parsed value. 2. Mutate it inside `.then()` — push a row, set a field. 3. `cy.writeFile('cypress/reports/tenants.json', merged)` to write the whole document back. JavaScript arrays and objects are stringified and formatted for you, so writing `{ slug: 'acme' }` produces readable JSON rather than a one-line blob. ## Options at a glance | option | default | effect | |---|---|---| | `flag` | `w` | file-system flag; `a+` appends instead of overwriting | | `encoding` | `utf8` | pass `null` to write a `Buffer` without encoding it first | | `timeout` | `defaultCommandTimeout` | how long the command may take before it fails | | `log` | `true` | whether the command appears in the Command Log | The timeout row is worth holding on to. `cy.writeFile()` and `cy.readFile()` are governed by `defaultCommandTimeout`, while `cy.task()` is governed by `taskTimeout`, which defaults to 60000 ms. As of Cypress 16, `execTimeout` no longer exists at all — it was removed alongside `cy.exec()`, and setting it prints a warning and is otherwise ignored. ## When writing a file is the wrong instinct Writing to disk from a spec is genuinely useful for a small set of jobs: capturing a generated per-environment settings document for a later assertion, saving a response body as a fixture while building a suite, or leaving a machine-readable artefact for a pipeline step to pick up. It is a poor tool for anything else: - **Carrying state between specs.** The file lives on one machine's disk, so it only works when every spec runs on that machine, and it survives past the run in a way that hides ordering bugs. - **Recording evidence of a failure.** Cypress already writes screenshots and video for a failing `cypress run`; a hand-rolled file duplicates that and is not linked to the failure. - **Talking to the application.** A file is not an interface. If the spec needs the application to be in a state, ask the application or the Node side for it rather than editing a file the application happens to read. The rule of thumb is that a written file should be an **output** of the run — something a human or a later step consumes — and never an input the suite depends on to be correct.

  • How do you add a record to a JSON file with Cypress rather than overwrite it?
    Not with `{ flag: 'a+' }` — that appends raw text and produces an invalid document. Read the file with `cy.readFile()`, mutate the parsed value inside `.then()`, then write the whole merged document back with `cy.writeFile()`.
  • Which timeout governs cy.writeFile(), and which governs cy.task()?
    `cy.writeFile()` and `cy.readFile()` use `defaultCommandTimeout`; `cy.task()` uses `taskTimeout`, which defaults to 60000 ms and can be overridden per call. As of Cypress 16 `execTimeout` no longer exists — setting it prints a warning and is ignored.

saying these in an interview costs you the question

  • Expects cy.writeFile() to yield the contents it wrote
  • Assumes the path is relative to the spec file's folder
  • Thinks the browser writes the file rather than Node
  • Appends to a JSON file with flag a+ and expects valid JSON
  • Confuses taskTimeout with the timeout cy.writeFile() uses