w3resource

Introduction to Cypress 2


In part one of this tutorial, we showed you how to query the DOM, manage subjects and how to make chains of command. In this part we will show you how to make assertions about elements.

Assertions enable you to do things such as ensuring that an element is visible or that it has a particular attribute, CSS class, or state. Assertions are commands that helps you to describe the desired state of your application. Cypress automatically waits for your element to reach this state, or it will fail the test if the assertions does not pass. Here are some quick examples of how an assertion works:

'''cy.get(':checkbox').should('be.disabled')'''
'''cy.get('form').should('have.class', 'form-horizontal')'''
'''cy.get('input').should('not.have.value', 'US')'''

For each of these examples, you should note that Cypress will wait automatically for these assertions to pass. This will prevent you from having to know or care about the precise moment that your elements eventually do reach this state.

Subject Management

Every new Cypress chain stats with cy.[command], where what is yielded by the command will establish the commands that can be called next (chained).

Some methods will yield null and hence cannot be chained, an example is cy.clearCookies().

Some methods, such as the cy.get() or the cy.contains(), will yield a DOM element, this allows you to chain further commands onto them (assuming that the expect  a DOM subject) such as .click() or even cy.contains() again.

Some commands cannot be chained

The commands that can be chained are:

cy.clearCookies(), cy.type() and cy.contains().

Some commands like cy.clearCookies() will yield null. While some others will like cy.click() will yield the same subject that were originally yielded, finally, some commands will yield a new subject as appropriate for the command cy.wait().

Using .then() to act on a subject.

If you have used promises, you will be familiar with the .then(). In Cypress when you use the .then() it will wait for the previous command to resolve then it will call your callback function with first argument being the yielded subject.

If you want tp continue chaining commands after using the .then(), you will need to specify the subject that you will like to yield to those commands, this is achieved by returning a value that is neither null or undefined.

Here is an example to illustrate this:

'''cy
  // Find the el with id 'sample-link'
  .get('#sample-link')

  .then(($sampleElement) => {
    // ...massages the subject with some arbitrary code

    // get its href property
    const href = $sampleElement.prop('href')

    // remove the 'hash' character and everything that is after it
    return href.replace(/(#.*)/, '')
  })
  .then((href) => {
    // href will now the new subject
    // which you can now work with
  })'''

 Cypress commands are Asynchronous

As we have stated earlier, Cypress commands are asynchronous. What this means is that when you invoke a cypress command it does not do anything immediately, rather it will get queued to be run later.

Commands Run Serially

Once a test has finished running, Cypress will start executing the commands that where queued using the cy.* command chains.

Here is an example:

'''it('will change the URL when "awesome" is clicked', () => {
  cy.visit('/my/resource/path')       

  cy.get('.awesome-selector')               
    .click()                                             

  cy.url()                                               
    .should('include', '/my/resource/path#awesomeness') 
})'''

The code above will first visit a URL, find an element using its selector, then perform a click action on that element, it will then grab the URL and assert the URL to include a specific string. This commands will be executed serially, but Cypress will also perform retries for each of them if they fail initially.

Commands are Promises

Promises is built into the fabric of Cypress. As we stated earlier, commands are enqueued in Cypress.

In the example below we will compare Cypress to a fictional version of raw, Promise-based code:

Promised-based code

'''it('changes the URL when "awesome" is clicked', () => {
  // THIS IS JUST A SAMPLE CODE
  return cy.visit('/my/resource/path')
  .then(() => {
    return cy.get('.awesome-selector')
  })
  .then(($element) => {
    // not analogous
    return cy.click($element)
  })
  .then(() => {
    return cy.url()
  })
  .then((url) => {
    expect(url).to.eq('/my/resource/path#awesomeness')
  })
})'''

Cypress code with Promises wrapped up hidden from us

'''it('will change the URL when "awesome" is clicked', () => {
  cy.visit('/my/resource/path')

  cy.get('.awesome-selector')
    .click()

  cy.url()
    .should('include', '/my/resource/path#awesomeness')
})'''

Aside the fact that Cypress makes the code cleaner, it also offers retry-ability which Promises cannot do.

Although we said Cypress is like Promises, it is not an exact 1:1 implementation of Promises. Here are some of the differences between Cypress and Promises:

  1. You cannot race or run multiple commands at the same time (in parallel).
  2. You cannot ‘accidentally’ forget to return or chain a command.
  3. You cannot add a .catch error handler to a failed command.

Assertions

Assertions help you describe the desired state of your elements, your objects, and your application. The unique feature of Cypress is that it Cypress commands will automatically retry their assertions. You should consider assertions as guards. You should use your guards to describe what your application should look like, and then Cypress will automatically block, wait and retry until it reaches that state.

Asserting in English

Consider a scenario where you expect a button’s class to become active after clicking on it. TO do this in Cypress, you will write:

'''cy.get('button').click().should('have.class', 'active')'''

You should note that it is possible to write a Cypress test without making assertions.

Default Assertions

There are many commands that have a default, built-in assertion, or they have requirements that can cause it to fail without the need for an explicit assertion. For instance the cy.visit() command expects the page to send back text/html and response code of 200.

Writing Assertions

You can make assertions in two ways, either by implicit subject using .should() or .and() or by explicit subject  using expect.

Implicit Subjects

The use of .should() or .and() commands is the preferred way to make assertion in Cypress.

An example is shown below:

'''cy.get('#header a')
  .should('have.class', 'active')
  .and('have.attr', 'href', '/users')'''

Implicit Subjects

Using expects enables you to pass in a specific subject and make an assertion about it. This is mostly how you are used to seeing assertions written in unit tests.

'''cy
  .get('p')
  .should(($p) => {
    // massage your subject from a DOM element
    // into an array of texts from all of the paragraphs
    let texts = $p.map((el, i) => {
      return Cypress.$(el).text()
    })

    // jQuery map will return jQuery object
    // and .get() will convert this to an array
    texts = texts.get()

    // array must have length of 3
    expect(texts).to.have.length(3)

    // having this specific content
    expect(texts).to.deep.eq([
      'Some text from the first p',
      'More text from the second p',
      'And even more text from the third p'
    ])
  })'''

Timeouts

Nearly all commands can time out in some way. Every assertion, whether they are the default ones or the ones that you added yourself share the same timeout values.

Applying Timeouts

You can modify the timeout of a command. This will affect both the default assertions and the special assertions that you have added.

You should remember that since assertions are used to describe a condition of the previous command, the timeout modification will go will go to the previous commands and not the assertions.

Here are some examples:

Example #1: Default Assertions

'''// since the .get() command has a default assertion
// that the element exists, it can time out and fail
cy.get('.mobile-nav')'''

Under the hood, cypress will, query for the element named .mobile-nav and then wait for up to 4 seconds for it to exist in the DOM.

Example #2: Additional Assertions

// we have added 2 assertions to our test

'''cy
  .get('.mobile-nav')
  .should('be.visible')
  .and('contain', 'Home')'''

In this case Cypress will query for the .mobile-nav element, wait for it to exist in the DOM, it then waits for up to 4 seconds for it to be visible. Finally, it will wait for up to 4 seconds for it to contain the text Home.

Example #3: Modifying Timeouts

// we have modified the timeout which affects default + added assertions

'''cy
  .get('.mobile-nav', { timeout: 10000 })
  .should('be.visible')
  .and('contain', 'Home')'''

Under the hood, Cypress will get the .mobile-nav element, wait for it to exist in the DOM, an additional 10 seconds is spent waiting for it to be visible. Finally, it waits for 10 seconds for the element to contain the text Home.