ScreenshotNeo

BlogHow-to

How to Pass a Fixture String Dynamically to a Cypress Test

Use a variable with cy.fixture() for runtime data, or import JSON synchronously when fixture records must define Cypress tests.

By the ScreenshotNeo team30 September 20269 min read

How to Pass a Fixture String Dynamically to a Cypress Test

Pass the fixture path as a string variable: cy.fixture(fixtureName). Cypress resolves that path relative to its fixtures folder, yields the parsed value, and lets you use it inside the command chain.

const fixtureName = 'users'

describe('user data', () => {
  it('loads the selected fixture', () => {
    cy.fixture(fixtureName).then((users) => {
      expect(users).to.be.an('array')
      cy.log(`Loaded ${users.length} users`)
    })
  })
})

With the default configuration, Cypress looks in cypress/fixtures. A value such as users commonly resolves to cypress/fixtures/users.json when that file exists. Nested paths work too, for example users/admin.json. See the Cypress fixture API for the supported path and encoding behavior.

Runtime fixture data versus test definitions

The most important distinction is when the fixture value is needed.

Goal Use Reason
Load controlled data during a test cy.fixture(variable) The command runs in Cypress’s test command queue and yields the data asynchronously.
Create separate it() blocks from records Static import or require Test definitions must exist while the spec is being evaluated.
Read a file that can change during the run cy.readFile() Fixture contents are cached; readFile reads current contents.
Perform large-file or Node-side work cy.task() Moves filesystem and other Node.js operations out of the browser runner.

Cypress states that “The describe()/it() structure is built synchronously when the spec loads.” Therefore, a cy.fixture() call inside a test can provide data to that test, but it cannot add new test blocks after the spec has loaded.

Load a fixture selected at runtime

Use a variable directly

const fixtureName = 'users/admin.json'

it('uses the selected user fixture', () => {
  cy.fixture(fixtureName).then((user) => {
    cy.visit('/admin')
    cy.get('[data-cy=user-name]').should('contain', user.name)
  })
})

The variable must contain a path Cypress can resolve from the configured fixtures folder. Keep the variable as a relative path; do not prepend cypress/fixtures/ unless your configuration intentionally makes that part of the path.

A dynamic fixture path is resolved during test execution and its value is yielded into the Cypress command chain.
A dynamic fixture path is resolved during test execution and its value is yielded into the Cypress command chain.

Choose the fixture from test configuration

const fixtureByEnvironment = {
  staging: 'users/staging.json',
  production: 'users/production.json'
}

const fixtureName = fixtureByEnvironment[Cypress.env('target')] || 'users/default.json'

it('loads data for the selected target', () => {
  cy.fixture(fixtureName).then((data) => {
    cy.intercept('GET', '/api/profile', data).as('profile')
    cy.visit('/profile')
    cy.wait('@profile')
  })
})

Validate the selection before calling cy.fixture() so a misspelled environment value produces a useful failure:

const fixtureName = fixtureByEnvironment[Cypress.env('target')]

expect(fixtureName, 'fixture selected for target').to.be.a('string')
cy.fixture(fixtureName).then((data) => {
  // test continues here
})

Pass the loaded value to another Cypress command

const fixtureName = 'login.json'

it('fills the login form from a fixture', () => {
  cy.fixture(fixtureName).then((credentials) => {
    cy.get('[name=email]').type(credentials.email)
    cy.get('[name=password]').type(credentials.password, { log: false })
    cy.get('button[type=submit]').click()
  })
})

Do not assign the yielded value to a normal variable and use it immediately outside the callback. Cypress commands are queued, so code after cy.fixture() may execute before the fixture has resolved.

Use a dynamic fixture with cy.intercept()

Load the selected fixture first, then pass its object to cy.intercept():

const fixtureName = 'items/active.json'

it('stubs the items request', () => {
  cy.fixture(fixtureName).then((items) => {
    cy.intercept('GET', '/api/items', {
      statusCode: 200,
      body: items
    }).as('items')
  })

  cy.visit('/items')
  cy.wait('@items')
})

For a fixed fixture path, Cypress also supports the shorter direct response form:

cy.intercept('GET', '/api/items', { fixture: 'items.json' })

Use the direct form when the path is known in the test source. Use the callback form when the path is selected from a variable or when you need to transform the loaded data first.

Read raw text instead of parsed JSON

JSON fixtures are parsed as objects by default. Supply an encoding when you need the file contents as text:

cy.fixture('payload.json', 'utf8').then((rawText) => {
  expect(rawText).to.be.a('string')
  cy.get('textarea').invoke('val', rawText)
})

This is useful for asserting exact formatting, sending a text payload, or loading a non-JSON fixture with an explicit encoding. Check the fixture API documentation for supported encodings and extension resolution.

Generate tests from fixture records

If each fixture record should become its own test, load the JSON synchronously with a module import:

Static imports can define test cases while cy.fixture() supplies data after a test has started.
Static imports can define test cases while cy.fixture() supplies data after a test has started.
import scenarios from '../fixtures/scenarios.json'

describe('scenario coverage', () => {
  scenarios.forEach((scenario) => {
    it(scenario.title, () => {
      cy.visit(scenario.path)
      cy.get('body').should('be.visible')
    })
  })
})

CommonJS projects can use require instead:

const scenarios = require('../fixtures/scenarios.json')

scenarios.forEach((scenario) => {
  it(scenario.title, () => {
    cy.visit(scenario.path)
  })
})

This works because the bundler makes the JSON available while the spec is loading. Calling cy.fixture() in a before() hook is still too late to create additional it() blocks.

Choose between cy.fixture(), cy.readFile(), and cy.task()

Use cy.fixture() for stable test input

cy.fixture('products.json').then((products) => {
  cy.wrap(products).as('products')
})

Cypress caches fixture content. That is convenient for stable, reusable test data, but it means an external process changing the file during the test run will not automatically change the value returned by cy.fixture().

Use cy.readFile() for mutable or generated files

cy.readFile('cypress/fixtures/generated.json').then((currentData) => {
  expect(currentData).to.have.property('updatedAt')
})

Choose cy.readFile() when your application, a setup script, or another process writes the file during the run and the test must see the latest contents.

Use cy.task() for Node.js filesystem work

// cypress.config.js
const { defineConfig } = require('cypress')
const fs = require('fs/promises')

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on) {
      on('task', {
        loadLargeFixture(filePath) {
          return fs.readFile(filePath, 'utf8')
            .then((contents) => JSON.parse(contents))
        }
      })
    }
  }
})

// spec
cy.task('loadLargeFixture', 'cypress/fixtures/large.json').then((data) => {
  expect(data).to.be.an('object')
})

Keep Node-only APIs in the plugin process. A browser spec should not attempt to call fs directly.

Common mistakes and edge cases

  • Missing quotes: cy.fixture(users) treats users as a JavaScript variable. Use cy.fixture('users') or a defined string variable.
  • Wrong relative path: Paths are relative to fixturesFolder, which defaults to cypress/fixtures. Nested paths use forward slashes, such as accounts/admin.json.
  • Unexpected extension: Omitting an extension can work when Cypress finds the matching supported file, but adding .json removes ambiguity when similarly named files exist.
  • Fixture changed but old data appears: Fixture content is cached. Use cy.readFile() for data that changes during execution.
  • Dynamic test generation fails: Replace runtime cy.fixture() with a static JSON import or require at the top of the spec.
  • Binary data is corrupted: Use the appropriate encoding documented by the fixture API rather than treating binary content as ordinary UTF-8 text.
  • Secrets in fixtures: Do not commit production credentials. Use environment variables or a task that obtains test-only secrets through your existing secret-management process.
  • Path traversal input: If a path comes from user-controlled or external data, allow-list fixture names instead of concatenating arbitrary path segments.

Troubleshooting

Symptom Likely cause Fix
Cannot find module or fixture-not-found error The path is wrong, the file is outside fixturesFolder, or the extension is ambiguous. Confirm the file location, use a path relative to the configured fixtures folder, and include the extension.
fixtureName is not defined The variable was never declared or is out of scope. Declare it before the test and keep it in the same scope as the cy.fixture() call.
The value is undefined outside .then() The test reads a normal variable before Cypress resolves its queued command. Use the value inside .then(), alias it with .as(), or continue with Cypress commands.
One test appears for all records instead of one per record Tests were created inside an asynchronous command. Import the JSON synchronously while the spec loads, then iterate with forEach.
Network stub returns the wrong shape The fixture was loaded as text or the API expects a nested response object. Remove the encoding when you need parsed JSON, or wrap the value in the response shape your application expects.
Updated generated data is ignored The fixture cache is returning the earlier value. Use cy.readFile() or a cy.task() that reads the current file.
Large fixture slows the browser runner Parsing and holding a large object in the browser consumes memory. Read or transform it in a Node task, reduce the fixture to test-specific records, or split it into smaller files.

Performance and reliability guidance

  • Keep fixtures focused on the fields each test needs. Smaller files reduce parsing and command-queue work.
  • Load a fixture once in a beforeEach hook when every test needs the same stable data; avoid repeatedly loading it inside nested commands.
  • Use aliases when several commands consume the same value:
beforeEach(() => {
  cy.fixture('users.json').as('users')
})

it('uses the alias', function () {
  expect(this.users).to.be.an('array')
})
  • Keep fixture paths deterministic. An allow-list of known names makes failures easier to diagnose than constructing paths from unchecked input.
  • Use cy.readFile() only when freshness matters; stable fixtures are simpler and benefit from Cypress’s caching behavior.
  • For parallel CI runs, ensure each test writes generated files to an isolated location or uses unique names so one worker cannot overwrite another worker’s input.

Or skip the browser setup

If your goal is to capture a page rather than run a Cypress browser workflow, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. Its consent handling accepts cookie banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also provides an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools. It supports full-page and element captures, device presets, custom viewport and retina scale, PDF settings, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, resizing, caching, signed links, async jobs, bulk capture, and a usage API. Every feature is available on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account and get 1,000 screenshots each month with no card.

FAQ

Can the fixture name come from Cypress.env()?

Yes. Read the environment value, map it to an allow-listed relative path, and pass that string to cy.fixture().

Does cy.fixture() reload a file for every call?

Fixture contents are cached. Use cy.readFile() when the file can change during the run.

Can I use a fixture to create tests dynamically?

Not with cy.fixture() inside a test or hook. Import the JSON synchronously so records are available while Cypress registers the spec’s it() blocks.

Should I use cy.intercept({ fixture: … }) or load the object first?

Use the direct fixture response for a fixed path. Load the object first when the path is variable or the response needs transformation.

Where should very large fixture processing happen?

Use cy.task() for Node.js filesystem work and return only the data the browser test needs.