JavaScript testing #6. Introduction to End-to-End testing with Cypress

JavaScript Testing

This entry is part 6 of 18 in the JavaScript testing tutorial

So far, we’ve covered quite a lot of different types of tests. Some of them were unit tests or integration tests, where we tested a specific part of our implementation or components working together. In this article, we look into End-to-End testing and perform it using the Cypress framework.

The principles of the End to End (E2E) testing

The End to End testing is an approach in which we test a fully working application. By doing so, we can test whether our app behaves correctly in a certain scenario from start to finish. By performing tests using many different parts of our application working together, we can test real-life situations. This is very helpful when we want to make sure that our application meets the requirements. It might also act as regression tests, where we make sure that our new features have not broken any previous scenarios. We might also use them as automated smoke tests, where we make sure that our new build is functional and the crucial functionalities are working.

The smoke testing term is an analogy to electronics and refers to the very first moments of powering up the circuit. If there is no smoke rising from your device, it looks promising!

Since End-to-End (E2E) tests aim to check the whole system in a way that is going to be used by users, it means doing so through the user interface. Since we write web applications, it involves the web browser. This makes our E2E tests relatively slow compared to other types of tests.

Introducing Cypress

Some time back, our framework of choice might have been Selenium. Cypress does not use it under the hood, though, since it has a new architecture. If you tried Selenium in the past and had an unpleasant experience, you might want to give Cypress a chance. It specializes in performing End-to-End tests through the browser and is framework-agnostic – we can use it with any JavaScript framework of our choosing.

Let’s learn by implementation! To start using Cypress, we can add it to our project through npm.

Cypress has its test runner. To make opening it up to be simple, let’s add a new entry in the scripts in our package.json file.

package.json

Running   for the first time, generates a new directory for us. Let’s look into it.

The fixtures directory holds the data that the tests can make use of. We run automated tests multiple times, and to ensure that the process is deterministic, we might want to run the test in some particular, constant state. Such a state is called a fixture, and we might use it to mock some responses from the back end to ensure their immutability.

The integration directory is a place to put our test files, although we might configure it differently. Cypress recognizes every file in the integration directory with a proper extension as a test.

cypress runner

The plugins directory contains files that extend and modify the behavior of Cypress. You can install one of the provided plugins or create your own.

The support directory might be a fitting place to put some reusable logic that we want to apply to every test. It holds the behavior that runs before every single one of our tests.

Writing our first tests

In the previous parts of the series, we use the Jest test runner. Its API is similar to mocha and chai that the Cypress is built on to of. Thanks to that, we might feel at home here.

Let’s create our first test.

/cypress/integration/homepage.spec.js

When we open the test runner, the above test is available. Running it results in success!

We can improve the above test by utilizing the   file generated at the very top of our file structure. Among other properties, we can use the  to set a URL as a default.

Now, when we use the   function, it uses the   as a prefix.

Interacting with elements

Cypress has a lot of functionalities built to enable us to interact with the DOM tree. The most basic one is the   function that can find an element using a selector. It is designed in a way that matches the behavior of   from jQuery.

Once we find the desired element, we can interact with it using a set of functions like click and type.

For more possible commands, check out this page in the documentation.

/cypress/integration/search.spec.js

The essential thing is that the   function automatically retries until all the elements exist in the DOM tree, and all of our assertions have passed. It simplifies our code greatly because we don’t have to worry about it. Cypress treats our assertions as a description of how the application should look like and automatically waits, blocks, and retries until the state is reached.

We can configure the timeout of the above operations through cypress.json

Our test results in success!

cypress search test

The assertions describe the desired state that we expect. An important note is that some functions have a built-in assertion. This also concerns some of the functions that we’ve used above in this article. For example, if the   function does not find the desired element eventually, the assertion fails.

Also,   expects the page to load with the   status code and the   content type. This is why our first code that contains just   works as a test! With Cypress, we don’t necessarily have to assert to have a useful test.

Often used assertions

The assertions in cypress are designed to resemble the English language. They come from the Chai library, and we can look up the full list there. In the upcoming parts of this series, there will inevitably be more examples of different assertions. If you need a good reference point of what assertions are possible, you can check out this page in the documentation.

Value

Text

State

Length

Class

CSS

The crucial thing to remember is the Cypress framework retries the above assertions until the requirements are met.

Also, an important note is that you can also reverse any assertion. Writing   makes the Cypress expect that the text won’t appear. We can also apply the above rule to default assertions, such as the one built into the   function. To do so, we need to use the existence assertion.

Summary

In this article, we’ve learned the very basics of the Cypress framework. We’ve written some first tests and understood how they work. With the basics of how to visit pages, interact with them, and assert, we can write a lot of meaningful tests. I encourage you to do so before the next part of the series. The Cypress framework has a lot of functionalities, so we’ve still got quite a bit to cover. Also, we will look into implementing Cucumber and Gherkin into our E2E tests, so stay tuned!

Series Navigation<< JavaScript testing #5. Testing hooks with react-hooks-testing-library and ReduxJavaScript testing #7. Diving deeper into commands and selectors in Cypress >>
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments