Introduction to Cypress

Cypress is a lightweight framework made by frontend developers to test UI applications. Cypress is gaining popularity in a world where Selenium is the master because it’s easy to use, it’s fast and really user friendly.

It supports debugging, repeat tests, headless mode and many more features just by using the Cypress browser. But it’s limited to write tests in JavaScript using the Mocka test runner which should not be a limitation at all.

Setting Up Cypress

1.- Install NodeJS higher than 8.x 2.- Create the package.json file with content:

{
  "name": "tests",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "cypress": "cypress open",
    "test": "cypress run"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Using the above configuration, we can then run our tests using either “npm run cypress” or “npm test”.

3.- Install Cypress:

npm install cypress

Cypress will create the folder structure:

  • cypress.json - to configure Cypress
  • cypress/fixtures - to configure static data to be used by our tests
  • cypress/integration/examples - to have the test files
  • cypress/plugins - to customise the Cypress behaviour (more in here)
  • cypress/screenshots - to see the screenshots or recordings made by Cypress when running the tests

4.- Run cypress:

npx cypress open

This will open a browser to run the example tests.

First Test Case

Let’s write our first test in _cypress/integration/.spec.js_. In Cypress, we can have either:

  • action or commands: “cy.get(…).type” or “.click” or …
  • validations or assertions: “cy.get(…).should”

For this test, we’re going to visit a TODO app that is part of the installed Cypress examples:

cypress/integration/todoapp.spec.js:

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

describe('todo actions', () => {

  beforeEach(() => {
    // given
    cy.visit('http://todomvc-app-for-testing.surge.sh') // browse to this URL
    cy.get('.new-todo').type("Clean room{enter}") // focus the input with class name new-todo, type some characters and click enter
  })

  it('should add a new element to the list', () => {
    // assert
    cy.get('label').should('have.text', 'Clean room') // validate the label is created with expected content
    cy.get('.toggle').should('not.to.checked') // validate that the toggle is not checked
  })

  it('should mark completed an element', () => {
    // when
    cy.get('.toggle').click() // mark the item as completed
    // assert
    cy.get('label').should('have.css', 'text-decoration-line', 'line-through') // validate the label is updated
  })

  it('should clear completed elements', () => {
    // given
    cy.get('.toggle').click() // mark the item as completed
    // when
    cy.contains('Clear completed').click() // Search an element with a text containing 'Clear completed' and click it.
    // assert
    cy.get('.todo-list').should('not.have.descendants', 'li') // Validate that the are no more items
  })
})
Note this is only an example, for further commands, visit the official Cypress documentation.

Run our tests!

Cypress supports two ways to run the tests:

- Interactively - see the actions in the browser

npx cypress open

And select our new test file and run it.

- Non Interactively (headless mode) - see the test statuses via command line

npx cypress run

We can see a rendered video to check what happened in each test.

Improve tests using Page Objects

The idea is that instead of doing:

cy.get('.new-todo').type("Clean room{enter}")

We want to have something with:

todoPage.addTodo('Clean room')

In order to achieve this, we need to create our page object class cypress/page-objects/todo-page.js:

export class TodoPage {

  // browse to the TODO site
  navigate() {
    cy.visit('http://todomvc-app-for-testing.surge.sh')
  }

  // focus the input with class name new-todo, type some characters and click enter
  addTodo(todoText) {
    cy.get('.new-todo').type(todoText + "{enter}")
  }
}

And our tests would look like as:

cypress/integration/todoapp.spec.js:

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

import { TodoPage } from "../page-objects/todo-page";

describe('todo actions', () => {

  const todoPage = new TodoPage()

  beforeEach(() => {
    // given
    todoPage.navigate()
    todoPage.addTodo('Clean room')
  })

  // ...
})
Note that we could export directly our functions if our page object does not share any data among tests.

Tips

- Use the Cypress browser to inspect the element you want to test

cy.get('<the element that I inspected in the Cypress browser>')

- Delay elements in the UI

What if my web is rendered but some of our components are loaded asynchronously? The way we can simulate this scenario is by sending a delay parameter when we browse to our web:

cy.visit('http://todomvc-app-for-testing.surge.sh/?delay-<MY_CLASS_ELEMENT=3000') // 3 sec

Also, we can configure the timeout to wait until a component appears:

cy.get('.<MY_CLASS_ELEMENT', {timeout: 6000})

- Run only one test of my group

We can mark a mocha test with it.only:

it.only('should ... 1', () => {
  // ...
})

it('should ... 2', () => {
  // ...
})

In this case, only the first test will be executed. Also, we can mark other tests with only.

- Disable for file changes

Go to cypress.js and add this settings:

{
  "watchForFileChanges": false
}

This way Cypress won’t load our tests automatically.

[ Cypress, Javascript ]