Ovie Okeh Programming enthusiast, lover of all things that go beep.

How to write useful end-to-end tests with Cypress

7 min read 2098

How To Write Useful End-To-End Tests With Cypress

Testing has become an essential aspect of software engineering, and for good reason, too. Writing software of any complexity can often be a messy task, and this only gets worse when teams get larger and more people start to work on the same codebase.

This problem is worsened in frontend development, where there are so many moving parts that writing unit and functional tests might not be enough to verify the application’s correctness. For example, you can’t really verify that a particular user flow doesn’t cause issues through a unit test.

End-to-end testing allows you to replicate user behavior on your application and verify that everything works as it should. If you’re writing production-grade web apps, writing e2e tests is a no-brainer.

In this article, we’ll take a look at how to write useful e2e tests on the frontend using Cypress. While there are other e2e testing tools like Selenium and Nightwatch.js, we’ll focus on Cypress because of its suite of features, which include time traveling through your tests, recording your tests for later playback, etc.

Note: To follow along with this tutorial, you’ll need to have Node.js installed. If you don’t have it, you can download the latest stable version here.

LogRocket Free Trial Banner

Setting up Cypress

Let’s create a new project and set up Cypress so we can get started.

Initialize a new project by running the following:

mkdir cypress-tutorial
cd cypress-tutorial
npm init -y

Install Cypress as a dev-dependency:

npm install --save-dev cypress

Update the project’s scripts by opening package.json and updating your scripts to the following:

"scripts": {
    "test": "$(npm bin)/cypress run",
    "cypress:open": "$(npm bin)/cypress open"
  },

Create a file called cypress.json in the root folder. This is how you customize Cypress’ behavior for this specific project. Add the following in it and save.

{ "chromeWebSecurity": false }

Why we do this will be explained further down, so keep reading.

Writing our tests

User stories (for those of us who practice Agile) usually follow a format that might look similar to this: “When a user takes a specific action, then the user should see this.”

Analyzing this, you can determine how to go about writing an e2e test for that specific story. All you need to do is simulate taking the action the user is expected to take through the test and then assert that the application state matches what you expect.

For frontend testing, those steps can usually be broken down into these:

  1. Visit a page on your app
  2. Query an element on that page, e.g., buttons, dropdowns, etc.
  3. Interact with the element, e.g., clicking on buttons, dragging divs, etc.
  4. Confirm that the new state after the interaction is correct

We’re going to write three tests to assert that we can perform certain actions on Wikipedia by mimicking a user’s actions. For each test, we’ll write a user story before writing the actual test.

Before we get started with writing the tests, you will have to create a special folder to hold your Cypress tests. In the root of your project, run the following commands:

mkdir cypress && cd cypress
mkdir integration && cd integration

All our Cypress tests will go inside this /integration folder. Don’t worry about the name of the folder, as you can store any type of test in here.

Note: Don’t use Cypress on a website/application that you do not own. Cypress is solely meant for testing your own apps and not as a tool for automating your personal life.

Test 1: A user can perform a search from the homepage

When a user visits the homepage, types in the search box, and click on the search icon, then the user should see a new page populated with the results from their search term.

The user story is pretty straightforward: it is just asserting the correct behavior for a search action on the homepage by a hypothetical user. Let’s write the test for it.

Inside the /integration folder, create a new file called homepage_search_spec.js and open it in your favorite text editor (I use Visual Studio Code).

describe('Testing Wikipedia', () => {
  it('I can search for content', () => {
    cy.visit('https://www.wikipedia.org');
    cy.get("input[type='search']").type('Leo Panthera');
    cy.get("button[type='submit']").click();
    cy.contains('Search results');
    cy.contains('Panthera leo leo');
  });
});

Let’s go through the test and see how it matches the steps we defined earlier.

  • Line 3: Visit a page on your app
  • Line 4: Query an element on that page and interact with it
  • Line 5: Query an element on that page and interact with it
  • Line 6: Confirm that the new state after the interaction is correct
  • Line 7: Confirm that the new state after the interaction is correct

A majority of your e2e tests will follow the above format, and you can begin to see the benefits of testing your applications this way. You don’t need to care about how the app is handling these actions in the background; all you care about is that your app should perform a certain way for a certain action.

How about the syntax? One thing I like about Cypress is how intuitive the syntax is. It uses natural language that makes it easy for even non-programmers to read and understand your tests.

Let’s run the test. In your terminal, run npm test and Cypress will look inside the cypress/integration folder and run all the tests there.

But this is not the only way to run your tests. You can run them in a GUI, where you get a real-time view of your app as Cypress manipulates it according to your test spec.

In your terminal, run npm run cypress:open and a window should pop up that looks like this:

Cypress Window

Simply click on homepage_search_spec.js to run your tests and you should see another window pop up.

Testing WIkipedia Cyprus Window

At the top left of the window, you can get a quick view of how many passing and failing tests you have in your test suite. On the right side of the window is what a user would see if they interacted with your application the way you specified in the test.

The left side is where most of the magic happens. Here you can see which test is currently running, the actions being taken, and the results of those actions.

With this simple test, we’ve been able to assert that whoever worked on the search functionality for Wikipedia has been able to satisfy the hypthetical user story requirements.

Test 2: A user can switch languages from the homepage

When a user visits the homepage, clicks on the language switcher, and selects a new language, then the user should be redirected to the appropriate domain for the selected language.

Inside the /integration folder, create a new file called homepage_switch_language_spec.js and open it.

describe('Testing Wikipedia', () => {
  it('A user can switch languages', () => {
    cy.visit('https://wikipedia.org');
    cy.contains('Read Wikipedia in your language');
    cy.get('#js-lang-list-button').click();
    cy.contains('Yorùbá').click();
    cy.url().should(
      'equal',
      'https://yo.wikipedia.org/wiki/Oj%C3%BAew%C3%A9_%C3%80k%E1%BB%8D%CC%81k%E1%BB%8D%CC%81',
    );
    cy.contains('Ẹ kú àbọ̀');
  });
});

Now let’s talk about some Cypress-specific syntax.

On line 3, we’re simply visiting Wikipedia’s homepage. On line 4, we assert that we’re on the page we want to be on by confirming that the page contains the text “Read Wikipedia in your language.”

On line 5, we query the language switcher button by its id and we simulate a click on it. I know the id because I inspected it in Chrome’s devtools. This brings me to an important concept in writing Cypress tests: there are multiple ways to select a DOM element on Cypress. You can do it by targeting its id, its class, or even its tag type.

In this particular test, we’re targeting the button’s id, but in our previous test, we targeted the tag name and attribute. You can read about the different ways of selecting a DOM element here.

On line 6, we encounter another common Cypress command, which you’ll notice shows up a lot in tests. We assert that there’s an element with the text “Yorùbá” on the DOM, and then we simulate a click on it.

This action will cause Wikipedia to redirect you to the appropriate domain for the language you selected. In our case, we selected the Yorùbá language from West Africa, and we can confirm that we were redirected to the correct page by looking at the current page’s URL.

On line 7, we do exactly that. By calling cy.url() we get the URL of the current page as text and then we assert that it should equal the appropriate domain for the Yorùbá language. On line 8, we have an extra optional check to see if there is any content on the page in the Yorùbá language.

Bonus fact: Ẹ kú àbọ̀ means “Welcome” in the Yorùbá language and is pronounced: Eh – Koo – AhBuh.

Run the test the same way we ran the previous one, and it should pass.

Test 3: A user can check the definition of a word on Wiktionary

When a user visits the homepage and clicks on the link to Wiktionary, then the user should be redirected to wiktionary.org.

When a user on wiktionary.org searches for a definition by typing in the search bar and hits enter, then the user should be redirected to a page with the definition of that search term.

Another straightforward user story. We want to check for the definition of the word “Svelte” on Wiktionary, but we’ll start off on Wikipedia’s homepage and navigate to Wiktionary before searching for the word.

Inside the /integration folder, create a new file named homepage_search_definition.js and open it.

describe('Testing Wikipedia', () => {
  it('A user can search for a word', () => {
    cy.visit('https://wikipedia.org');
    cy.get('.other-project-link')
      .eq(2)
      .click();
    cy.url().should('equal', 'https://www.wiktionary.org/');
    cy.get('#searchInput').type('Svelte{enter}');
    cy.contains('Etymology');
    cy.contains('svelte');
  });
});

Let’s walk through this test the same way we did the previous one.

On line 3, we visit Wikipedia’s homepage as usual. On line 4, we target a class name, pick the third element with the class (don’t forget that indices start at 0), and simulate a click on the element.

Before we move further, a little caveat: if your code relies on autogenerated CSS classes, targeting elements by class names might result in inconsistent test results. In those cases, targeting by id or tag type and attribute would be the way to go.

On line 7, we assert that we’re on the correct domain by checking the URL using cy.url().

Another caveat: If you’re testing your own app, the need to navigate to another domain may be rare, and it’s for this reason this test would fail if we didn’t add "chromeWebSecurity": false in our cypress.json config.

On line 8, we target the search input by its id, and then we simulate a change event by typing in “Svelte” and hitting the enter button (hitting the enter button is handled by “{enter}”). This action takes us to a new page with the result of our query.

We then confirm on lines 9 and 10 that we’re on the correct page by asserting that the words “Etymology” and “svelte” can be found on the page.

Run the test the same way we ran the previous one and marvel at your newfound skill!

Conclusion

You’ve seen how easy it is to verify the correctness of your web app by writing simple e2e tests using Cypress. We’ve barely scratched the surface of what Cypress can do and some of the features it provides.

For example, you can even see screenshots of your tests and watch recorded videos by looking under cypress/videos. You can define custom commands to avoid code repetition, mock API response data using fixtures, etc.

Having well-written end-to-end tests can save you hours of development time and can help you catch bugs and unexpected behaviors before you merge into production. Get started by going through the Cypress docs and playing around until you get comfortable enough to start writing live tests.

Plug: , a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Ovie Okeh Programming enthusiast, lover of all things that go beep.

4 Replies to “How to write useful end-to-end tests with Cypress”

  1. One comment on your test. You are using cy.contains() and not cy.should()
    cy.contains is a selector and not a validator, sure your test will fail if it cannot find the element but its not an assertion.

Leave a Reply