w3resource

Retry-ability


In this tutorial you will learn how Cypress retries commands and assertions, you will learn the scenarios when commands are retried and when they are not retried. Finally, we will show you how to address some situations of flaky tests.

The ability to retry tests (retry-ability) is a feature of Cypress that assists with testing dynamic web application. A good understanding of how retry-ability works will enable you to write faster tests with fewer run-time surprises.

Commands vs assertions

There are two types of methods that you can call in your Cypress tests, which are commands and assertions.

For instance, in the spec_test file below there are 6 commands and 2 assertions:

'''it('this will create 2 items', () => {
  cy.visit('/')                       // command
  cy.focused()                        // command
    .should('have.class', 'new-todo') // assertion

  cy.get('.new-todo')                 // command
    .type('todo A{enter}')            // command
    .type('todo B{enter}')            // command

  cy.get('.todo-list li')             // command
    .should('have.length', 2)         // assertion
})'''

The Command Log will show both commands and assertions with passing assertions in green, while the failing assertions will be in red.

Consider the last command and assertion pair:

cy.get('.todo-list li') // command

.should('have.length', 2) // assertion

Since modern web application are asynchronous, Cypress cannot query all the DOM elements having the class todo-list, it will then check if there are only two of them. This examples could fail if the application has not updated DOM when this commands run, or the application is waiting for the backend to update the DOM elements or the application has some intensive computation that needs to be completed before the result is shown on the DOM.

Cypress therefore has to find a smarter way to handle these potential updates. The cy.get() will query the application's DOM, then find the elements that matches the selector, then tries to perform the assertion that follows it against the list of found elements.

  • If the assertion following the cy.get() passes, your command will finish successfully.
  • If the assertion fails, the cy.get()command will query the application's DOM once again.This retry will be done as many time as required provided the timeout is not exceeded.

The concept of retry-ability enables the test to complete each command as soon as trhe assertion passes, without you hard-coding the waits.

'''app.TodoModel.prototype.addTodo = function (title) {
  this.todos = this.todos.concat({
    id: Utils.uuid(),
    title: title,
    completed: false
  })

  // let us trigger the UI to render after 3 seconds
  setTimeout(() => {
    this.inform()
  }, 3000)
}'''

Multiple assertions

A single command that is followed by multiple assertions will retry each one of them in order. Thus when the first assertion you make passes, the command will then be retried with first and second assertion.

For instance, the following test has .should() and .and() assertions. The .and() assertion is an alias of the .should() command.

'''cy.get('.todo-list li')     // command
  .should('have.length', 2) // assertion
  .and(($li) => {
    // 2 more assertions
    expect($li.get(0).textContent, 'first item').to.equal('todo a')
    expect($li.get(1).textContent, 'second item').to.equal('todo B')
  })'''

Since the second assertion expect($li.get(0).textContent, 'first item').to.equal('todo a') fails, the third assertion will never be reached.

It is not every command that is retried

Cypress will not retry a command that queries the DOM, these include cy.get(), .find(), .contains(), etc. Check the “assertion” section of Cypress API documentation to see if the command is retried till all the assertions that follows it are passing.

Why are some commands not retried?

Cypress will not retry commands that could potentially change the state of the application under test. For instance, the .click() command will not be retried since it can change something in the application.

Built-in assertions

Most often a Cypress command has built-in assertions that causes the command to be retried. For instance, the .eq() command will be retried even when you don’t attach assertions until it finds an element with the given index in  the previously yielded list of elements.

Timeouts

Every command that retries does so for up to 4 seconds by default. This configuration can be changed using the configuration file, a CLI parameter, via an environment variable, or programmatically.

To do this via the CLI you will have to run the following command via the terminal:

'''cypress run --config defaultCommandTimeout=10000'''

The above command will set the default command timeout to 10 seconds. However, it is recommended that you change this setting by passing {timeout: ms} option into your command.

Only the last command will be retried

Cypress will only retry the last commands before the assertion in your test.

'''cy.get('.new-todo').type('todo B{enter}')
cy.get('.todo-list li')         // queries immediately, finds 1 <li>
  .find('label')                // retried, retried, retried with 1 <li>
  .should('contain', 'todo B')  // never succeeds with only 1st <li>'''

Merging queries

It is recommended that you avoid unnecessary splitting commands that query elements. You can query elements using cy.get() and then make a query from that list of elements using .find().

Also, when you are working with deeply nested JavaScript properties using the .its() command, you should try not to split it across multiple calls. Rather, you should combine property names into a single cell using the . separator.