Skip to content

Cypress - Data Driven Testing

Cypress - Data-Driven Testing with Fixtures

Section titled “Cypress - Data-Driven Testing with Fixtures”

Data-driven testing is a technique where test data is stored separately from the test scripts. This allows you to run the same test logic with multiple datasets, improving test coverage and maintainability. Cypress achieves data-driven testing primarily through its fixtures feature.

Fixtures are files used to store static test data. By default, Cypress looks for fixture files in the cypress/fixtures folder. These files can be in various formats, with JSON (.json) being the most common.

A typical cypress/fixtures folder structure might look like this:

cypress/
fixtures/
example.json
userCredentials.json
productData.json
integration/ (or e2e/ for Cypress 10+)
login_spec.js
// other test files

Fixture data is typically organized in key-value pairs, especially when using JSON.

The cy.fixture() command is used to load data from a fixture file. It can then be used within your tests.

cy.fixture(filePath)
cy.fixture(filePath, encoding)
cy.fixture(filePath, options)
cy.fixture(filePath, encoding, options)

Where:

  • filePath: Path to the fixture file (relative to the cypress/fixtures folder). You can omit the file extension if it’s .json.
  • encoding: Encoding type (e.g., ‘utf8’, ‘ascii’) used to read the file. Defaults to ‘utf8’.
  • options: An object to modify default behavior. For example, timeout can change the default wait time for the fixture to load.

Let’s create a scenario where we test a login form using data from a fixture file.

Create a file named cypress/fixtures/userLogin.json with the following content:

{
"username": "standardUser",
"password": "secret_sauce",
"invalidUsername": "lockedOutUser",
"invalidPassword": "wrong_password"
}

In your test file (e.g., cypress/integration/login_ddt_spec.js or cypress/e2e/login_ddt_spec.cy.js):

describe('Login Functionality - Data-Driven', () => {
// Load fixture data before tests run
before(function () {
cy.fixture('userLogin').then(function (data) {
// 'this.data' now holds the content of userLogin.json
// Using standard 'function' allows 'this' to refer to the Mocha context
this.loginData = data;
});
});
it('Should login successfully with valid credentials from fixture', function () {
cy.visit('https://www.saucedemo.com/'); // Example login page
cy.get('[data-test="username"]').type(this.loginData.username);
cy.get('[data-test="password"]').type(this.loginData.password);
cy.get('[data-test="login-button"]').click();
cy.url().should('include', '/inventory.html');
});
it('Should display an error with invalid credentials from fixture', function () {
cy.visit('https://www.saucedemo.com/');
cy.get('[data-test="username"]').type(this.loginData.invalidUsername);
cy.get('[data-test="password"]').type(this.loginData.invalidPassword);
cy.get('[data-test="login-button"]').click();
cy.get('[data-test="error"]').should('be.visible')
.and('contain.text', 'Username and password do not match'); // Adjust error message as per actual application
});
});

In this example:

  • The before hook loads the userLogin.json fixture once before any tests in the describe block run.
  • cy.fixture('userLogin').then(function (data) { ... }) makes the fixture data available as this.loginData within the test cases (it blocks). Using a standard function() for the callback is important here so that this refers to the Mocha test context.
  • The tests then use this.loginData.username, this.loginData.password, etc., to populate form fields.

If your fixture contains an array of test data sets, you can iterate over them:

Fixture cypress/fixtures/multipleUsers.json:

[
{ "username": "user1", "password": "pass1" },
{ "username": "user2", "password": "pass2" }
]

Test script:

describe('Multiple User Login', () => {
before(() => {
cy.fixture('multipleUsers').as('users'); // Alias the fixture data
});
it('should attempt login for each user', function() {
// 'this.users' is now available due to the alias in the before hook
this.users.forEach((userData) => {
cy.log(`Attempting login for ${userData.username}`);
cy.visit('https://www.saucedemo.com/');
cy.get('[data-test="username"]').type(userData.username);
cy.get('[data-test="password"]').type(userData.password);
cy.get('[data-test="login-button"]').click();
// Add appropriate assertions based on expected outcome for each user
// For simplicity, this example only logs and attempts login.
});
});
});

Using cy.fixture(...).as('aliasName') in a before or beforeEach hook makes the data accessible via this.aliasName in subsequent hooks and tests within the same scope.

  • Separation of Concerns: Keeps test data separate from test logic.
  • Reusability: The same fixture data can be used across multiple tests.
  • Readability: Makes tests cleaner and easier to understand.
  • Maintainability: Easier to update test data without modifying test scripts.

While fixtures are excellent for static data, for dynamic data generation or more complex data scenarios, you might consider using JavaScript/TypeScript modules or custom Cypress commands. However, for most common data-driven testing needs, fixtures provide an elegant and straightforward solution.