Tutorial: Javascript End to End Testing with Cypress

Are you struggling with End to End Testing? I feel you. E2E shouldn’t be hard. In the following post you’ll learn how to do Javascript End to End Testing with Cypress.

End to End Testing with Cypress

End to End Testing, also called E2E or UI testing is one the many testing phases covering a web application.

By writing an End to End Test it is possible to assert whether a web application works as expected or not. Plus, with E2E you will test the user flow of your application. Starting from the signup process.

Is End to End Testing really important?

Yes it is. But nobody likes E2E tests. They can be slow, cumbersome and expensive.

On the other hand testing gives you confidence. By testing an application you can be sure it will work for your customers. So what to do?

Enter Cypress: a Javascript End to End testing framework. It will make your life easier.

End to End Testing with Cypress: testing a Knowledge Base application

Recently I wrote about Puppeteer.

The article got a lot of traction and some folks asked if I’m using Cypress for testing. So I thought it would be nice to expand the topic.

End to End Testing with Cypress: developers will love it

Right now I’m building a Knowledge Base application for managing frequently asked questions. The application has already some feature tests but it’s lacking End to End.

How can I fix that? Puppeteer is great for quick testing.

But when it comes to testing an entire application it’s better to stand on the shoulders of the giants.

Cypress is a Javascript End to End testing framework.

In contrast to Puppeteer, which is a library, Cypress gives you a solid platform for writing and automating UI tests. Without worrying too much about the rest.

Back to my application, what requirements do I have?

  1. I want to show a placeholder when there are no articles
  2. I want to log in as Admin
  3. I want to to create and save a new article
  4. Every guest should be able to click a link to the published article

This is how you design any web application. You start by writing the requirements on paper and then a suite of  tests to describe how the app should behave.

And with the requirements in place it’s time to write some tests.

End to End Testing with Cypress: setting up the project

Cypress will fit right away inside any existing application.

To install Cypress move inside your project folder and run:

npm i cypress --save-dev

Give it some seconds and you’re ready to go.

Before moving on scaffold Cypress’s directories with

node_modules/.bin/cypress open

Cypress will create its own directories under ./cypress

Feel free to close Cypress now. We’ll get back to it.

Cypress comes with a nice testing example. Snitch inside cypress/integration/example_spec.jsand you’ll find a file with many lines.

That’s everything you need to know to start testing. But for the sake of learning it’s better to write your own tests from scratch.

In my case I start creating a new file named app_spec.jsinside cypress/integration/. It will hold my first test.

End to End Testing with Cypress: Show a placeholder when there are no articles

Since a blank page would be useless for my users it’s better to display a placeholder when there are no articles.

In the the newly created file I can write:

describe("Knowledge Base Application", () => {

  it("Shows a placeholder", () => {

    cy.visit("/");
    cy.get(".list-group-item")
      .should("have.text", "Coming Soon...");

  });

});

I guess you’re familiar with describeand  it . Cypress uses Mocha under the hood.

Next up you can see Cypress in action.

cy.visit() does what its name suggests: it visits an url.

Next we have cy.get()for querying elements by selector.

Once we have an element we can make assertion about it with should().

Another cool feature of Cypress is that many commands have built-in assertions. In other words there is no need to explicitly assert everything.

In fact I can also write the above assertion as:

cy.get(".list-group-item").contains("Coming Soon...");

and have still the same result.

The assertion would fail if no element containing “Coming Soon..” were to be found.

At this point the first test is ready to run.

To start Cypress run:

node_modules/.bin/cypress open

A new window will open up:

End to End Testing with Cypress: the first test

By clicking on app_spec.js I can see the magic happen:

End to End Testing with Cypress: Show a placeholder when there are no articles

Cypress will launch its own UI and then you’ll see the browser going through every step, as listed in your spec file.

Snapshot is another Cypress’s killer feature. Every step of the test is recorded on the left side of the UI.

Cypress saves a DOM snapshot for every single step. So you can navigate through them conveniently.

At some point you’ll be tempted to close the browser. That’s not necessary. Cypress watches every test file for changes. I think it is another great feature.

End to End Testing with Cypress: Admin should be able to login

Another important requirement for my Knowledge Base application is the ability to login as an admin. At this moment I don’t want any other user.

After admin is logged in it should be presented with the dashboard. The dashboard contains a React JS form for creating a new article.

End to End tsting with Cypress: a React JS form
A React JS form for inserting new articles inside my Knowledge Base application

Users, articles. All of this data are good candidates for fixtures aren’t they? What is a fixture in testing? A fixture is a piece of static data that can be used during your tests.

Fixtures are used for faking JSON responses from a route. Moreover they are useful for defining users and models inside your testing environment.

You can learn more about fixtures in Cypress by going through the docs: cy.fixture and Fixtures are good starting points.

For my admin user I can create a fixture inside cypress/fixtures/users/admin.json. The fixture will look like the following:

{
  "name": "Valentino",
  "email": "valentino@valentinog.com",
  "password": "secret"
}

Be aware that it is important to have fixtures loaded before each test. Otherwise you won’t be able to access them. It is a good idea to load fixtures inside a beforeEach hook:

describe("Knowledge Base Application", () => {

  beforeEach(() => {
    cy.fixture("users/admin").as("admin");
  });

  // previous test omitted for brevity

});

In Cypress you can load a fixture from a range of files.

Also, a fixture can be aliased with .as(). Later on you can use this.admin as it were a regular Javascript object.

NOTE: Make sure to use a regular function in your itblock while accessing fixtures. More about the topic: arrow functions in Javascript and Using Aliases in Cypress.

With this in place it’s time to add the new test inside the describe block. The test will look like:

describe("Knowledge Base Application", () => {

  beforeEach(() => {
    cy.fixture("users/admin").as("admin");
  });

  // previous test omitted for brevity

  it("Should be able to login: admin", function() {

    cy.visit("/login");
    
    cy
      .get('input[name="email"]')
      .type(this.admin.email)
      .should("have.value", this.admin.email);

    cy
      .get('input[name="password"]')
      .type(this.admin.password)
      .should("have.value", this.admin.password);

    cy.get("form").submit();

    cy.location("pathname").should("eq", "/home");
  });

});

Let’s break down the above test.

First we visit the /login page. I’m able to write the relative path because Cypress makes easy to configure the base url. So you don’t have to type it over and over.

The base url is defined in cypress.json(you can find it inside the root project folder):

{
"baseUrl": "http://127.0.0.1:8000"
}

Also within the test you can see another Cypress command. .type()is used for typing into a DOM element (an input in this case).

Then there are some assertion about the value typed inside every input.

Finally I’m submitting the form with .submit().

Another important assertion is made about the redirect: with cy.location()you can assert that the user is redirected to the correct page after the login process is completed.

After adding the new test I can just save the file and Cypress takes care of the rest. It will re-run the test suite without any intervention:

End to End Testing with Cypress: testing the login

The login test is passing! Time to move on to the next requirement: creating and saving a new article inside my Knowledge Base application.

End to End Testing with Cypress: Admin should be able to create a new article

A Knowledge Base application contains articles that guides the user through resolving simple problems.

How to insert an article inside my application?

To create a new article I must log in as an Admin. A React JS form will appear in my dashboard. The form works fine but it isn’t covered by an automated test yet.

Cypress makes testing React JS forms easy as a breeze.

Now, the team at Cypress provides a ton of advices. One of them is: bypass the login UI when building up state during testing. You can find more here: Do not use your UI to login before each test.

I encourage following the above best practices. But for the scope of this post I will use the UI to login again before creating a new article.

What’s an article made of? I can think of:

  1. a title
  2. a body
  3. a SEO title
  4. a meta description

All of those can be defined inside a fixture, cypress/fixtures/models/article.jsonwith the following content:

{
    "title": "Better Javascript End to End Testing with Cypress IO",
    "seo_title": "End to End Testing done right with Cypress IO",
    "seo_meta_description": "Do you want to improve your happinness as a QA engineer? Look no further",
    "body": "Are you struggling with End to End Testing? Do you want to improve your happinness as a QA engineer?"
}

The article fixture should be loaded before each test.

Let’s update the hook to reflect the changes:

//
  beforeEach(() => {
    cy.fixture("users/admin").as("admin");
    cy.fixture("models/article").as("article");
  });
//

Oh, by the way, these hooks are useful for everything that must run before or after each test. Such as seeding data.

Learn how to use hooks here: End to End Testing with Cypress: Seeding Data

Ready to write the test!

A React JS form can contain many inputs. An End to End test with Cypress covers the entire process of typing inside every field:

it("Should be able to create a new article: admin", function() {
  // that's considered bad practice but I'll do it either way for the scope of this post
  cy.visit("/login");

  cy
    .get('input[name="email"]')
    .type(this.admin.email)
    .should("have.value", this.admin.email);

  cy
    .get('input[name="password"]')
    .type(this.admin.password)
    .should("have.value", this.admin.password);

  cy.get("form").submit();

  // Compiling the form
  cy
    .get('input[id="title"]')
    .type(this.article.title)
    .should("have.value", this.article.title);

  cy
    .get('input[id="seo-title"]')
    .type(this.article.seo_title)
    .should("have.value", this.article.seo_title);

  cy
    .get('input[id="seo-meta-description"]')
    .type(this.article.seo_meta_description)
    .should("have.value", this.article.seo_meta_description);

  cy
    .get('textarea[id="body"]')
    .type(this.article.body)
    .should("have.value", this.article.body);

  cy.get("select").select("Published");

  cy.get("#article-form").submit();
});

The test above uses the same commands we’ve seen in the login test. In fact, when writing and End to End test with Cypress you’ll likely use these 3 commands:

cy.get() for selecting an element

.type() for tiping inside an input

.should() for asserting something

.submit() for submitting a form

Notice how the test is readable by anyone. It’s plain english.

By saving the file Cypress will run the test suite again:

End to End Testing with Cypress: Admin should be able to create a new article

How nice!

End to End Testing with Cypress: Guest should be able to see a published article

One last thing I would like to test is the ability to read articles.

Since it’s a Knowledge Base every guest should be able to see a published article.

With Cypress it’s a matter of adding a simple test:

it("Should be able to see the published article: guest", function() {
  cy.visit("/");

  cy
    .get(".list-group-item")
    .contains(this.article.title)
    .click();
});

The above test:

  1. gets an element by the .list-group-item(it comes from Bootstrap 4)
  2. assert that element contains the article title
  3. finally it clicks on the element

That’s it!

End to End Testing with Cypress: wrapping up

End to end testing shouldn’t be hard.

Cypress makes integration testing pleasing and enjoyable.

The team at Cypress really nailed it. You can start writing end to end tests without worrying about browsers, Selenium, and other scary stuff.

The framework is so nice that I’m planning to use nothing but Cypress for integration testing. Starting from now.

Plus, the documentation is pure gold: Cypress Docs are filled up with best practices and examples.

Last but not least, make sure to give the Dashboard Service a try.

Stay tuned for more about the topic!

Thanks for reading!

Valentino Gagliardi

Valentino Gagliardi

I help busy people to learn Web Development and JavaScript - DevOps Consultant
Valentino Gagliardi

8 Replies to “Tutorial: Javascript End to End Testing with Cypress”

  1. We already have the application developed, and I as a tester have access to the application URL. Can we perform the E2E testing using Cypress with the URL and login credentials??

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.