Making use of custom commands and fixtures to reduce repeated code in your Cypress automation

The array of original web applications released today is outstanding. From blogging platforms, marketing tools and even websites dedicated to sharing photographs of your beloved pet cat. The diversity in the web’s utilisation for users to work, play and get tasks done is huge.

But there’s some functionality that nearly every application out there today has in common with one another. Whether its to keep track of how often you use particular services. Enable the ability to provide a rich and tailored experience. Or just restrict access to allow certain users. The process of registration and logging in is one of the most common tasks that users have to do when they want to interact with a new web application.

And for testers trying to construct an automation framework or toolset. These can be some of the most challenging parts of any automation approach. Not because it’s notoriosly difficult. But more because there are several unique approaches to achieving the desired goal:

  • You could use a static account. Already pre-configured and authenticated.
  • You could create a new account on the fly. Simulating the process of a real user.
  • You could also seed the database with an API POST request. Allowing you to mock login responses and other core functionality.

Each one of these options has their own pros/cons and I would highly suggest taking the time to think about which one not only works for your situation. But which one will provide the most value to the project that you are working on.

In this example. We will be looking at the registration part of the puzzle. And introducing concepts and structure that can be easily transferred to features like logging in, or passing data to a server.

Creating a registration test

I will be using Docket for illustrative purposes, which is a web application built in Python to practice API testing. See my post on API Testing with Cypress if you’re interested about using Cypress for API testing.

I already have Cypress installed and created a fresh project, but if you don’t yet have Cypress installed and want to follow along. Follow these steps to get up to speed.

Inside the integration folder, create a new file and name it docker_registration.spec.js

If you haven’t used Docket before or unaware of its capabilities. It is essentially a simple web application that allows users to register an account, login with their credentials and create and change the status of to-do items. We won’t be automating the entire flow here. But maybe if you want you can use the concepts outlined here to automate more of the application.

The flow we will code is:

  • Load up the home page
  • Clicking the registration button
  • Filling in the form
  • Submitting our details
  • Asserting that we have successfully registered

And just like Blue Peter, here’s a script that I made earlier.

/// <reference types=“cypress” />

describe(“Testing Docket’s Registration functionality”, () => {
    it(“Registers a new account”, () => {
        cy.visit(“https://docket-test.herokuapp.com/“);
        cy.contains(
            “Docket is fully fledged Todo web application for you to practise and learn about API testing.”
        ).should(“be.visible”);
        cy.get(“a”).contains(“Register”).click();
        cy.contains(“Registration”).should(“be.visible”);
        cy.get(‘input[id="username"]’).type(“TestUser1”);
        cy.get(‘input[id="username"]’).should(“have.value”, “TestUser1”);
        cy.get(‘input[id="email"]’).type(“TestUser1@example.com”);
        cy.get(‘input[id="email"]’).should(
            “have.value”,
            “TestUser1@example.com”
        );
        cy.get(‘input[id="password"]’).type(“password123”);
        cy.get(‘input[id="password2"]’).type(“password123”);
        cy.get(‘input[type="submit"]’).contains(“Register”).click();
        cy.get(“.alert-info”).should(
            “contain”,
            “Congratulations, you are now registered”
        );
    });
});

Quickly walking through the above code. You can see that I am meeting all of my objectives. I am hitting the right end-point and ensuring I am on the correct page. Then after finding the registration link and making sure my transition to the page is correct. I am then filling in the form while making sure that the fields are keeping the text after I type it. And finally, I am pressing the register button and ensuring I am getting the alert to tell me my actions are successful.

While the above code works and fills my needs outright. I can see a lot of repetition that if left unchanged. Could lead to a solution difficult to maintain and at risk of breaking easily.

For example, the username and password information. Hard coding these values into my tests might work for now or when I want to prove that my code works. But what if I want to change them further on down the line? Luckily, a single test isn’t so bad. But if I have hundreds of tests using the same data. I could be setting myself up for a task that would take a long time to complete.

This is where Cypress fixtures come in handy as they allow you to use data stored in files within your test cases. There are many types of fixture files and file formats that you can use (check out the documentation to learn more and you can see some examples here).

To create a fixture, create a new file within the fixtures folder which will house our data. I’ve named mine user.json

The data we will store is the username, password and email address. All of which is held within a single object.

{
    “username”: “Test123”,
    “password”: “Password123”,
    “emailAddress”: “test@example.com”
}

Next, we need to make use of it in our test through the use of the cy.fixture command.

Here’s another I made earlier 😁

/// <reference types=“cypress” />

describe(“Testing Docket’s Registration functionality”, () => {
    before(function () {
        cy.fixture(“user”).as(“user”);
    });

    it(“Registers a new account”, function () {
        cy.visit(“https://docket-test.herokuapp.com/“);
        cy.contains(
            “Docket is fully fledged Todo web application for you to practise and learn about API testing.”
        ).should(“be.visible”);
        cy.get(“a”).contains(“Register”).click();
        cy.contains(“Registration”).should(“be.visible”);
        cy.get(‘input[id="username"]’)
            .type(this.user.username)
            .should(“have.value”, this.user.username);
        cy.get(‘input[id="email"]’)
            .type(this.user.emailAddress)
            .should(“have.value”, this.user.emailAddress);
        cy.get(‘input[id="password"]’).type(this.user.password);
        cy.get(‘input[id="password2"]’).type(this.user.password);
        cy.get(‘input[type="submit"]’).contains(“Register”).click();
        cy.get(“.alert-info”).should(
            “contain”,
            “Congratulations, you are now registered”
        );
    });
});

I have placed the fixture in Cypress’s before class so it is run once it before the start of my tests. But if you want to use different data or perform different tests. A beforeEach would work in its place. I have also refactored the code a little, making use of the ability to chain commands together. Making any assertions on field contents after I type into them. Negating the need to locate the element a second time.

The last thing I want to do is abstract this functionality into its own separate command. A task that is achieved with page objects in Selenium. But with Cypress, the preference is to use the built-in custom commands feature instead.

Adding custom commands to Cypress is achieved via the commands.js file that is located with the support folder of your Cypress project structure.

This file is loaded prior to test execution, so our tests will recognise and execute any command defined in this file.

We define custom commands by using the following syntax:

Cypress.Commands.add(name, (inputData) => { instructions })

And we can then call the command within our tests by using cy.name. Simple!

So for our needs, our custom command would look like this.

Cypress.Commands.add(“login”, (data) => {
    cy.contains(
        “Docket is fully fledged Todo web application for you to practise and learn about API testing.”
    ).should(“be.visible”);
    cy.get(“a”).contains(“Register”).click();
    cy.contains(“Registration”).should(“be.visible”);
    cy.get(‘input[id="username"]’)
        .type(data.username)
        .should(“have.value”, data.username);
    cy.get(‘input[id="email"]’)
        .type(data.email)
        .should(“have.value”, data.email);
    cy.get(‘input[id="password"]’).type(data.password);
    cy.get(‘input[id="password2"]’).type(data.password);
    cy.get(‘input[type="submit"]’).contains(“Register”).click();
    cy.get(“.alert-info”).should(
        “contain”,
        “Congratulations, you are now registered”
    );
});

This code is pretty much as it was in our previous test spec. But instead of using that data from a fixture file directly. I am passing in a data object and then passing the properties of that object to the required fields.

And my final spec file looks like this.

/// <reference types=“cypress” />

describe(“Testing Docket’s Registration functionality”, () => {
    before(function () {
        cy.fixture(“user”).as(“user”);
    });

    it(“Registers a new account”, function () {
        cy.visit(“https://docket-test.herokuapp.com/“);
        cy.register({
            username: this.user.username,
            email: this.user.emailAddress,
            password: this.user.password,
        });
    });
});

Much cleaner, easier to maintain and simple to understand what I am testing.

This is the power of fixtures and custom commands that Cypress makes possible!

You can find the completed code for this example on my GitHub. Feel free to modify it, extend it, or adapt it to fit your own use case.

I hope you found this example useful. Please let me know on Twitter or LinkedIn if there’s a Cypress topic that you would like to know more about.

Posted by Kevin Tuck

Kevin Tuck is an ISTQB qualified software tester with nearly a decade of professional experience. Well versed in creating versatile and effective testing strategies. He offers a variety of collaborative software testing services. From managing your testing strategy, creating valuable automation assets, or serving as an additional resource.

One Reply to “Making use of custom commands and fixtures to reduce repeated code in your Cypress automation”

  1. […] I would recommend taking a look at the ForEach loop that JavaScript offers. Combining these with fixtures is an excellent way to generate dynamic test cases from predefined data in JSON […]

    Reply

Leave a Reply