An introduction to writing tests using Cypress

Alex.Muriuki
9 min readJan 25, 2022
cypress logo (the modern way to test frontend web applications)

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

  1. Installation using npm
  2. 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.

cypress test runner open

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

vscode screen shot

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
cypress test runner

Click on the last file and a chromium browser will run the tests for you.

tests passed 🎉

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 👏🏼

--

--

Alex.Muriuki

Am a software engineer based in Nairobi, Kenya. Here is where I document my learning.