In this post, let's dive into E2E testing with Cypress and how to use it in a real world application.
We'll cover:
- Setup
- Writing tests
- Troubleshooting flaky tests
Setup
Get started by running yarn add cypress.
This will generate a cypress folder in your project's root directory, and should be initialized with lots of great example tests. If not, you can find examples here.
Next, run yarn run cypress open and select E2E testing and Chrome. Look to Cypress's getting started guide here for complete instructions. If everything looks good, let's keep going!
Writing Tests
Let's write our first test. To start, we'll create some helpers to navigate and log in to our app.
There's quite a few things happening here! First, we're initializing an intercept that will listen for requests to our authentication endpoint, filling out a form, then attempting to log in. The ability to wait for a response from an API endpoint is one of the most useful features of Cypress, and I rely on it heavily.
Finally, let's put these previous two helpers together for the actual test.
Our directory should look something like this, with each feature or route living in a new folder within /e2e. All Cypress tests should have a cy in their file name.
Here's what this test run looks like in the Cypress UI using my frontend, with speeds throttled so you can see what's happening:
Tips for writing tests:
- Use the attribute data-testid and reserve it for selecting Cypress and test-specific elements
- Select nested elements using a single .get(), or chaining .get() with .find() -- chaining .get()'s will not select nested elements
- Use aliases to use the value of API and DOM elements.
- Try out the cy.intercept command with its options object, to fine tune the way that you intercept API requests
- Leverage helper functions to reuse code between each test, especially for navigating to a page
- Initialize cy.intercept's first in your tests
Here's an example using these tips:
Also, leverage the Cypress UI to find suggested selectors. In this case, data-testid="login-submit" is the suggested selector for the button element labeled "SIGN IN".
Handling Flaky Tests
Intermittent test failures can make Cypress a pain to work with.
Typically, flaky test failures should be fixed at their root: your app's code. I've found that many test failures are due to duplicated API requests and API request timing. Using cy.intercept and cy.wait can help expose these issues.
But there are some times where problems with third-party component libraries, or inconsistently reproducible issues will occur that you may not be able to resolve. Then what? Try these solutions below to remedy intermittent test failures, or in my experience, expose underlying bugs in your app.
1. Adjust your main config
The focus here is the retries config, where we can tell Cypress to run failed tests multiple times in the case of a race condition or other intermittent issue. This will make failed tests run a max of 2 more times, and it can resolve some frustrating problems when running in CI.
2. Adjust your E2E config
Next, we'll look at the global config for Cypress tests that lives in cypress/support/e2e.ts.
I've largely had success running Cypress tests locally, with both cypress open and cypress run. But when these same tests run in CI, the flaky tests really come out to play. From watching videos of failed tests and lots of troubleshooting, this appears to be due to CI running tests on slower network speeds and a less powerful machine. Running these tests on a home WiFi network and a Macbook Pro just doesn't typically expose them.
Luckily, Cypress gives us some options for simulating slower network and hardware. We want the throttle settings to only be in effect when running locally.
3. Exclude a test
As a last resort, you can exclude specs from your test runs
These pointers should be enough to get you started, and now you should be set to write tests that cover all of your users flows!
Oh, you did that already? Sweet. Check out the next step: integrating your Cypress tests in Continuous Integration (CI) here.