Table of contents
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!