End-to-End Testing With Cypress


Intro:

End-to-end testing can be intimidating. There are different reasons for this intimidation, but ultimately it’s the initial lack of speed when setting up end-to-end testing on a project. That’s where Cypress comes in handy. With Cypress, you can quickly and easily implement crucial tests that help allow for a higher quality product with better integrity. In this article, we will review the initial setup of Cypress, creating a test with Cypress, and expanding the testing further with some real-life use cases.

Prerequisites:

  • Node.js installed
  • Some JavaScript knowledge will be helpful

Initial Setup:

Setting up Cypress is fairly straightforward.

npm install cypress --save-dev

Cypress is now installed in your project directory as a dependency. Next, we will open the Cypress testing interface and this will add example tests to your project.

You can run this command to open the Cypress testing interface. This interface will list all the tests available in your project.

$(npm bin)/cypress open

Example Cypress tests in Cypress interface

In your project root directory, you will see a folder labeled “cypress”, the integration folder contains all the example test specs.

These example test specs are fantastic to review the different use cases. I’d recommend after installing Cypress, creating a few core tests needed for your application.

Creating a test:

In the integrations folder let’s create a folder called core_elements, in this folder, we will write tests to check that our website has the HTML elements we need. Next, let’s create a test called footer.spec.js. We will use this test to check that our website has a footer with the expected elements.

Cypress uses JavaScript for its tests, making it very easy for anyone who knows a little JavaScript. Some things can be comparable to jQuery like how the selectors work, so some of the syntaxes might look familiar if you also know jQuery, if not that’s okay too!

Here is an example test in which we will check if this current website has a footer and as a bonus, we will check for a social media link.

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

context('Homepage', () => {
  // Before each test, visit the local homepage
  beforeEach(() => {
    cy.visit('http://localhost:8000')
    // If we had a baseUrl set we could do this instead
    // cy.visit('/')
  })

  // Check that a footer exists
  it('Has a footer', () => {
    cy.get('footer').should('exist')
  })

  // Check that a twitter link exists in the footer
  it('Has a twitter link in the footer', () => {
    cy.get('footer .social--twitter').should('exist')
  })
})

Notice the beforeEach brings our test to the page we want to test, then we are using the get() function to evaluate the document element.

In the Cypress interface, this test can be run and a visual output of the test will be displayed.

Example footer test

This is a very simple example, next we will discuss some more realistic use cases you might be able to apply to your project.

Use cases:

Cypress has a wide variety of use cases, on the libraries’ website they have a great list of recipes.

Here are a few great example use cases that can quickly impact your website. Let’s take a look at some example code for each test type.

  • Testing login
  • Linting inline CSS elements
  • Continuous integration

Testing login:

In this login example, we make an XHR request. Alternatively, you could fill in the actual form interface with the Cypress type() function.

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

describe('Logging in using XHR request', function () {
  const username = 'testUsername'
  const password = 'testPassword'

  it('can bypass the UI and still log in', function () {
    cy.request({
      method: 'POST',
      url: 'http://localhost:8000/login',
      body: {
        username,
        password,
      },
    })

    // Check for session cookie
    cy.getCookie('your-site-session-cookie').should('exist')

    // Visit the page behind the authentication
    cy.visit('/profile')
    cy.contains('h1', username)
  })
})

After you set up a working login test with your website, you may want a custom login function that you would call before any tests that require the user to be logged in.

In cypress/support/commands.js you can add a custom function like this:

// Default form login
Cypress.Commands.add('loginByForm', () => {
  const username = 'testUsername'
  const password = 'testPassword'

  cy.request({
    method: 'POST',
    url: 'http://localhost:8000/login',
    body: {
      username,
      password,
    },
  })
})

Then in your tests, you can call this function before your test evaluations like the following:

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

describe('Logging in using XHR request', function () {
  before(() => {
    // log in only once before any of the tests run
    cy.loginByForm()
  })

  it('can bypass the UI and still log in', function () {
    // Check for session cookie
    cy.getCookie('your-site-session-cookie').should('exist')

    // Visit the page behind the authentication
    cy.visit('/profile')
    cy.contains('h1', 'testUsername')
  })
})

Notice our tests will be much more concise the more we need the user to be authenticated.

Linting inline CSS elements:

I had a website I was working on where the CMS didn’t have any CSS linting. This became problematic when there was a CSS typo saved and it made it very hard to find where the broken CSS was.

I decided to create a test where we check each inline <style></style> element. Here is what that test looks like:

/// <reference types="cypress" />
const path = require('path')

describe('Validates CSS', function () {
  // with respect to the current working folder
  const downloadsFolder = 'cypress/downloads'

  context('Style elements', function () {
    it('Has valid css in style element', () => {
      const downloadedCssFile = path.join(downloadsFolder, 'styles.css')
      
      // Go to the page needing to be checked
      cy.visit('http://localhost:8000')

      // Validate each style element as a CSS file
      cy.get('style').each(($el) => {
        cy.writeFile(downloadedCssFile, $el.text())

        cy.exec(
          `$(npm bin)/csslint ${downloadedCssFile}`, 
          { failOnNonZeroExit: false }
        ).then((obj) => {
          // Output the specific error message
          if (obj.code == 1){
            throw obj.stdout
          }
        })
      })
    })
  })
})

A few things to note with this, we need to have the csslint library installed in our project. The cy.exec() function allows us to run the CSS linter on our file we have created as though it is an existing CSS file. This means you can utilize any node package and test against any errors that the package may result in when testing.

Continuous integration:

Cypress can easily be implemented in your continuous integration pipeline. In your pipeline simply add the dependencies and the commands needed for the cypress tests.

The following is an example of a CircleCI pipeline configuration. This will run cypress every time there is a commit and once nightly.

image: node:10.15.3

version: 2.1
orbs:
  # use Cypress orb from CircleCI registry
  cypress: cypress-io/cypress@1

workflows:
  commit:
    jobs:
      - cypress/run:
          start: npm start
          wait-on: 'http://localhost:8000'
          # Key is set in project environment variables
          record: true
  nightly:
    triggers:
      - schedule:
          # Cron in UTC time
          cron: "0 6 * * *"
          filters:
            branches:
              only:
                - master
    jobs:
      - cypress/run:
          start: npm start
          wait-on: 'http://localhost:8000'
          # Key is set in project environment variables
          record: true

Note the record: true lines, this means cypress will record these tests to Cypress’s dashboard. The key CYPRESS_RECORD_KEY must be set in CircleCI as an environment variable. The Cypress dashboard is a service that Cypress offers that is a great visual dashboard of test runs. Tests can be recorded to the dashboard from your local environment as well, you just need your dashboard key. More information on the Cypress dashboard can be accessed on the Cypress website.

Conclusion:

Although this is just scratching the surface of Cypress’s capabilities, I hope it inspires you to try it out on one of your projects. With Cypress, you can have important tests up and running in no time.


About the author

Written by Nicholas Diesslin Pizza acrobat 🍕, typographer, gardener, bicyclist, juggler, senior developer, web designer, all around whittler of the web.
Follow Nick on Twitter!