Cypress fixtures are actually just static JSON files that get loaded into your tests, which is less magical and more straightforward than most people realize.

Let’s see this in action. Imagine we’re testing a simple login form. We want to test successful login with valid credentials and a failed login with invalid credentials.

First, we’ll create a cypress/fixtures directory if it doesn’t exist. Inside, we’ll create two JSON files: validLogin.json and invalidLogin.json.

cypress/fixtures/validLogin.json:

{
  "username": "testuser",
  "password": "password123"
}

cypress/fixtures/invalidLogin.json:

{
  "username": "wronguser",
  "password": "wrongpassword"
}

Now, in our Cypress test file (e.g., cypress/e2e/login.cy.js), we can use these fixtures:

cypress/e2e/login.cy.js:

describe('Login Functionality', () => {
  beforeEach(() => {
    // Visit the login page before each test
    cy.visit('/login');
  });

  it('should allow login with valid credentials', () => {
    // Load the valid login fixture
    cy.fixture('validLogin').then((userData) => {
      // Interact with the form using fixture data
      cy.get('#username').type(userData.username);
      cy.get('#password').type(userData.password);
      cy.get('button[type="submit"]').click();

      // Assert that login was successful (e.g., redirected to dashboard)
      cy.url().should('include', '/dashboard');
    });
  });

  it('should display an error message with invalid credentials', () => {
    // Load the invalid login fixture
    cy.fixture('invalidLogin').then((userData) => {
      // Interact with the form using fixture data
      cy.get('#username').type(userData.username);
      cy.get('#password').type(userData.password);
      cy.get('button[type="submit"]').click();

      // Assert that an error message is displayed
      cy.get('.error-message').should('be.visible').and('contain', 'Invalid username or password.');
    });
  });
});

This setup provides a clear separation between your test logic and the data your tests operate on. It makes tests more readable, maintainable, and reusable.

The core problem Cypress fixtures solve is managing dynamic or complex test data. Instead of hardcoding values directly into your cy.type() or cy.get().should() commands, you externalize them. This is crucial because:

  • Readability: Test files become cleaner. Instead of seeing cy.get('#username').type('testuser'), you see cy.get('#username').type(userData.username), with the actual value defined elsewhere.
  • Maintainability: If your test data changes (e.g., a password needs updating), you only modify the fixture file, not every single test that uses that data.
  • Reusability: The same fixture can be loaded and used across multiple tests or even in different test suites.
  • Data Variety: You can easily create multiple fixture files to represent different scenarios (e.g., adminUser.json, regularUser.json, premiumUser.json) and load them as needed.

Internally, when you call cy.fixture('yourFixtureName'), Cypress looks for a file named yourFixtureName.json within your cypress/fixtures directory. It reads this JSON file, parses its content, and then passes the resulting JavaScript object to the .then() callback you’ve provided. This object is essentially your test data, ready to be used.

You can also alias fixtures for easier access within your tests. Instead of chaining .then() every time, you can do:

describe('Login Functionality with Aliased Fixtures', () => {
  beforeEach(() => {
    cy.visit('/login');
    // Alias the fixtures
    cy.fixture('validLogin').as('validUserData');
    cy.fixture('invalidLogin').as('invalidUserData');
  });

  it('should allow login with valid credentials', () => {
    // Use the aliased fixture data
    cy.get('@validUserData').then((userData) => {
      cy.get('#username').type(userData.username);
      cy.get('#password').type(userData.password);
      cy.get('button[type="submit"]').click();
      cy.url().should('include', '/dashboard');
    });
  });

  it('should display an error message with invalid credentials', () => {
    // Use the aliased fixture data
    cy.get('@invalidUserData').then((userData) => {
      cy.get('#username').type(userData.username);
      cy.get('#password').type(userData.password);
      cy.get('button[type="submit"]').click();
      cy.get('.error-message').should('be.visible').and('contain', 'Invalid username or password.');
    });
  });
});

This approach with .as() and @aliasName is cleaner when you need to reference the same fixture multiple times within a single test or across different tests within the same describe block.

You can also load multiple fixtures in a single cy.fixture() call by passing an array of names. Cypress will return an array of the parsed JSON objects in the same order.

it('should use multiple fixtures', () => {
  cy.fixture('validLogin', 'settings.json').then(([validUserData, settings]) => {
    // Use validUserData and settings here
    cy.log(`Username: ${validUserData.username}`);
    cy.log(`API Endpoint: ${settings.apiEndpoint}`);
  });
});

For this to work, you’d need a cypress/fixtures/settings.json file as well.

The most surprising mechanical detail about Cypress fixtures is that they are not limited to JSON. You can place any file type in the cypress/fixtures directory, and cy.fixture() will read its contents as a string by default. This is incredibly useful for scenarios like uploading files or interacting with non-JSON data. For instance, if you had a text file named sample.txt in your fixtures folder:

cypress/fixtures/sample.txt:

This is a sample text file for testing.

You could load and use it like this:

it('should read text file content', () => {
  cy.fixture('sample.txt').then((textFromFile) => {
    expect(textFromFile).to.equal('This is a sample text file for testing.');
    // You can then use this string, for example, to type into a textarea
    cy.get('textarea').type(textFromFile);
  });
});

This flexibility means cy.fixture() is your go-to for any static data, not just structured JSON.

Once you’ve mastered fixtures, the next logical step is to explore how to intercept and mock network requests using cy.intercept(), which often involves using fixtures as response bodies.

Want structured learning?

Take the full Cypress course →