Cypress testing: Tips And Tricks That Will Save Your Time

Marcela Olszak
28 April 2020 · 8 min read

In the field of E2E test automation, in my opinion, Cypress is an invaluable tool. One of the reasons is the extensive and very legible documentation. Of course, it contains detailed know-how of the program, but also a lot of practical advice. You can find many valuable hints: how to use commands correctly, how to deal with annoying edge cases, how to organize work to facilitate testing. This is a great source of knowledge.

I have been using Cypress for some time now and I noticed that following this advice became my habit. They are simple and effective - they do not require any special workload and usually, they need to be used only once - at the very beginning.

Organize your tests - separate and categorize them

Be prepared for the fact that you will have many, many tests for one application. It can cause a lot of chaos if you thoughtlessly throw them all into one folder. Even more so, if you haven't taken care of the correct naming beforehand. It will take a while before you find yourself in it after a month's break. Good luck then!

That is why it is so important to keep the tree clean. First, use suggestive names. If you are testing the login process - name the test ‘login-spec’ or ‘login-test’ or just ‘login’. It's about knowing what this test does, without having to dig into the code.

Don't be afraid to go a step further - create folders to more accurately categorize files. If you test from the points of view of different users who have different access to the application - divide the tests into adequate folders, e.g. 'admin' and 'regular-user'. You can also split tests by category, e.g. in the 'user' folder put tests for login and registration, and in the 'shopping' folder - tests for the buying process.

Cypress: Organise the tests - divide the tests into adequate folders, e.g. 'admin' and 'regular-user'. #max400#
Organise the tests - divide the tests into adequate folders, e.g. 'admin' and 'regular-user'.

Write independent tests

Once you have your strategy on how to keep your tree clean and legible, you don't have to worry about creating tests. So create a lot of them - preferably one test per one feature. Avoid writing long code to cover as many things as possible at one go. If you are testing the login process - do not check password changing at the same time. This is a separate feature. Logging in may work correctly, and changing the password may not - if you put them in one test, then you have one failure. You lose half the information here. A better approach is to create two tests - one passes, the other does not. Then you know exactly where to look for the problem.

But remember this - tests must be independent of each other. You should be able to choose any test or run several tests in any order and still get the same result. For example, using one test to log in and another to test your profile photo change is a bad approach. If we release them interchangeably - the first test will fail if the user is not logged in. Your job is to provide a suitable environment at the beginning of the test.

Set base URL

It's important to keep things simple. Setting base URL is the way to start with it. Instead of repeating the lines with web addresses in each test, set the base URL in the 'cypress.json' file.

{

   "baseUrl": "http://cypress.io"

}

Thanks to this, you don't have to provide the address every time you use the cy.visit() command. You also have simplified access to the subpages: cy.visit (’/ account’).

it('Add to cart', () => {
   cy.visit('')
   cy.visit('/account')
})

Use IntelliSense

In general - use all the help you can find. There’s lots of command and rules of using them, which are not always obvious. You can use Cypress documentation on a regular basis, but it will take a lot of time if you do it for each command. Instead, use IntelliSense. At the very beginning of the test spec, add this:

/// <reference types="cypress" />

And you're good to go! When you write the code, the editor will prompt you with commands that match the characters you entered. Just type in part of the command, select it from the list of hints and confirm by clicking ‘Enter’.

Cypress - Tricks and Tips That Will Save Your Time#max400#
When you write the code, the editor will prompt you with commands that match the characters you entered. Just type in part of the command, select it from the list of hints and confirm by clicking ‘Enter’.

In the next step, when you enter the attributes for your command, IntelliSense suggests what type and how many should be.

Cypress - enter the attributes for your command, IntelliSense suggests what type and how many should be. #max500#
Cypress - enter the attributes for your command, IntelliSense suggests what type and how many should be.

Thanks to this, you have access to the entire command database. You can easily look them up and search for the command you need at the moment.

Run tests, that are crucial at the moment - use ‘cy.only’ and ‘cy.skip’

When you created an extensive test database for your application, you need a significant amount of time to run them all. Thanks to Cypress, you can fire them all at once, and afterwards compare the results.

In a situation where you care about a specific area of the application, it is quite useless though. It is not worth waiting for the test results that you are not interested in at the moment. On the other hand - passing the remaining tests one by one is inconvenient and long lasting.

In such a situation, use the commands: 'cy.only' and 'cy.skip'. They will work perfectly here (provided that you have followed point 2). The first one means that only the selected tests will be fired in the current spec. Here, it is ‘Open menu’:

it.only('Open menu', () => {
   cy.get('.side-menu')
   cy.click()
})
it('Open profile', () => {
   cy.get('.user-profile')
   cy.click()
})

You can also mark more than one test this way - then all other tests will be skipped. If you know that you have to skip one specific test out of 10, use ‘cy.skip’ instead - then all tests except the one will be run. Here we skip the 'Open profile' test:

it('Open menu', () => {
   cy.get('.side-menu')
   cy.click()
})
it.skip('Open profile', () => {
   cy.get('.user-profile')
   cy.click()
})

When you mark your test like this, you can run them all at once in Cypress and get the results you need at the moment.

Use your own commands

In order to keep the code clean you can't do without creating your own commands. It is very important to bring every repeating element to one command that you can use many times. An example is login process. This is how it could look like if you won’t write a command:

it('Login', () => {
   cy.visit('/login')
   cy.get('input[name="email"]')
     .type('john.smith@js.com')
   cy.get('input[name="password"]')
     .type('pass123')
   cy.get('button[name="submit"]')
     .click()
})

From Cypress, everything is prepared to make it as easy as possible for you. Add your command to the 'commands.js' file based on the instructions Cypress gives you.

Cypress.Commands.add('login', (email, password) => {
   cy.visit('/login')
   cy.get('input[name="email"]')
     .type(email)
   cy.get('input[name="password"]')
     .type(password)
   cy.get('button[name="submit"]')
     .click()
})

The environment created by Cypress is arranged this way, that you don’t need anything more. Your command will work for each test, and the login process will look like this:

it('Login and open menu', () => {
   cy.login('john.smith@js.com', 'pass123')
   cy.get('.side-menu')
   cy.click()
})
it('Login and open profile', () => {
   cy.login('john.smith@js.com', 'pass123')
   cy.get('.user-profile')
   cy.click()
})

Use dedicated selectors (data-cy = “confirm-button”)

There are some practices that you should think about before you start testing. Some aspects should be taken into account at the stage of development. Especially if we know ahead that the application will be later tested by us using Cypress. I'm talking about selectors here.

A problem that appears quite often - at least from what I’ve noticed - is the lack of unambiguous and unchangeable naming, that can be used to locate elements. Cypress has a very useful tool that provides a path to the indicated element. This is a great help. Sometimes, however, this path consists of 10 words, because there is no single selector that would point to the element. This makes the code less readable and difficult to understand:

cy.get('.styled__AltMenu-sc-16oj5lj-2 > .styled__NavList-sc-16oj5lj-3 > :nth-child(1) > .Link-sc-5cc5in-0')

For this reason, the key elements, that are included in the steps to reproduce test cases, should be given unique names. For example, data-cy =”login-button”, data-cy = “logout-button”. Like here:

cy.get('[data-cy="logout-button"]')

This improves test readability and provides better stability. ’data-cy’ selectors will most likely not change, but selectors based on class might.

Wait for content before you use it (but not with cy.wait(500))

To be sure that the item you want to refer to is already visible on the page, you must wait for it. But how much exactly should you wait? Nobody knows that. There is no fixed number of seconds after which you can be sure, that the button you want to click on will definitely be visible. Therefore you cannot use cy.wait(500). In most cases, the page will load faster and the additional delay will only slow down your test.

What you want to do is wait for the particular item until its loaded - no matter how long it takes. For this purpose, you must provide the right path and give it an alias. Then wait for Cypress to refer to it. No more unnecessary waiting!

it('Wait for image', () => {
   cy.server()
   cy.route('GET', '/dashboard/profile/user-image')
     .as('user-image')
 
   cy.visit('/dashboard') 
   cy.wait('@user-image')
 
   cy.get('[data-cy="open-profile"]')
     .click()
})

Use assertions to ensure you got the right element

To be sure that your test will not accidentally fail, it's good to add assertions - to be sure that you are referring to the right element at the right time.

it('Login', () => {
   cy.visit('/login')
   cy.url()
     .should('contain', 'login')
    
   cy.get('input[name="email"]')
     .should('be.visible')
     .type('john.smith@js.com')
     .should('contain', 'john.smith@js.com')
})

Assertions have a unique attribute - they last until they find the item you are looking for or reach the timeout. If you add the condition that the button should be visible before you do 'click', Cypress will wait until the button appears and then clicks on it. You can also assert that the button you’re clicking is green, that it contains the word 'yes' and does not include the word 'no'. Whatever comes to your mind. Assertions will protect you from accidentally clicking on random elements and failing the test.

This is what I consider a basic must-have if we’re talking about Cypress tests. There sure are plenty more tips worth trying, which haven’t been mentioned here. Consider this an intro to the topic, which definitely will be developed in the future.

Contact us

Related blogposts:

How I Discovered Automation with Cypress

DevSecOps Explained: Important Questions and Answers

GraphQL from Django Developer Perspective

Go to the section about JavaScript development services for business

Share on

Talk to us about your project

Get in touch with us and find out how we can help you develop your software
Contact us