Cypress Promise and Cypress Asynchronous: 13 Important Facts


In our previous article, we saw the configurations in Cypress and various options that can be configured in JSON files. This article will understand Cypress Promise and Cypress Asynchronous behaviour with hands-on implementation and examples in our project. We will also discuss how to incorporate awaits in our asynchronous code and some essential functions like wrap() and task(). Let’s get started!

Cypress Promise and Cypress Asynchronous:

Cypress Promise and Cypress Asynchronous nature are some of the essential concepts. Like any other Javascript framework, Cypress also revolves around Asynchronous and Promises. Cypress handles all the asynchronous behaviour internally, and it’s hidden from the user. We will use .then() to handle promises manually in our code. There are external packages like Cypress-promise in npm where we can manipulate the Cypress asynchronous behaviour. We will discuss each of these topics in detail.

Cypress Promise and Cypress Asynchronous
Cypress Promise

Table of Contents

Cypress Asynchronous

As we know, Cypress is based on Node JS. Any framework that is written build from Node.js is asynchronous. Before understanding the asynchronous behavior of Cypress, we should know the difference between synchronous and asynchronous nature.

Synchronous nature

In a synchronous program, during an execution of a code, only if the first line is executed successfully, the second line will get executed. It waits until the first line gets executed. It runs sequentially.

Asynchronous nature

The code executes simultaneously, waits for each step to get executed without bothering the previous command’s state. Though we have sequentially written our code, asynchronous code gets executed without waiting for any step to complete and is completely independent of the previous command/code.

What is asynchronous in Cypress?

All Cypress commands are asynchronous in nature. Cypress has a wrapper that understands the sequential code we write, enqueues them in the wrapper, and runs later when we execute the code. So, Cypress does all our work that is related to async nature and promises!

Let’s understand an example for it.

 it('click on the technology option to navigate to the technology URL', function () {
        cy.visit('https://lambdageeks.com/') // No command is executed
        //click on the technology option
        cy.get('.fl-node-5f05604c3188e > .fl-col-content > .fl-module > .fl-module-content > .fl-photo > .fl-photo-content > a > .fl-photo-img') // Nothing is executed here too
            .click() // Nothing happens yet
        cy.url() // No commands executed here too
            .should('include', '/technology') // No, nothing.
    });
        // Now, all the test functions have completed executing
        // Cypress had queued all the commands, and now they will run in sequence

That was pretty simple and fun. We now understood how Cypress Asynchronous commands work. Let us dive deeper into where we are trying to mix sync and async code.

Mixing Cypress Synchronous and Asynchronous commands

As we saw, Cypress commands are asynchronous. When injecting any synchronous code, Cypress does not wait for the sync code to get executed; hence the sync commands execute first even without waiting for any previous Cypress commands. Let us look into a short example to understand better.

 it('click on the technology option to navigate to the technology URL', function () {
        cy.visit('https://lambdageeks.com/') 
        //click on the technology option
        cy.get('.fl-node-5f05604c3188e > .fl-col-content > .fl-module > .fl-module-content > .fl-photo > .fl-photo-content > a > .fl-photo-img')
            .click() 
        cy.url() // No commands executed here too
            .should('include', '/technology') // No, nothing.
        console.log("This is to check the log")  // Log to check the async behaviour
    });
});
Synchronous execution of the log command

The log is added at the end of the code, which is a sync command. When we run the test, you can see that the log has been printed even before the page is loaded. This way, Cypress does not wait for the synchronous command and executes it even before executing its commands.

If we want them to execute as expected, then we should wrap it inside the .then() function. Let us understand with an example.

it('click on the technology option to navigate to the technology URL', function () {
        cy.visit('https://lambdageeks.com/') 
        //click on the technology option
        cy.get('.fl-node-5f05604c3188e > .fl-col-content > .fl-module > .fl-module-content > .fl-photo > .fl-photo-content > a > .fl-photo-img')
            .click() 
        cy.url() // No commands executed here too
            .should('include', '/technology') // No, nothing.
        .then(() => {
            console.log("This is to check the log")  // Log to check the async behaviour
        });
    });
Async execution with .then() command

What is Cypress Promise?

As we saw above, Cypress enqueues all the commands before execution. To rephrase in detail, we can say that Cypress adds promises(commands) into a chain of promises. Cypress sums all the commands as a promise in a chain.

To understand Promises, compare them with a real-life scenario. The explanation defines the Promise in asynchronous nature too. If someone promises you, they either reject or fulfill the statement they made. Likewise, in asynchronous, promises either reject or fulfill the code we wrap in a promise.

However, Cypress takes care of all the promises, and it is unnecessary to manipulate them with our custom code. As a Javascript programmers, we get curious about using awaits in our commands. Cypress APIs are completely different than we are used to generally. We will look into this a later part of this tutorial in depth.

States of Cypress Promises

Promises have three different states based on the Cypress commands. They are

  • Resolved – Occurs when the step/ command gets successfully executed.
  • Pending – State where the execution has begun, but the result is uncertain.
  • Rejection – Occurs when the step has failed.

As a Javascript programmer, we tend to write promises in our code and return them. For example,

//This code is only for demonstration
describe('Cypress Example ', function () {
    it('click on the technology option to navigate to the technology URL', function () {
        cy.visit('https://lambdageeks.com/')
        //click on the technology option
        cy.get('.fl-node-5f05604c3188e > .fl-col-content > .fl-module > .fl-module-content > .fl-photo > .fl-photo-content > a > .fl-photo-img')
            .then(() => {
                return cy.click();
            })
        cy.url() 
            .then(() => {
                return cy.should('include', '/technology') 
            })
    });
});

Here, we are returning promises to each of the commands. This is not required in Cypress. Fortunately, Cypress takes care of all the promises internally,and we don’t need to add promises in each step. Cypress has the retry-ability option, where it retries for a particular amount of time for executing the command. We will see an example of a code without including promises manually.

    it('click on the technology option to navigate to the technology URL', function () {
        cy.visit('https://lambdageeks.com/')
        //click on the technology option
        cy.get('.fl-node-5f05604c3188e > .fl-col-content > .fl-module > .fl-module-content > .fl-photo > .fl-photo-content > a > .fl-photo-img')
            .click()
        cy.url()
            .should('include', '/technology')
    });
});
Cypress commands with promises handled internally

The above code is not clumsy and is easy to read and understand. Cypress handles all the promise work, and it is hidden from the user. So we don’t have to worry about handling or returning the promises anywhere!

How do you use await in Cypress?

As discussed above, Cypress has its way of handling asynchronous code by creating a command queue and running them in sequence. Adding awaits to the commands will not work as expected. Since Cypress is handling everything internally, I would recommend not adding awaits to the code.

If you need to add awaits, you can use a third-party library like Cypress-promise that changes how Cypress works. This library will let you use promises in the commands, and use await in the code

Let us understand the ways to use awaits and how not to use them.

You should not use awaits like this

//Do not use await this way
describe('Visit the page', () => {
  (async () => {
     cy.visit('https://lambdageeks.com/')
     await cy.url().should('include', '/technology');
  })()
})

Instead, you can use like this

describe('Visit the page', () => {
  cy.visit('https://lambdageeks.com/').then(async () => await cy.url().should('include', '/technology') ())
})

This will work for any Cypress commands.

Cypress Wrap

wrap() is a function in Cypress that yields any object that is passed as an argument.

Syntax

cy.wrap(subject)
cy.wrap(subject, options)

Let us look into an example of how to access wrap() in our code.

const getName = () => {
  return 'Horse'
}
cy.wrap({ name: getName }).invoke('name').should('eq', 'Horse') // true

In the example, we are wrapping the getName and then invoke the name for it.

Cypress Wrap Promise

We can wrap the promises that are returned by the code. Commands will wait for the promise to resolve before accessing the yielded value and. then proceed for the next command or assertion.

const customPromise = new Promise((resolve, reject) => {
  // we use setTimeout() function to access async code.
  setTimeout(() => {
    resolve({
      type: 'success',
      message: 'Apples and Oranges',
    })
  }, 2500)
})
it('should wait for promises to resolve', () => {
  cy.wrap(customPromise).its('message').should('eq', 'Apples and Oranges')
});

When the argument in cy.wrap() is a promise, it will wait for the promise to resolve. If the promise is rejected, then the test will fail.

Cypress-promise npm

If we want to manipulate the promises of Cypress, then we can additionally use a library or package called Cypress-promise and incorporate it in our code. This package will allow you to convert a Cypress command into a promise and allows you to await or async in the code. However, these conditions will not work before or beforeEach the blocks. Initially, we should install the package in our project by passing the following command in the terminal.

npm i cypress-promise

Once installed, the terminal will look something like this.

Cypress-promise install

After installation, we should import the library into our test file.

import promisify from 'cypress-promise'

With this library, you can create and override the native Cypress promise and use awaits and async in the code. You should access the promise with the promisify keyword. Let us look into an example for the same.

import promisify from 'cypress-promise'
it('should run tests with async/await', async () => {
    const apple = await promisify(cy.wrap('apple'))
    const oranges = await promisify(cy.wrap('oranges'))
    expect(apple).to.equal('apple')
    expect(oranges).to.equal('oranges')
});
Promisify in Cypress-promise

This was very simple and fun to learn! This way, you can assign asynchronous code in Cypress.

Cypress Async Task

task() is a function in Cypress that runs the code in Node. This command allows you to switch from browser to node and execute commands in the node before returning the result to the code.

Syntax

cy.task(event)
cy.task(event, arg)
cy.task(event, arg, options)

task() returns either a value or promise. task() will fail if the promise is returned as undefined. This way, it helps the user capture typos where the event is not handled in some scenarios. If you do not require to return any value, then pass null value.

Frequently Asked Questions

Is Cypress Synchronous or Asynchronous?

Cypress is Asynchronous by returning the queued commands instead of waiting for the completion of execution of the commands. Though it is asynchronous, it still runs all the test steps sequentially. Cypress Engine handles all this behavior.

Is it possible to catch the promise chain in Cypress?

Cypress is designed in a way that we will not be able to catch the promises. These commands are not exactly Promises, but it looks like a promise. This way, we cannot add explicit handlers like catch.

Aishwarya Lakshmi

I am a testing enthusiast and have nearly 2+ years of experience in the testing domain. I am passionate about testing and love to explore new things in my field and share them with my peers. I enjoy writing blogs during my free time in the simplest but effective way. As a tester, I like to have things to perfection, so I wish my readers to have the perfect understanding of the technology. I keep myself updated with the new technologies related to testing and spend time understanding them. I am glad to help students understand the concepts in testing. Let's connect through LinkedIn - https://www.linkedin.com/in/aishwarya-lakshmi-n-46903217a

Recent Posts