Cypress: A New Way of Testing UI

Have you ever tested a single page app with Selenium? It can be cumbersome and flaky, Cypress aims to fix those issues.

3rd Nov. 2019

Gone are the days when JavaScript was only used to add simple functionalities to websites. We now have web applications that replaces web applications; from word processing to image manipulation, they’re all out there.

What is Cypress?

Cypress is a JavaScript UI testing framework, written from the ground up. It does not rely on webdriver to automate the browser, instead it uses a browser extension to do so. As a result of this, you can only test websites on Chrome or Chromium with support for other browsers in development.

What seems to be the problem?

One of my biggest gripes with Selenium-based tests is the need to create page object models even before you write the tests! In one of our projects, we were sometimes writing more lines of code for the page objects than the new UI itself.

With Cypress, the concepts of page object models have been thrown out the window. Instead, you can directly query the DOM and get the elements you want to test or interact with.

Take the example code below, we want to fill the form and then submit it.

<form class=form>
  <div class=field>
    <label class=label>Name</label>
    <div class=control>
      <input
        data-test=input-name
        class=input
        type=text
        placeholder=e.g John Smith
      />
    </div>
  </div>

  <div class=field>
    <label class=label>Email</label>
    <div class=control>
      <input
        data-test=input-email
        class=input
        type=email
        placeholder=e.g. johnsmith@email.com
      />
    </div>
  </div>

  <div class=control>
    <button data-test=submit class=button is-primary>Submit</button>
  </div>
</form>

Your test could look like something like this.

describe(‘Test form’, () => {
  before(() => {
    // do your setup here, like navigating to the page
  })

  it(‘fills form and submits’, () => {
    cy.get(‘[data-test=input-name]’).clear().type(‘Jane Smith’)

    cy.get(‘[data-test=input-email]’).clear().type(‘jane.smith@email.com’)

    cy.get(‘[data-test=submit]’).click()

    // Add other assertions
  })
})

As you can see, it’s as easy as querying the DOM using the data attributes and interacting with them. If for some reason those elements are not present, your test will fail and Cypress will take a screenshot for you.

But I’ve got dynamically loaded elements.

No problem, Cypress will wait for up-to 4 seconds for elements to be in the DOM before it fails the test. This setting can be globally configured or per assertion.

If the elements you are testing rely on an API call, you can explicitly wait for that request to finish before proceeding to your assertion.

it(‘shows server response’, () => {
  // Hook into requests
  cy.server()
  cy.request(‘POST’, ‘/api/person/*’).as(‘updatePerson’)

  // Do your api call here

  // This will wait for the request to complete
  cy.wait(‘@updatePerson’)

  // An example of increasing the maximum timeout
  cy.get(‘[data-test=response-info]’, { timeout: 6000 }).should(‘be.visible’)
})

Conclusion

Cypress seems to be a perfect fit for testing single page applications, provided your main target browser is Chrome. Even with that downside, the conveniences Cypress bring are the reason why I enjoy writing UI tests with it.