What is e2e testing?
End-to-end (e2e) testing is a common type of software application testing that replicates a whole user workflow rather than a single piece of functionality. An e2e test is essentially the opposite to a unit test as described by the testing pyramid. Philosophically, unit tests are about testing a single unit of functionality. In terms of code this is often a single function, or a class method, or similar. E2e tests do the opposite, and test a workflow rather than a function. A workflow might, for example, be a user logging in to an app, checking the balance of their account, and logging out. E2e tests have the major benefits of testing multiple functions and components, and the interaction between them. This makes end-to-end tests especially useful for regression testing. The downside, however, of e2e tests is that they are slower to run as many different parts of a code base are being tested.
Typically, end-to-end tests should test the most common workflows, rather than every feature. With unit tests a team may aim for 100% code coverage, but with e2e tests this would likely result in a very slow runtime of the test suite. E2e tests commonly make API calls, render whole pages, or load resources, which make the slow and time consuming to run. This also means they can be flakier, and time out or crash for reasons outside of simply the correctness of the code base.
Let’s give a few example of possible end-to-end tests for a typical front end example project, a to-do list app:
- Logging in, adding a todo item, and logging out
- Logging in, adding three todo items, re-ordering the todo items, editing the text of a todo item, marking a todo item as done, then logging out
- Checking data persistence by checking presence of a todo item after logging out and then back in again.
- And so on.
Why is e2e testing useful
End to end testing is highly complementary to unit and integration level testing. As already mentioned, by testing common user workflows e2e testing ties together multiple functions, components, and parts of the code base. This allows greater confidence in systems and functionality due to those systems being tested together. It also allows testing of interactions that can be difficult in a unit or integration testing framework.
Cypress
Common testing frameworks for e2e tests in JavaScript include Cypress, Selenium, Nightwatch, Puppeteer and Testcafe. Here, I’m going to give a quick overview of Cypress. Cypress is a modern, fast, next generation framework for e2e testing. It has a clean interface with good documentation, and has a very cool video rollback feature which gives a good idea of the cause when tests fail. Personally, I’ve found Cypress to be faster than some other frameworks (e.g. Selenium) but slower than others (e.g. Puppeteer). I haven’t spent time optimising any of the above for speed though, so take my opinion with a pinch of salt! Cypress also has a great selection features and a wide range of helper functions, making it a good choice for many different projects.
Getting started with Cypress
Here we’re going to make a toy React project, and write a couple of basic tests and assertions with Cypress. Let’s dive straight in!
Create the react starter project
To keep things simple, let’s create a quick web app in React, and use Cypress to test that the React app renders correctly. We can create a configured starter project using create-react-app
at the command line:
npx create-react-app test-cypress-project
Install Cypress
Once we have created our test React app, next we need to install Cypress into the React app project. Luckily, installing cypress is a breeze. From the command line, navigate to the root folder of the React app project, and run:
npm i cypress --save-dev
Then we want to open Cypress for the first time, which will cause it to create a new folder of example tests and plugin support to be created. We can open Cypress from the command line by typing:
npx cypress open
This will cause the new “cypress” folder to be made in the project.
Explore Cypress
Let’s now get a handle on how Cypress works and a little of its functionality. First, we’ll start by testing that our React app is running on localhost. We’ll then test that certain HTML elements can be found on the page and are visible, and finish by looking at testing the HTML elements have certain attributes.
Test that components have rendered
First, create a new file in cypress/integration called react_tests.js
Next we need to check that our React app is running. In a terminal window, navigate to the project root directory and run:
npm run start
Cypress uses syntax that will be familiar if you have used Jest or Mocha for testing, in that it uses describe()
and it()
function to organise tests into logical groups. Let’s write our first test using describe()
and it()
function in our react_tests.js file. In that file, add the following snippet:
describe("visits the page", () => {
it("tests elements are visible", () => {
cy.visit("localhost:3000");
cy.contains("Learn React");
});
});
Here we start with a describe()
function, which takes a string and a callback function as first and second arguments. We pass an it() function as the callback argument. Similarly, the it() function also takes a string and call-back function as the first and second arguments. The two further lines of code are cy.visit(“localhost:3000”)
and cy.contains("Learn React”)
. Cy.visit()
visits a provided url, and asserts that an http response is received. The second interesting function is cy.contains()
, which will search for an element on the page the contains the text passed as an argument. This provides a nice API for selecting elements in cases where multiple elements won’t be matched with the passed string. Cypress will automatically assert that the element selected by the selector give to cy.contains()
is present in the DOM.
Now we want to run our tests in cypress. In a new terminal window (leave the old terminal window open to keep running our react app) navigate to the project root directory, and run
npx cypress open
This should once again open the cypress test runner window. In this window you should see the example tests that cypress autogenerates, as well as our react_tests.js file. In the cypress window, click the react_tests.js label to open and run our test.
We should see the tests all pass!
Let’s next look at how we can assert more information about selected elements. Elements selected by cypress (such as by cy.contains()
) supports a .should()
method. The .should()
method can take many different types of assertion, for example “have.css”
. The have.css
method allows us to assert that a css property is attached to the selected element. We are already testing whether we can successfully select an element with the content “Learn React” from the DOM, let’s now test that the selected element has the Font Family css property. And while we are at it, let’s illustrate that the cy.contains()
function that we have already used has additional functionality - it can select elements based on partial text matches, rather than needing the complete text. Let’s select the element containing the text “Edit src/App.js and save to reload.” by just asking Cypress to select an element containing the word “Edit”. And we can do both the selecting of an element with the text “Edit”, and test for it’s css property in 1 line of code, like so:
describe("visits the page", () => {
it("tests elements are visible", () => {
cy.visit(“localhost:3000");
cy.contains("Edit").should("have.css", "font-family");
cy.contains("Learn React");
});
});
If you still have the cypress test runner window open then the tests should automatically re-run when you save the new code. If not, open the test runner again with npx cypress open, and click the react_tests.js file.
Finally let’s wrap up with another method to select elements on the DOM in cypress, and how to assert elements are visible, and have html attributes and css classes. The additional method of selecting elements is the cy.get()
method. cy.get()
selects based on a css style selector. In this case, let’s select the spinning react image. As it is the only image on the page, we can simply select it with cy.get(“img”)
. We can then test for visibility, attributes and classes using a very similar chaining syntax to that already covered with the .should()
method. The only new addition compared to what we have already covered is that cypress supports a .and()
method when has the same functionality as a .should()
method; the .and()
is easier to read as it make the code more like written English. Add the following to our code for our final example:
describe("visits the page", () => {
it("tests elements are visible", () => {
cy.visit("localhost:3000");
cy.contains("Edit").should("have.css", "font-family");
cy.get("img")
.should("be.visible")
.and("have.class", "App-logo")
.and("have.attr", "src");
cy.contains("Learn React");
});
});
Again, if the cypress test runner is still open you should see the test run automatically upon save.
And that concludes this as a first, very brief introduction to end to end testing in Cypress. We have covered installing Cypress, creating a test project, opening Cypress, making a new test file, structuring tests inside describe()
and it()
functions, looked at visiting a website with cy.visit()
, selecting elements from the DOM with cy.contains()
and cy.get()
, and asserting that elements have css properties, are visible, have certain classes and html attributes. This is just the tip of the iceberg, however, and there is a huge amount of learn. I refer you to the Cypress documentation as a great source of information and some useful tutorials. Particularly good explanations can be found on writing and organising tests.
And that’s all! Thanks for reading. If you have any questions, get in touch at murray@loupetestware.com
Top comments (0)