TL;DR: This post is an introduction on using Cypress for writing browser-based automation tests for web applications. This tutorial uses a React application as an example to illustrate the testing framework, but you can apply the learnings to write integration tests for any web application.
The source code for the React application and the corresponding Cypress tests are available in this repository. The
master
branch contains the code needed to start this tutorial and the cypress-tests
branch contains the final setup with all Cypress tests.“Learn how to use Cypress to write end-to-end tests that help you validate your web application automatically.”
Tweet This
The Test Pyramid and Importance of E2E tests
A typical test suite for your application can generally consist of three layers (you could have many more depending on the nature of the application):
Numerous tools and libraries help you write tests that offer a safety net to your application at each of the layers listed above. But, when it comes to the level of redundancy to test coverage and which layer should assert what, the following two rules might help:
- Write tests with different granularity.
- The more high-level you get the fewer tests you should have.
Also, the feedback cycle for tests in each of the layers can vary. The unit tests offer the lowest latency on feedback and the latency increases as you go up the pyramid. For this post, we will focus on E2E tests and specifically on user interface tests.
User Interface (UI) tests, or UI tests, are tests that use the system just like a real user would interact with the application on the browser. They use scripts to mimic users' actions and they basically interact with the app just as a regular user would. Cypress is one of the many web automation tools available to help you write effective user interface tests.
How Is Cypress Different as a UI Testing Tool?
Most browser-based automation tools are selenium-based, which works by implementing a web driver that remotely executes commands on the browser through the network. Whereas, Cypress runs in the same run-loop as your parent application. Also, Cypress tests are only written in JavaScript. While you can compile down to JavaScript from any other language, ultimately the test code is executed inside the browser itself.
These architectural improvements unlock the ability to run tests much faster in browser mode as well as headless mode. You can read more about the internal details of Cypress here. As you start writing more Cypress tests and build the pipeline with a test suite, you will see that many aspects of writing automation tests have improved with this library.
Writing Your First Cypress Test
To see Cypress in action, you will take advantage of an existing to-do app written with React. Don't worry if you don't have experience with React per se. Cypress can be used with any other front-end technology like Vue.js or Angular and you will use an existing React app here just to avoid having to set up too much stuff before diving into Cypress.
Below, you can see a screenshot of the application that you are going to use while learning about Cypress.
As you can see, this app has a list of to-do items that are either open or completed. A to-do item can be completed or deleted using the action icons present on the item. The text field and the button can be used to add a new to-do item.
To get started, you will first clone the sample application and install its dependencies:
git clone https://github.com/auth0-blog/cypress-todo-example.git cd cypress-todo-example yarn install
Note: You could easily use
instead ofnpm
. It is more a matter of taste here.yarn
When you are done installing all the dependencies, run
yarn start
to run the application in the browser to make sure it looks good. The same application is also available in codesandbox if you have trouble running it on your machine.Now, you will bootstrap Cypress and write your first test. With the application running, open another terminal session and run the following commands inside the same folder (
cypress-todo-example
):yarn add --dev cypress yarn cypress open
Cypress will sense that you are running it for the first time and create a folder called
cypress
with necessary files to get you going. It would also launch the cypress test runner. Think of it as a GUI for running/debugging your automation specs.After that, you will create a file called
todo_spec.js
in the cypress/integration
directory. You might need a third terminal for this (one for running the sample, one for Cypress, and one for creating new files. Or, even better, you can use an IDE like WebStorm or Visual Studio Code for this last task.Note: You should already see an
file that contains tests for the sample Kitchen Sink application with plenty of documentation for various scenarios. It is recommended that you read through the file at a later point in time for reference.example_spec.js
In this new file, add the following code:
//cypress/integration/todo_spec.js describe('Todo App', function () { it('.should() - allow adding a new todo item', function () { cy.visit('http://localhost:3000'); cy.get('input[data-cy=newItemField]').type('Write Test'); cy.get('#addBtn').click(); cy.get('tr[data-cy=todoItem]:nth-child(1)').should('contain', 'Write Test') }) });
The test is pretty self-explanatory. It just connects to your app (
cy.visit('http://localhost:3000');
), then it types 'Write Test'
to the input[data-cy=newItemField]
field, then it clicks on the #addBtn
button so it can validate that the first tr
element contains the 'Write Test'
string.The
cy
variable that you are using to interact with your application is a global Cypress object that drives the tests. It has various helper methods to visit web pages, interact with web elements, and also to fetch data present in the DOM.To learn more, you can check the excellent documentation on Cypress and the sample application that you would be frequently referring to as you add more test cases to your application.
Also, note that Cypress bundles the
assertion library and the matchers you see in the test is chained to the Cypress helper methods seamlessly for readability.chai
Using the Cypress Test Runner
When you have saved the
todo_spec.js
file, it will start appearing in the Cypress test runner.When you click on
todo_spec.js
in the test runner, a new browser instance will open up and run the test visually. You can observe Cypress hop through each step that you wrote in the todo_spec.js
test.If everything works properly (as it should), you will see something similar to this screen:
The left pane is called the Command Log and lists the name of the test followed by each step in sequence and its outcome. When the test runs, you can see Cypress interacting with the application on the browser webview to the right and you see that the new todo item is added to the list successfully. Lastly, you can see that the last step on the left is the assertion to verify if our new item is present in the list.
One very helpful feature on the browser view is the DOM selector that is shown highlighted below the internal URL address bar (not the real one from the browser, but the one from the inner browser). It will appear when you click on the Open Selector Playground icon to the left of this address bar. You can type in a selector in the textbox like you have entered
#addBtn
and Cypress will highlight the selected item for you. Or you can select an element from the screen and Cypress will try to find the optimal selector for you to use in your test.Setting up Continuous Integration for Cypress
The whole point of writing and maintaining browser-based automation tests is to test the application for any flows that break and give early feedback to the team. You can set up the suite of Cypress tests to run on your continuous integration (CI) server to run on every check-in or as a nightly build.
In this section, you will go through the steps needed to set up the build and to any particular CI tool.
Most CI build agents are servers and not workstations, so you cannot expect them to have a GUI or X Window system to launch a real browser UI. So, it is a common practice to run web automation tests in headless mode. To keep it simple, you will use a virtual GUI buffer like
to fire a web browser virtually, launch the target application, and run Cypress tests against it.xvfb
Assuming, you are setting up a Docker agent on the CI server to run the tests, this is the specification for the
Dockerfile
:FROM cypress/base // Clone your code base here RUN npm install RUN $(npm bin)/cypress run
Refer to this page for setting up Cypress on your favorite CI server.
“Cypress can be easily integrated into continuous integration tools to validate your web apps on each development iteration.”
Tweet This
Time Travel and Debugging with Cypress
Another cool feature present in the Cypress Test Runner tool is that as you hover through the command log on the left, for each step in the test, you can check the state of the application on the right. You can also click on the pin icon to freeze the runner for that particular step, open the developer console and get insights into the specific web interactions that happened, the selectors used, parameters passed and results.
“Testing web applications with Cypress is awesome because you even get access to features like time travel.”
Tweet This
Postmortem Analysis on Cypress
The last thing you want with a browser automation test is false positives. By that, a test case failing because of the way the automation test was written and not because something in the actual application is broken. When that frequently happens, the test suite loses its credibility and a red build is not worth its attention anymore!
So, when a test fails on the CI, you need enough trails and artifacts to understand what really went wrong. Like most automation frameworks in the market, Cypress generates screenshots and videos of the entire test runs if configured correctly. All of these settings can be tweaked using the
cypress.json
configuration file generated in the root of your project. By default, screenshots and video recording are turned on and you can see them generated inside the cypress
folder after running the tests.Check out the configuration documentation for additional settings and options.
Aside: Auth0 Authentication with JavaScript
At Auth0, we make heavy use of full-stack JavaScript to help our customers to manage user identities, including password resets, creating, provisioning, blocking, and deleting users. Therefore, it must come as no surprise that using our identity management platform on JavaScript web apps is a piece of cake.
Auth0 offers a free tier to get started with modern authentication. Check it out, or sign up for a free Auth0 account here!
Then, go to the Applications section of the Auth0 Dashboard and click on "Create Application". On the dialog shown, set the name of your application and select Single Page Web Applications as the application type:
After the application has been created, click on "Settings" and take note of the domain and client id assigned to your application. In addition, set the Allowed Callback URLs and Allowed Logout URLs fields to the URL of the page that will handle login and logout responses from Auth0. In the current example, the URL of the page that will contain the code you are going to write (e.g.
http://localhost:8080
).Now, in your JavaScript project, install the
library like so:auth0-spa-js
npm install @auth0/auth0-spa-js
Then, implement the following in your JavaScript app:
import createAuth0Client from '@auth0/auth0-spa-js'; let auth0Client; async function createClient() { return await createAuth0Client({ domain: 'YOUR_DOMAIN', client_id: 'YOUR_CLIENT_ID', }); } async function login() { await auth0Client.loginWithRedirect(); } function logout() { auth0Client.logout(); } async function handleRedirectCallback() { const isAuthenticated = await auth0Client.isAuthenticated(); if (!isAuthenticated) { const query = window.location.search; if (query.includes('code=') && query.includes('state=')) { await auth0Client.handleRedirectCallback(); window.history.replaceState({}, document.title, '/'); } } await updateUI(); } async function updateUI() { const isAuthenticated = await auth0Client.isAuthenticated(); const btnLogin = document.getElementById('btn-login'); const btnLogout = document.getElementById('btn-logout'); btnLogin.addEventListener('click', login); btnLogout.addEventListener('click', logout); btnLogin.style.display = isAuthenticated ? 'none' : 'block'; btnLogout.style.display = isAuthenticated ? 'block' : 'none'; if (isAuthenticated) { const username = document.getElementById('username'); const user = await auth0Client.getUser(); username.innerText = user.name; } } window.addEventListener('load', async () => { auth0Client = await createClient(); await handleRedirectCallback(); });
Replace the
andYOUR_DOMAIN
placeholders with the actual values for the domain and client id you found in your Auth0 Dashboard.YOUR_CLIENT_ID
Then, create your UI with the following markup:
<p>Welcome <span id="username"></span></p> <button type="submit" id="btn-login">Sign In</button> <button type="submit" id="btn-logout" style="display:none;">Sign Out</button>
Your application is ready to authenticate with Auth0!
Check out the Auth0 SPA SDK documentation to learn more about authentication and authorization with JavaScript and Auth0.
Conclusion and Further Reading
As you could see throughout this article, Cypress is an amazing tool that facilitates end-to-end tests on web applications. If you want to learn more about Cypress, the following list will lead you to some references that you might find useful: