Fixtures — Loading External Test Data from JSON, CSV and Static Files

Hardcoding test data inside test files creates the same maintenance nightmare as hardcoding locators: change a username or product name and you edit 30 files. Cypress fixtures solve this by storing test data in external JSON files inside the cypress/fixtures/ folder. Load a fixture with cy.fixture('filename') and the data flows into your test as a JavaScript object — clean, centralised and easy to update.

Loading and Using Fixture Data

Fixtures are loaded asynchronously and yielded to the next command in the chain. You can use them directly in .then() callbacks or save them as aliases for use throughout the test.

// ── Fixture file: cypress/fixtures/users.json ──
/*
{
  "validUser": {
    "username": "standard_user",
    "password": "secret_sauce",
    "expectedProducts": 6
  },
  "lockedUser": {
    "username": "locked_out_user",
    "password": "secret_sauce",
    "expectedError": "Sorry, this user has been locked out"
  },
  "invalidUser": {
    "username": "wrong_user",
    "password": "wrong_pass",
    "expectedError": "do not match"
  }
}
*/


// ── Method 1: cy.fixture() with .then() ──

it('should login with valid credentials', () => {
  cy.fixture('users').then((users) => {
    cy.visit('/');
    cy.get('[data-test="username"]').type(users.validUser.username);
    cy.get('[data-test="password"]').type(users.validUser.password);
    cy.get('[data-test="login-button"]').click();
    cy.get('.inventory_item').should('have.length', users.validUser.expectedProducts);
  });
});


// ── Method 2: cy.fixture() with alias (recommended for reuse) ──

describe('Login Tests', () => {
  beforeEach(() => {
    cy.fixture('users').as('userData');
    cy.visit('/');
  });

  it('should login with valid credentials', function () {
    // Access alias with this.userData (requires function(), not arrow =>)
    cy.get('[data-test="username"]').type(this.userData.validUser.username);
    cy.get('[data-test="password"]').type(this.userData.validUser.password);
    cy.get('[data-test="login-button"]').click();
    cy.url().should('include', '/inventory');
  });

  it('should show error for locked user', function () {
    cy.get('[data-test="username"]').type(this.userData.lockedUser.username);
    cy.get('[data-test="password"]').type(this.userData.lockedUser.password);
    cy.get('[data-test="login-button"]').click();
    cy.get('[data-test="error"]')
      .should('contain.text', this.userData.lockedUser.expectedError);
  });
});


// ── Method 3: Fixtures for network stubbing ──

// Fixture file: cypress/fixtures/products.json
/*
[
  { "id": 1, "name": "Backpack", "price": 29.99 },
  { "id": 2, "name": "Bike Light", "price": 9.99 },
  { "id": 3, "name": "T-Shirt", "price": 15.99 }
]
*/

it('should display stubbed products from fixture', () => {
  // Intercept API and respond with fixture data
  cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts');

  cy.visit('/shop');
  cy.wait('@getProducts');

  cy.get('.product-card').should('have.length', 3);
  cy.get('.product-card').first().should('contain.text', 'Backpack');
});


// ── Fixtures for file uploads ──

// Place test files in cypress/fixtures/
// cypress/fixtures/avatar.jpg
// cypress/fixtures/report.pdf

it('should upload a profile photo from fixtures', () => {
  cy.get('input[type="file"]').selectFile('cypress/fixtures/avatar.jpg');
  cy.get('.upload-preview').should('be.visible');
});


// ── Fixture organisation for large projects ──
/*
cypress/fixtures/
  users/
    valid-users.json
    invalid-users.json
    admin-users.json
  products/
    catalogue.json
    empty-catalogue.json
  api-responses/
    products-200.json
    products-500.json
    checkout-success.json
  files/
    avatar.jpg
    report.pdf
    large-file-6mb.zip
*/

// Load nested fixture:
// cy.fixture('users/valid-users').then((users) => { ... });
Note: When using fixture aliases with this.userData, you must use a regular function() declaration — not an arrow function (=>). Arrow functions do not bind this, so this.userData would be undefined. This is a Mocha/Cypress convention: it('test', function() { this.alias }) works, but it('test', () => { this.alias }) does not. Alternatively, use cy.get('@userData') which works in both function styles.
Tip: Use fixtures with cy.intercept() to stub API responses for fast, deterministic testing. Instead of hitting a real API that might be slow or unavailable, respond with fixture data: cy.intercept('GET', '/api/products', { fixture: 'products.json' }). This makes tests run in milliseconds instead of seconds, eliminates dependency on backend availability, and lets you test error scenarios by swapping in error fixtures like products-500.json.
Warning: Fixture files are loaded from disk, which means they are static snapshots. If your API response format changes (a field is renamed, a new field is added), your fixture files become stale and your tests pass against outdated data. Schedule periodic fixture updates when the API contract changes, and consider contract tests to detect format drift automatically.

Common Mistakes

Mistake 1 — Using arrow functions with this.alias

❌ Wrong: it('test', () => { cy.get('[data-test="username"]').type(this.userData.username); })this.userData is undefined in arrow functions.

✅ Correct: it('test', function() { ... this.userData ... }) or use cy.get('@userData').then((data) => { ... }) which works everywhere.

Mistake 2 — Putting all test data in a single massive fixture file

❌ Wrong: One testdata.json file with 500 lines covering users, products, addresses, and API responses.

✅ Correct: Organised fixture folders: fixtures/users/, fixtures/products/, fixtures/api-responses/. Each file is small, focused, and easy to update independently.

🧠 Test Yourself

What is the primary advantage of using cy.intercept('GET', '/api/data', { fixture: 'data.json' }) instead of hitting the real API?