An introduction to writing tests using Cypress
Cypress is a testing framework for web applications that makes writing tests for frontend web applications seamless, faster and reliable.
Cypress is used by developers and quality analysts to write all types of tests ie. e2e tests, unit tests and integration tests.
This tutorial will cover the basics of writing tests using Cypress. We will go through the installation of Cypress into your machine together with some configurations and lastly, we will use Cypress for testing a todo application that I wrote for this tutorial. So let’s dive in.
Installing Cypress
You can install cypress in several ways as they are listed below
- Installation using npm
- Installing through a direct download
Installation using npm
Before running this command ensure that you have already done npm init or you have a node_modules package in your project or you have a package.json file.
Navigate to your projects root folder on the terminal and run the following npm command
npm install cypress --save-dev
This installs cypress locally as a dev dependency for your project.
You can also use yarn to install cypress
yarn add cypress --dev
Using npm or yarn to install cypress is the recommended way of adding cypress to your projects because Cypress is a versioned dependency package just like any other and this makes it easier to run Continuous Integration on.
Installation using a direct download
Download Cypress here.
Your platform will be detected automatically. After the download is complete, you can go ahead unzip the file and double click to open. No dependencies are needed for this.
Opening Cypress
If you used npm or yarn to install cypress, cypress has already been added to your ./node_modules directory with its binary executable accessible from the ./node_modules/.bin. You can now open cypress from the root of your project by running
./node_modules/.bin/cypress/open
or with this shortcut
$(npm bin)/cypress open
or using npx (npx is included with npm > v5.2)
npx cypress open
or using yarn like so
yarn run cypress open
After a moment, the cypress test runner will launch.
You can also add some npm scripts that will enable cypress to run with npm commands. So navigate to your package.json under scripts and include the following,
"scripts": {
"cypress:open": "cypress open"}
You can invoke this command from the root of your project like so,
npm run cypress:open
This should also open the cypress test runner.
Configuring Cypress
The first time you open the cypress test runner it creates a cypress.json configuration file. You can now add any configuration to cypress using this file.
There are multiple configurations that can be done for cypress (I’ll leave this task for you) but for the sake of this tutorial’s length, we won't cover them all here. We will only go through the baseUrl configuration option.
So search through the folder structure and open cypress.json
then add the following line
{
"baseUrl": "http://localhost:3000/"
}
The baseUrl configuration option is for setting the default url for all cy.visit()
s. That is whenever we have cy.visit(‘/’)
and the baseUrl value is set to for instance http://localhost:3000/
You won’t have to manually insert the url on the cy.visit()
or any consequent cy.visit(/users)
They will be automatically inserted to the test runner as http://localhost:3000/
and http://localhost:3000/user
respectively. This also works for any cy.request()
tests.
The actual tests
To get the project used for this tutorial please run the following on your terminal
$git clone https://github.com/muriukialex/learn-testing-using-cypress.git$cd learn-testing-using-cypress$code .
#this opens VScode
We’ve covered the installation of cypress together with the configuration options for cypress. Now let’s use cypress for testing our application. For the sake of this tutorial I made this simple todo list application where a user has the ability to add an item to a todo list, the user also has the ability to remove an item from the list and also mark any item as complete.
In the project where we cloned the todo application repo, open the window at the left of the code editor window and click on the cypress folder to open it
Under the cypress folder click on integration and you will notice that there are 2 other folders included by default by cypress. You can use these folders as a reference to know the kind of APIs cypress offers. Let’s add a folder which will be in the same folder level together with the other two default folders. We will name this folder 3-my-own
.
Under the 3-my-own folder, add a file and name it as my-own-test.spec.js
So let’s begin writting these tests. At the top of the my-own-test.spec.js
file let’s add this,
/// <reference types="cypress" />
This code allows us to leverage the power of VScode intellisense for code autocompletion when writing cypress tests. After this code we will have the following,
describe("test for my todo application", () => { beforeEach(() => { cy.visit("/") })}
Tests are organized to blocks and describe is one way of formatting our code into code blocks which describe what the test is doing. describe
can also be renamed to context
and it would work the same way.
beforeEach
is a hook provided by mocha and it is very helpful since it sets conditions that you want to run before a set of tests or before each test. So in this case we are telling cypress to visit the baseUrl when each test runs.
You can now add the following code which tests that there is no item in the list when the page first renders.
it("displays No item in the list by default", () => { cy.findByText(/No item in the list/i).should("exist")})
findByText
is an function that we get from the cypress API and all it does is search through the page for a text that matches the text passed in through the argument. We chain the findByText
function to should()
which has an argument “exist”
that pretty much does what it says 😅, it checks if the text in the findByText
function exists. The opposite of this would look like
cy.findByText(/No item in the list/i).should("not.exist")
Yeap. If the text does not exist in the page, this assertion would pass.
So far we have the following code
/// <reference types="cypress" />describe("test for my todo application", () => { beforeEach(() => { cy.visit("/") }) it("displays No item in the list by default", () => { cy.findByText(/No item in the list/i).should("exist") })}
To check if this tests would pass, go over to the terminal at the root of your project (if you are using VScode you could also do CTRL + `) and type in the following to boot up cypress
npm run cypress:open
This opens the cypress test runner. In this test runner window, open the last folder that we just added and click on the my-own-test.spec.js
file to see the results of the tests we just wrote.
Great, let’s include some more tests.
it("can add new todo items to the page", () => { const newItem = "first item"
cy.get("[data-testid=item-field]").type(`${newItem}{enter}`) cy.get("li") .should("have.length", 1) .first() .findByText("first item") .should("exist") const secondItem = "second item" cy.get("[data-testid=item-field]").type(`${secondItem}{enter}`) cy.get("li") .should("have.length", 2) .last() .findByText("second item") .should("exist")})
Phew! That was a bunch. Let’s go through the following test to see what is going on. At the top we have a description which informs us what the test doing. In this case it tests if the application that we have can add items into the page. We start by creating a variable which will contain text first item
for our first item. We then use cy.get()
and pass in [data-testid=item-field]
.So cypress uses this get
to search through the DOM for an element with the data-testid
attribute that equals item-field
. In our case, the element here is our input field and we chain that with .type
which accepts an argument of what we want the test runner to insert into the input field. In this case ‘it types’ the text of the first variable newItem
and hits {enter}
to the page. After this cypress now uses cy.get(‘li’)
to search for any list element in the page. It looks for the first list item using first()
and checks if that first item in the list is whatever we added and it does so using findByText(‘first item’)
To assert this to either true or false we chain the .should(‘exist’)
at the end. If the item was added to the list successfully, this test should pass.
The same assertions above happen for the second item added to the list and that test should also pass.
This is great but you have noticed that we do have a checkbox which is checked to false when an item is added. Let’s test that when an item has been added and the checkbox is clicked, the UI responds accordingly to the way that we want it to. In this example, the item has a CSS property that strikes through the item text if the item is checked.
it("can check off an item as completed", () => { const newItem = "first item" cy.get("[data-testid=item-field]").type(`${newItem}{enter}`) cy.contains("first item")
.parent()
.find("input[type=checkbox]")
.check() cy.contains("first item")
.should("have.class", "complete")})
We create an item variable with the text first item
, we type it into the input field and hit enter to add it to the items list. We then use cy.contains
to search through the DOM to find any item with the text first item
and after we find it, we look for its parent DOM element using parent()
and look for the element within the parent element which is an input checkbox element and we check it using check()
After this, we assert that the first item
has its class changed to complete
Now let’s test if we can now remove an item from the item list by adding the following test
it("can delete a task in the item list", () => { const newItem = "first item" cy.get("[data-testid=item-field]").type(`${newItem}{enter}`) cy.contains("remove ❌").click() cy.contains("first item").should("not.exist")})
We add an item, then type it to the input field and insert it into the item list after hitting {enter}
We then assert that the DOM now contains remove ❌
which you have noticed is added with each item in the item list. We click on this element ‘containing’ remove ❌
using click()
and later confirm that the item has been removed from the DOM by using should(‘not.exist’)
which asserts that it has been successfully removed.
This is how our test now looks like
/// <reference types="cypress" />describe("test for my todo application", () => { beforeEach(() => { cy.visit("/") })
it("displays No item in the list by default", () => { cy.findByText(/No item in the list/i).should("exist")})
it("can add new todo items to the page", () => { const newItem = "first item" cy.get("[data-testid=item-field]").type(`${newItem}{enter}`) cy.get("li") .should("have.length", 1) .first() .findByText("first item") .should("exist")
const secondItem = "second item"cy.get("[data-testid=item-field]").type(`${secondItem}{enter}`)cy.get("li") .should("have.length", 2) .last() .findByText("second item") .should("exist")})it("can check off an item as completed", () => { const newItem = "first item" cy.get("[data-testid=item-field]").type(`${newItem}{enter}`) cy.contains("first item")
.parent()
.find("input[type=checkbox]")
.check() cy.contains("first item").should("have.class", "complete")})it("can delete a task in the item list", () => { const newItem = "first item" cy.get("[data-testid=item-field]").type(`${newItem}{enter}`) cy.contains("remove ❌").click() cy.contains("first item").should("not.exist")})})
Let’s head over to the terminal to run our tests (or if in VScode just do CRTL + `) and type in the following command.
npm run cypress:open
Click on the last file and a chromium browser will run the tests for you.
This is just the tip of the iceberg of what manner of testing cypress offers but I hope you have learnt a lot from this simple project we did.
Thanks for reading 👏🏼