Test coverage using Cypress

In this blog post, I have explained how Cypress.io framework helped me to test the ‘create-booking’ feature @ ANAROCK Technology.

Vishal Karkera
ANAROCK
7 min readSep 21, 2021

--

Automating test cases are considered significant for every tech teams. Test engineers try hard to identify issues before the product is released. It would be unrealistic to test hundreds of test cases manually after every single commit during the developing phase in an agile testing methodology.
With many test tasks piled up, we had a new feature ‘create booking’ coming up to test which required quite a few retesting with multiple sets of input.
It has four pages and a list of multiple test cases for a QA to test. Few important tests were as follows:

  • On all four pages, input fields accepted multiple data types. So I had to validate the request from the front-end and verify that all the keys and values are as per the requirement.
  • Role based user login and automate different pages based on logged in user’s permission.
  • Adding assertions on request/response of all the APIs (GET, POST & PATCH) used in this feature.

To solve the above problem statement of QAs, I had shortlisted the following tools:

  • Selenium
  • Protractor
  • WebdriverIO &
  • Cypress

I explored each tools for all pros and cons. To cover all the above mentioned cases in front-end and also cover api request-response tests in a single framework, Cypress.io emerged as the best available option for me. Here’s what Cypress testing framework could do which made me go with it:

- Time Travel: Hover over commands in the Command Log to see exactly what happened at each step.

- Debuggability

- Automatic Waiting: No need to add waits or sleeps to your tests.

- Spies, Stubs, and Clocks

- Network Traffic Control

- Consistent Results

- Screenshots and Videos

Additionally, Cypress is a JavaScript end-to-end testing framework which was my preference. Moreover, it is easy and hassle free to set up Cypress. I have documented the installation process in these quick steps:

source : cypress.io

Cypress installation
Change the current working directory to the location where you want and clone your project.
In the code editor(VS code in my case), open the newly created/cloned project folder.

I will be installing Cypress via yarn.

yarn add cypress -D
yarn add cypress -D

In the terminal, at the new project path run the following command which will install Cypress in your project.

yarn add cypress -D

After the installation is done, this will create package.json in the project folder.

yarn cypress open

Then run Cypress with the following command:

yarn cypress open

Take a look at this window where we would be creating test files under the ‘integration’ folder.

config

Then add configuration baseURL on cypress.json file as shown above.

By adding baseUrl it would allow us to skip passing the baseUrl to commands like cy.visit() and cy.request() as shown in the code snippet above. Now we are all set to write our tests by adding it to the integration folder.

TypeScript Installation

I recommend writing your tests in TypeScript as it does type checking and makes code easier to read and understand. Run the following commands to install TypeScript:

yarn add — dev typescript

After typescript installation is done, add tsconfig.json file to your cypress folder as shown above.

tsconfig.json

Open the newly created tsconfig.json file and add this configuration. The “types” will tell the TypeScript compiler to only include type definitions from Cypress. You can edit lib/types etc accordingly.

Next you will have to restart your IDE’s TypeScript server. Open the command palette (Mac: cmd+shift+p) and select the “TypeScript: Restart TS server.” option.

To add custom commands to the cy object, you have to manually add their types to avoid TypeScript errors.

Let’s say if you add a custom command (cy.createBooking in my case) into your support file like this,

then you have to create a new TypeScript definitions file beside your support file (at cypress/support/index.d.ts) and add the createBooking() on your global Cypress Chainable interface as shown below.

cypress > support > index.d.ts

After this process, when you write tests under the integration folder and call custom command cy.createBooking(“phoneNumber”), TS compiler should not complain about unknown method.

After setting up Cypress to my project, this is how my project structure looked where I wrote my multiple user tests cases on create-booking.spec.ts file and defined all my reusable commands on cypress/support/commands.ts file as it is loaded before any test files are evaluated.

I have written my ‘createBooking & login’ functions here in commands.ts file as shown below. Its a good practice to write all your reusable functions here in this file such as :- clickLink / checkToken / getSessionStorage / login / logout etc.

As shown in the sample format structure in the screenshot, I have written the login function of different type of users roles on my actual code in commands.ts file. Also on the ‘createBooking’ function I had to cover all the text/number fields with data input. As mentioned earlier after entering all the fields and hitting the POST call, I made sure all the key-value pair requests and responses are as per requirement.

Since I had to assert the multiple keys and values and keep it clean under one expect, BDD Chai assertions ‘to.include’ was the best fit for these scenarios compared to (.to.have.property). As seen in the sample code, if any one of the key value pair doesn't match, the complete ‘it test block’ would fail.

After covering all the different roles of logins commands and reusable createBooking command, I focused on writing test cases for different set of user-roles and set assertions as per it.

Here in the test spec file (which is under the integration folder), I have routed all my API calls on beforeEach function. Cy.route works as a listener for xhr requests.

In this sample case, I have shown how I nested three test cases (it block) inside describe(‘TL user cases’). I have made separate describe() for each logged in role users which are not shown in this screenshot. The reason for writing it separately is that I wanted to write cy.login() inside particular describe() beforeEach so as to cover all the cases of that user and also wanted to keep my code readable.

1] icon visibility

1] The first test case was the simple one but it saved an ample amount of time to retest during the developing phase.

I had to test that the ‘createBooking’ icon should be visible only to four user-roles. I wrote the tests as per logged-in user and modified should(‘be.visible’) / (‘not.be.visible’) accordingly.

createBooking()

2] Since this user should have access to create booking, I called the function which was written in command.ts file ‘createBooking()’. I also needed to send a random ten digit number as parameter to this function call, hence I used math.random() method to generate a random number and also converted it to string (as shown in above code snippet). Once the createBooking function is called, as shown above I have written all the assertions within it, so the test would give a result accordingly.

3] In this test case I had to check if hitting /bookings endpoint is returning status 403 for this user. If I had not passed failOnStatusCode: false on line number 4, the test would have failed since the status code was not 2xx or 3xx and I won’t be able to pass on bad status codes which is intentional. So passing failOnStatusCode: false on cy.request() solves my problem in this use case which I run on 10 different test cases.

Before using Cypress I used to write frontend and API tests separately with different tools. But with Cypress, I could automate front-end and also wait for API calls to complete and assert it’s ‘request-response’ and expect UI success / fail messages accordingly. Also with Cypress, I could decide whether to stub responses or allow them to hit your server, delay the response etc.

I am also planning to automate more user specific test cases on this over time. The idea behind this is that code coverage which would save manual time and effort and also would contribute to faster release cycles.

Also there would be a phase 2 & 3 release planned for this feature, I am going to carry forward this knowledge and experience for the rest of the upcoming tests.

Thanks

--

--