Automating End to End Testing of Web Applications with Cypress

Automating End to End Testing of Web Applications with Cypress

Testing the frontend of applications is easier said than done. And if you have built a frontend before, you have probably experienced this. While the testing of backends is typically more straightforward, frontend testing deals with additional challenges. One of those is dealing with user input. Sometimes this needs to be collected over multiple screens and through complex interactive flows. Instead of writing unit tests and testing single functions, we now need to mimic the behavior of a user and test if they can successfully perform a user story.

Software engineers have probably dealt with building and testing frontends before to various degrees. But from a data science perspective, this step also should not be overlooked. For example, as a data scientist you might find yourself working on a (prototype) application where users can interact with your model. And where manual testing might work when the application is still small, it quickly becomes time-consuming and annoying for bigger applications. Which can cause testers to rush through tests and potentially miss important bugs.

For our frontends we can write unit tests like with every other testing procedure. But more importantly, we want to test the functionality of the user interface. This is different then running a test on a single function or a combination of functions. Instead, we need to mock the user input to fully test the functionality of the frontend. Does the correct thing happen when a user clicks on X? Does the right validation happen when a user fills in input field Y? Does the whole flow from A to Z work and can it deal with all exceptions?

The need to automate frontend testing

But why do we need automated testing of the frontends of our applications? There are many reasons, but here are three I want to highlight:

  1. Manual testing is cumbersome and human mistakes can slip in

Manual testing can suffice when working on a small application or when you are still in the early phases, developing a POC. But once you start the scale up process, it can quickly become cumbersome. I don’t think anyone gets excited by 400 Jira test tickets and manually testing if each click and input field works. This also means that your team will be working on repetitive tasks each release, which opens the door for bugs to go unnoticed.

2. Developer hell; manually changing inputs while developing

I remember a previous project where an application was built that predicted the success of campaigns. In this app, users go through a multi-step process where they define the aspects of the campaign, compare it to historic campaigns and evaluate the predictions. As developers who wanted to test what they were working on, this meant that they sometimes had to go through steps 1–4, just to see if what you were developing in step 5 went successfully. And if you made a small change in the backend, you had to go through steps 1–4 again. At the end of the project, every developer knew way to many product codes by heart after going through the steps that many times. Thankfully this has now been automated.

3. Not being able to test due to dependency failure

When testing your application you want to be able to do that regardless of whether dependencies work or not. Let’s say your application is dependent on an external API of which the staging version is currently offline. Suddenly, you can’t do your testing anymore. When automating these tests we can mock these type of API responses such that the tests can always run regardless of whether those dependencies are working at that time.

Using Cypress for end to end testing

For the end to end testing of applications you might have heard of Selenium, which is probably the most popular tool for this purpose. However, in this blog I would like to introduce Cypress. Cypress uses a different setup than Selenium and therefore eliminates some of the common issues with Selenium. For example, Cypress runs in the same loop as your application, which means that it waits for DOM elements to appear. This eliminates the need for all the manual waiting steps in your tests required for Selenium.

Cypress focuses on end-to-end testing. Is is not a general testing framework, or a unit test framework, but instead focuses on end to end testing for web applications and does that very well. Below is a GIF that shows how running the integration tests in Cypress looks like. On the left you can see all tests and steps in the test, while on the right you see the tests being executed in the application.

An example of end to end testing in Cypress

Let’s write a first test in Cypress!

Writing and executing tests in Cypress is relatively simple and Cypress allows you to quickly get started. So, let’s get to it!

In this simple test we want to validate that clicking on the ‘Settings’ button in our application redirects us to the correct page, the ‘/settings’ page. We start the test by going to the home page on which the ‘Settings’ button is located. When running the tests, the Cypress UI will show all the tests, their statuses and you will be able to see everything Cypress is executing. For our test it will look the GIF below.

The Cypress UI executing our first integration test

The test described above consists of 3 actions:

  1. Go to the home page
  2. Click the settings button
  3. Validate that we are now on the /settings page

Let’s have a look at how this test would be written in Cypress by looking at the code block below. Notice the describe() and it() keywords at the start. These stem from the Mocha testing framework that Cypress uses. Describe() is used to group our tests together, and it() is used to describe a test case (it should be able to …).

describe('Settings', () => {
 it('clicking the settings button redirects to /settings', () => {
 cy.visit('/home')
 cy.get('#settings-button').click()
 cy.location('pathname').should('include', '/settings')
 })
})

Within the it() function are the 3 parts of the test described above.

  1. cy.visit(‘/home’) is used to visit the /home page. This ensures us that this test will be ran on the /home page, and is not dependent on what page the previous test ended.
  2. cy.get(‘#settings-button’).click() selects the settings button and then clicks it. Notice how we can find the button by simply using its identifier. In HTML simply give your button an ID you can reference in Cypress, <button id=”settings-button”>Settings</button>. Similarly, we can also retrieve other objects by using their class or even by its content using cy.containts().
  3. cy.location(‘pathname’).should(‘include’, ‘/settings’) is used to validate that we are now on the /settings page. The should() function is used to validate that the pathname includes ‘/settings’. Notice how these commands can be chained. Similarly to how we used the click() command on the selected button earlier.

This is how the tests look like in the Cypress UI. You can find the text that we put in the describe() and it() parts as well as the separate parts of the test.

Taking Screenshots and Videos

Next to running the tests in Cypress and seeing if they succeeded or not, you might also want to store screenshots made during the testing phase. This can be done by using the cy.screenshot() command. This is our test with two screenshots added in between:

describe('Settings', () => {
 it('clicking the settings button redirects to /settings', () => {
 cy.visit('/home')
 cy.screenshot()
 cy.get('#settings-button').click()
 cy.location('pathname').should('include', '/settings')
 cy.screenshot()
 })
})

This will cause Cypress to automatically store the screenshots in the ‘cypress/screenshots’ directory. This can be great if you want to keep a track record of new releases and how they were validated. For example, the screenshots can be used for compliance purposes, as a way to proof that the application looked correctly and tests passed successfully. Cypress also records a video of the test run, which can be uploaded to the Cypress dashboard. This will also allow you to re-watch all videos from tests that have been ran in CI/CD processes. Let me know if you would like to read more about this functionality in a future blog! Because integrating Cypress into your CI/CD process is where it will really shine.

The two screenshots stored by Cypress by running the code above


Automated testing AI applications .. was originally published in MIcompany-engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.