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 Knowledge/Engineering/Technologies/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.
A simple test suite I wrote for my portfolio website.
it are sourced from
expect is sourced from
Chai, two core dependencies of Cypress. Also, Mocha provides
context which is just an alias for
See Cypress API Documentation. You’ll be frequently chaining many methods together in a single statement.
- Methods like
cy.getreturn a DOM element that can be further chained with methods like
- Some methods like
cy.clearCookiesdo not yield anything that you can chain further methods on.
Note: all of these
cy.* statements execute asynchronously.
chainer argument is a stringified chainer from
Sinon-Chai, which are dependencies of Cypress.
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.
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
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
# Cypress CI
I used the GitHub Actions workflow YAML file provided by the official docs to run Cypress in a CI pipeline.