Cypress is an end-to-end testing library for testing the entirety of a web app, including its frontend and backend, in a way that closely simulates how real users would use the app. It also has an API for writing integration tests and unit tests.
Cypress tests aim to simulate the user experience as closely as possible. This means that tests you write will usually start up a real browser process, navigate to the URL of your web app, then execute a series of user interactions (eg. clicking on links, buttons, sending keystrokes, etc.) and let you make assertions on how the document should respond and what its contents should have.
The purpose of end-to-end testing this way is to give you confidence that the user can perform critical actions without error. For example, you might have a test that verifies your web appâs authentication system, the purchase of an item, the sending of a message, etc. Unit tests with Jest, for example, wouldnât be sufficient for that purpose.
Writing Tests
In general, the arrange, act, assert pattern for writing unit tests is also a useful way to structure e2e Cypress tests.
Example
A simple test suite I wrote for my portfolio website.
Note: describe
and it
are sourced from Mocha and expect
is sourced from Chai, two core dependencies of Cypress. Also, Mocha provides context
which is just an alias for describe
.
API
See Cypress API Documentation. Youâll be frequently chaining many methods together in a single statement.
- Methods like
cy.get
return a DOM element that can be further chained with methods liketype
,click
,contains
, etc. - Some methods like
cy.clearCookies
do not yield anything that you can chain further methods on.
Top-Level Methods
Note: all of these cy.*
statements execute asynchronously.
Chained Methods:
The chainer
argument is a stringified chainer from Chai, Chai-jQuery, Sinon-Chai, which are dependencies of Cypress.
Aliases
You must always chain commands off of an invocation on cy.*
otherwise commands wonât be properly enqueued. Eg. if youâre doing const elem = cy.get(...); elem.then(...)
, then you need to use an alias instead, as shown below.
Fixtures
In Cypress, fixtures are a collection of static test data that can be used by tests. Theyâre located at cypress/fixtures
and are typically .json files, but can also be .js, image files, etc. The common usage of fixtures is in stubbing network requests.
Reusuable Custom Commands
Cypress gives you many useful commands, however you might need some custom reusable helper functions to help with stubbing network requests, for example. You define custom helpers in cypress/support/commands.ts
by doing the following:
This makes your helpers available under the cy
object, eg. from the above example, weâd be able to access cy.helperName()
from any test.
Mocking Network Requests
Often, youâll want to test the frontend independently of the backend, that is, you might not actually want your frontend to make requests to your backend server. You can do this by stubbing API requests with responses using cy.intercept
.
Tradeoffs
When you stub network requests, youâre no longer writer âtrueâ end-to-end tests. Your tests are more isolated and generally less flaky since it has fewer points of failure, however you are straying away from testing the real user experience.
If you write true end-to-end tests, then:
- If you have a database, youâd have to seed it to generate state.
- Tests are possibly much slower since theyâll actually go through the full backend request-handling logic.
- Itâll be hard to test for edge cases like network failure.
Itâs recommended to maintain a balance of both stubbed tests and true end-to-end tests (especially for the critical user actions in your application like authentication).
Seeding the Database
Cypress CLI
The Cypress package ships with a powerful CLI. Official reference.
Some basic commands to know and consider adding to the NPM scripts inside package.json
:
Cypress CI
I used the GitHub Actions workflow YAML file provided by the official docs to run Cypress in a CI pipeline.