skip to content

In a Cypress run, how many times does cy.fixture() actually read a file from disk?

level: seniorimportance: should knowfreq 36%

answer

  1. It is not a fresh read every time
  2. The key is more than the path
  3. Encoding participates in the lookup
  4. Each caller gets its own copy
  5. One command invalidates the entry

basics

~20 s

Once per path and encoding. Cypress caches the parsed contents in the browser-side driver and serves every later call from that cache, handing back a fresh clone each time so one test cannot corrupt another's copy.

solid answer

~40 s

The first `cy.fixture('receipts.json')` goes to disk; every later call for the same path and encoding is served from a cache held in the driver, so ten tests in a spec cause one read. The cache key includes the encoding, so asking for the same file as `utf8` and as `null` is two reads. Each call returns a **deep clone** of the cached value, which is why a test that mutates the object it received does not poison the next test. Since Cypress 15, a `cy.writeFile()` whose target lands inside `fixturesFolder` invalidates the matching entries, so rewriting a fixture mid-run does show up. Any other change on disk does not — for a file that moves under you, use `cy.readFile()`, which re-reads and retries.

code

javascript · 12 lines
javascript
it('edits its own copy of the receipts fixture', () => {
  cy.fixture('receipts.json').then((receipts) => {
    receipts[0].amountCents = 999999
  })
})

it('still sees the original amounts', () => {
  // served from the cache, but as a fresh clone
  cy.fixture('receipts.json').then((receipts) => {
    expect(receipts[0].amountCents).to.equal(1450)
  })
})

go deeper

for a junior

Be ready to say that Cypress loads a fixture once and reuses it, and that changing the object your test received does not change the file in cypress/fixtures.

for a middle

Explain the mechanics: a driver-side cache keyed by path and encoding, a clone returned to every caller, and cy.writeFile() as the one thing that invalidates an entry inside the fixtures folder.

for a senior

Show that you have debugged a stale-data failure. Explain how a cached fixture produces a silent assertion mismatch far from its cause, and when you would switch the whole flow to cy.readFile().

for a principal

Own the rule for the suite: fixtures as immutable inputs, generated data kept out of the fixtures folder, and a documented answer for teams that want tests to rewrite their own canned data mid-run.

## One read per path and encoding `cy.fixture()` is deliberately not a file-watching command. The first call for a given path goes to the Cypress Node process, reads and parses the file, and stores the result in a cache that lives in the browser-side driver alongside your tests. Every later call for that path is answered from memory. A spec with fifteen expense-report tests that each start from `cy.fixture('receipts.json')` performs **one** disk read, not fifteen. The cache key is the pair *(path, encoding)*, not the path alone. That has a few practical consequences: - `cy.fixture('scans/receipt.png')` and `cy.fixture('scans/receipt.png', null)` are two entries and two reads, because one yields base64 and the other a `Cypress.Buffer`. - Asking with an explicit encoding and asking without one are likewise distinct entries, since passing an encoding also skips the extension-based parsing. - Two spellings of the same file — with and without the extension — are separate keys as well. ## Every call hands back a clone The cache stores one copy; each call returns a **deep clone** of it. This is the part candidates most often get wrong in both directions. A test that does this: ```js cy.fixture('receipts.json').then((receipts) => { receipts[0].amountCents = 999999 // local edit }) ``` has not damaged anything. The next test's `cy.fixture('receipts.json')` gets a clean clone of the original cached value. Mutating fixture data in a test is safe **for other tests**; what it does not do is change the file, and it does not change what a later call in the *same* test receives either, because that later call clones the cache afresh rather than handing back your edited object. ## What invalidates the cache Since **Cypress 15**, `cy.writeFile()` invalidates fixture cache entries when the file it wrote resolves to a path inside `fixturesFolder`. So the sequence people expect to work does work: 1. `cy.fixture('receipts.json')` — reads and caches. 2. `cy.writeFile('cypress/fixtures/receipts.json', updatedReceipts)` — writes, and invalidates every cached encoding of that fixture. 3. `cy.fixture('receipts.json')` — misses the cache and reads the new contents. Plenty of older advice on the internet asserts the opposite, that a fixture rewritten during a run is invisible until the next run. That was true once; as of Cypress 16 it is not, provided the write went through `cy.writeFile()` and landed inside the fixtures folder. ## What does not invalidate it The invalidation hook hangs off `cy.writeFile()` specifically. Nothing else in the loop tells the driver its copy is stale: - A file rewritten by the application under test, or by a build step running alongside the suite. - A file rewritten by a `cy.task()` handler in Node, or by any process outside Cypress. - A write whose resolved path lands **outside** `fixturesFolder`, even if it is the same file reached by a different route. For any of those, reach for `cy.readFile()` instead. It caches nothing, re-reads on every retry attempt, and keeps retrying until the assertions you chained pass — which is exactly the behaviour a changing file needs. ## Why the cache is there at all The behaviour is a deliberate trade, not an oversight. Fixture files are canned inputs, and canned inputs are assumed not to move while the suite runs: - Every read is a round trip from the browser to the Cypress Node process and back, so caching turns a per-test cost into a per-spec one. - Parsing happens once too, which matters for a large JSON file that thirty tests share. - The guarantee that every test in a spec starts from identical data is worth having on its own — it removes a whole class of order-dependent failure. What you give up is freshness, and the command that gives it back is `cy.readFile()`. ## Reading the symptom in a failing spec The failure this produces is quiet and confusing, because nothing errors. A test asserts on data it believes it just changed, sees the old values, and reports a plain assertion mismatch two layers away from the cause. Three habits shorten the hunt: - Treat a fixture as **immutable input**. If a test needs different data, load a different fixture or edit the object in memory and pass that object on, rather than rewriting the file. - When data genuinely must change during a run, name that intent by switching to `cy.readFile()`, which has no cache to be stale. - Remember that `cy.fixture()` does not appear in the Command Log, so time travel will not show you a fixture read at all — the value only becomes visible in the command you passed it to. ## The one-line version `cy.fixture()` is a cache with a file behind it, not a file read. That is a feature for canned inputs that never move during a run, and a trap for anything that does.

  • A cy.task() rewrites cypress/fixtures/receipts.json mid-spec. Will cy.fixture() see it?
    No. Only `cy.writeFile()` invalidates the fixture cache, and only for paths inside `fixturesFolder`. A file rewritten by a task, by the application, or by any other process leaves the cached copy in place, so `cy.fixture()` keeps serving the old contents. Read it with `cy.readFile()` instead.
  • Why does passing null as the encoding cause a second read of the same fixture?
    Because the cache key is the path plus the encoding. `cy.fixture('receipt.png')` and `cy.fixture('receipt.png', null)` are different keys, and they must be: one yields a base64 string and the other a `Cypress.Buffer`. Each variant is read and cached independently.

Think of it as a photocopy taken the first time anyone asks for the file: everyone gets their own copy of that photocopy, so scribbling on yours harms nobody, but nobody sees the original change unless someone deliberately retakes the copy.

saying these in an interview costs you the question

  • Says cy.fixture() re-reads the file on every call
  • Fears that mutating fixture data leaks between tests
  • Thinks editing the object rewrites the file on disk
  • Expects a file changed by the app to be picked up
  • Cannot name cy.readFile() as the uncached alternative