Table of contents

  1. Intro
  2. Cypress
  3. Comparison
  4. Setting up Cypress
  5. Amazing features
  6. Continuous Integration
  7. Conclusion

Intro

Those of us familiar with E2E testing a user interface, we all know the struggle. Not only coding with waits and timeouts but setups that were harder to manage especially when running it on a continuous integration platform. Maintaining versions, network issues, browser support, …

For as long as I can remember, there was one constant in this setup! Selenium was always there. No matter what framework you used, Nightwatch, Protractor, Gauge, Robot, … Selenium was the man in the middle. All I can remember from building a Selenium setup a few years ago, are all the difficulties and frustrations. Communication was not working, versions were not matching, timeout issues due to network lag, …. But still, Selenium was a dependency that was needed!

Not that I am not grateful for Selenium and its team of developers and maintainers, because let’s be honest, what would we have done without it? But now, ohh yeah, there’s a new kid in town.

Cypress

Fast, easy and reliable testing for anything that runs in a browser ~Cypress

Cypress is aiming to provide its users with a bundled experience for writing end-to-end tests for web applications. While lots of other frameworks (as mentioned above) are all interacting over a Selenium server, for remote communication and by definition, running its tests outside of the browser, Cypress is executed inside the browser. Therefore Cypress is executed in the same runtime as your application itself. Because of this, Cypress has native access to every single object. The window, the document, a DOM-element, a service worker, … . Cypress does not need to send commands over-the-wire and can just access everything.

Comparison

  Selenium Cypress
Debugging: Hard/Remote Easy/Access to everything/Nice tool
Speed: Remote = slow In browser = Fast
Parallel: V V
Headless: V V
Language support: Java, Perl PHP, Python, Ruby, C#, Javascript Javascript
Browser support: Everything Only webkit

Basic Architecture

Cypress consists of a few different building blocks. One of them is its own Nodejs process. You can look at this as being a backend. This backend then launches a browser window, sets up a proxy to this browser window and sets the domain to localhost. The browser window has two iFrames inside. One is for Cypress itself. The second one will hold the application under test. To make it possible to communicate with the application’s iFrame, it injects a <script>-tag that also sets its domain to localhost. Because now, both iFrames are running on ‘localhost’, it is possible to access everything of the application.

The proxy is proxying all requests from the web application itself to its backend. Because the proxy is part of the Cypress-setup, Cypress can act as the man-in-the-middle and spy on, mock or modify the requests and responses.

Communication between the Cypress Nodejs-backend and the Cypress iFrame, that is running the tests, is through a websocket.

There are a few downsides as seen in the comparison. Because Cypress is running inside of the browser, the language is Javascript. Cypress only supports Chrome so far. Although Cypress is working on supporting other browsers. Today there is no way of testing in Safari, Ìnternet Explorer, Edge, … Cypress tests are written using Mocha and Chai.

Setting Up Cypress

Installing Cypress

Installation of Cypress is really simple. You can install it through Yarn:

$ yarn add cypress --dev

Or plain npm:

$ npm install cypress --save-dev

You can also install it globally. This way you do not need a local package.json to run Cypress and all Cypress commands can be run straight from the command line.

Running Cypress

Depending on how you just installed Cypress, you can run Cypress by: $ yarn run cypress open of $ $(npm bin)/cypress open

Or globally: $ cypress open

The first positive surprise! When opening Cypress for the first time, it notices that you haven’t run it before and it kindly scaffolds a /cypress folder with examples into your project. This way, you already have a configured base to start from.

As you can see, Cypress opens its own application. This is kind of a backend application that will orchestrate the tests. Running one of the tests, means that Cypress will open a second window, which is actually just a new browser window. It will then inject itself into that window in one frame, and load the application under test in another frame.

Configuring Cypress

You can custom configure Cypress by adding a cypress.json file in the root of your project.

{
  "baseUrl": "http://demo-app.localtest.me/demo-app",
  "integrationFolder": "src",
  "testFiles": "**.spec.js",
  "reporter": "mochawesome",
  "reporterOptions": {
    "overwrite": false,
    "html": true,
    "json": false,
    "reportDir": "results",
    "reportFilename": "report.html"
 }
}

You can always override these settings on the command-line: $ cypress run --spec src/** -c baseUrl=http://localhost

For a full overview of the configuration options, just check out the docs.

Reporters

Just like other frameworks, Cypress lets you add custom reporters for the test results. As you can see above, we’ve added mochawesome.

Install it via Yarn:

$ yarn add mochawesome --dev

And then manually add it to the cypress.json config file.

Take a look at the documentation for the configuration.

Cypress and TypeScript

Cypress ships with official type declarations for TypeScript. This allows you to write your tests in TypeScript. All that is required is a little bit of configuration. ~docs

The documentation itself is linking to some different examples. You can read all about the setup here. And you can also take a look at the npm package add-typescript-to-cypress here.

Writing your first tests

As shown above, you can configure the path to your spec-files. In our case, we are using /src. Cypress will go through that directory and show all the spec files when using Cypress in development mode. When running Cypress command line to only run the tests, it will just run all those spec-files and then create the report.

In our small example we have an Angular demo app that has a material sidenav with three links. Dashboard, clients and products. The latter two both have a material datatable.

Let’s say we want to test our clients navigation and datatable. Create a spec file in the /src directory, in our case, named clients.spec.js:

/// <reference types="Cypress" />

context('Clients test', () => {
    beforeEach(() => {
      cy.visit('/clients')
    })

    it('Clients page should have Clients as a title', () => {
      cy.get('.table-container-header h1').contains('Clients');
    });

    it('Clients table should initially have 20 rows', () => {
        cy.get('.mat-row').then(($rows) => {
          expect($rows.length).to.be.eq(20);
        });
    });

    it('Clients table should show 10 rows when pagesize is set to 10', () => {
        cy.get('mat-select').click();
        cy.get('mat-option').contains('10').then(option => {
            cy.wrap(option).contains('10');
            option[0].click();
            cy.get('mat-select').contains('10');
            cy.get('.mat-row').then(($rows) => {
                expect($rows.length).to.be.eq(10);
              });
        });

    });
})

Running your first tests

If you would now run $ yarn run cypress open. Cypress will open itself, showing you your new spec-file. You can now run your spec-file by double clicking it, or click the ‘run all’ option on the top right of your Cypress application.

Changing the spec-file will trigger a reload/retest in your Cypress-environment.

Amazing features

The purpose of this post is not to provide you with some sample code, but trying to convince you to take a look at Cypress. To do so, I’ll quickly go over some really nice features because besides the easy setup and nice main application, Cypress has much more to offer.

Debugging with Cypress

One of the hardest things when writing E2E tests is debugging. Running tests over and over again, while logging everything to check what is going on, is now history. Cypress injects itself in the same window as the application under test, so it has access to everything. Everything, including the debugger.

This means that you can actually debug your test code as you would debug the application itself. Although using the debugger is not that straight forward, it’s a great help. Check out out the documentation here.

Snapshots

Take a look at the image above. As you can see, Cypress takes a snapshot at every stage of the test. You can navigate through them later and see the snapshot at a specific time and even see the difference in the state of the application before a request and after its response when running XHR requests.

Network accessibility

As mentioned above, Cypress can take snapshots before and after each XHR request. Cypress knows what is going on under the hood because it is running in the same window. This makes it easy to implement stubs and spies.

A simple example for our use case would be intercepting the client-service calls and return mocked data. To do so, Cypress needs to run a server. This can be done by just running cy.server(). Next step is to define the route you want to listen on and attach new data to it. Cypress enables this with its route configuration.

beforeEach(() => {
  cy.server();
  cy.route('GET', '**/*/api/client-service/**/*',
    {
      "content":[
        {
          "id":18,
          "firstName":"Tatiana",
          "lastName":"Velez",
          "email":"diam.dictum@Proin.net",
          "birthday":"626286135",
          "city":"Dumfries",
          "zip":"694245"
        }
      ],
      "pageable":{
        "sort": {
          "sorted":true,
          "unsorted":false
        },
        "pageSize":20,
        "pageNumber":0,
        "offset":0,
        "paged":true,
        "unpaged":false
      },
      "last":false,
      "totalElements":1,
      "totalPages":1,
      "first":true,
      "sort":{
         "sorted":true,
         "unsorted":false
      },
      "numberOfElements":1,
      "size":1,
      "number":0
    }
  ).as('stub-clients');   
  cy.visit('/clients');
})

Screenshots and videos

Another cool embedded feature is the ability to capture screenshots or record videos. Cypress comes with screenshot and video recording out of the box. Screenshots always come in handy when trying to find a bug. Cypress even lets you take a screenshot, manually, from within your code. Before, we were always setting this up using ‘yet another plugin’ (and dependency).

Continuous integration

Running Cypress on a continuous integration platform is also pretty easy. You just want Cypress to run the tests and not to open its Electron test manager for development. $ cypress run just does that. Just append the other options you want to override from the cypress.json and you are good to go. $ yarn run cypress run --spec 'src/**/*' --reporter mochawesome --reporter-options reportDir=results,reportFilename=report.html.

Example package.json:

scripts: {
  ...
  "cypress:ci": "cypress run --spec 'src/**/*' --reporter mochawesome --reporter-options reportDir=results,reportFilename=report.html"
  ...
}

Conclusion

Although there are still some downsides to this new player in the E2E testing field, such as only supporting JavaScript and Chrome, there are just too many positives to keep it on the sideline. Cypress provides us with a new amazing test runner and manager. It strips all the hassles of previous setups and provides us with a real robust framework. It is providing us with cool but effective features like the snapshot time travel, easy debugging, headful/headless mode for CI and more.

Cypress is too good to not take a look at!

Tim is a senior developer and architect at JWorks Ordina Belgium. Tim has a DevOps culture mindset and is experienced in many different domains. From frontend to backend to pipelining and automation. Tim is keen on learning new technologies.

Tim is a true sportsman and spends part of his free time running and working out. Tim is also very passionate about surfing and is learning how to snowboard.

Glenn is a Java Consultant at Ordina Belgium. With a background in testing, his main interests lie in all things automation, from automated testing to CI/CD and cloud automation.