End to end testing using Cypress (part 2)

Ignacio Machado
Devgurus
Published in
5 min readOct 29, 2021

--

At DMI we build quality, cutting edge e-commerce experiences and are proud members of the MACH alliance. Quality is at the center of our culture and our embrace of continuous testing and automation is a big part of that.

For part 1 of this article series, check out End to end testing using Cypress (part 1)

Hello world!

In this article, we will try to demonstrate the most basic form of a front-end test using Cypress. Let’s start by building a basic scenario and start exploring what Cypress is all about.

Every test we do will always follow the same structure

Set application state → Take some action → Compare new state against expectations.

Inside the integration folder, add the following test code.

In this case, we are setting the initial application state by using cy.visit() inside our beforeEach() block, we are updating it by using cy.click(), and then verifying expectations using cy.url().should().

Another thing of note to fully understand our example is that Cypress offers common hooks like before() and beforeEach() and their counterparts after() and afterEach().

Test runner

Cypress offers this nifty feature allowing for real-time debugging of our tests.

The test runner allows us to hover over a step and see the state of the website at that time and also logs all kinds of useful information for us. In the event a command passes we can check the website state to make sure that we are not falling prey to a false positive. In the case of a failing step, we can see a log of exactly why it failed. This will generally be enough to understand why it went wrong and how to remediate it. For example, we might see an error telling us that the element was not found when trying to do a cy.get() on it probably meaning that our selector is wrong or that we didn’t allow enough time to pass for the application to update.

Selectors

These are how we find elements within the DOM. The concept of selectors is not unique to Cypress, if you have ever manipulated a website using JavaScript or jQuery, or applied styles using CSS style sheets you are probably familiar with them. For further reading on selectors, the list of possible selectors is better explained here.

Building complex selectors to find DOM elements is a good skill to have but they come with risks. As we mentioned in the last article we should try to avoid including implementation details in our tests as much as possible. The importance of this comes from our inability to control how the DOM changes when developers update it. Therefore we cannot guarantee that these selectors will still work after some time.

One strategy to mitigate this is to use unique selectors, for example, ids. These are supposed to be unique, but they also come with risks since they can also be used by developers in the application logic and their tests and also by people working in stylesheets that can make changes to them and unknowingly break our tests.

To avoid these problems we will follow the good practice recommended by the Cypress team to add our own unique identifiers to relevant elements using the [attribute=value] attribute. The goal is to have unique identifiers that are unlikely to change when implementation and styles change.

Commands

Cypress offers a wide range of commands enabling us to set and update the application state, I’m not going to go over a detailed description of each command we use in this article because the official documentation is excellent and will serve you better than my interpretation of them.

In the case of our example, we are using cy.visit() to open a given URL in the browser, cy.get() to find and return an element matching the selector passed in the parameters, and cy.url() finds and returns the current URL as a string.

Assertions

Cypress comes bundled with Chai, a popular assertion library. If you are unfamiliar with these types of libraries then all you need to know is that they allow us to ask questions about the application state as we manipulate it with Cypress to validate expectations.

In our example, we are using the cy.url() command that returns the URL as a string and then chaining .should() to verify that it matches the expected value.

Removing implementation details

As explained in the previous article, the ultimate goal is to have a framework that not only checks for functional requirements but does so in a maintainable and scalable manner. For that purpose, we will be removing code that is repetitive or that has references that are likely to change.

In practical terms that means that for example, we are going to remove all our selectors to a different file. This is a good idea because in the best-case scenario in which we are using the [data-cy-*] selector and we need to change it we will have to do so only once even if that selector is being used by hundreds of scenarios. And in the worst-case scenario in which we are relying on classic selectors that not only can change but are likely to, this becomes even more important.

We will simply create a new file and store our selectors there. If for some reason, you are unable to add your own selector to the front-end you can still use a complex selector as we showed in the hello world example, it will work just as well but it will be trickier to maintain in the future.

And our simple hello world example looks like this.

In a scenario in which this selector needs to be used by multiple tests, all we have to do is use the same reference to “accesoriesLink”, saving us from having to repeat code and make multiple changes if or when it changes.

Conclusion

This is the most basic form of a Cypress E2E test, in future articles I will be exploring removing the remaining implementation details into a Page Object and showing off more features offered by Cypress like fixtures and tasks.

--

--