Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Cypress.io - Angular End-to-End Testing Tutorial

Episode 117 written by Jeff Delaney
full courses for pro members

Health Check: This lesson was last reviewed on and tested with these packages:

  • Cypress v3
  • Angular v6

Find an issue? Let's fix it

Source code for Cypress.io - Angular End-to-End Testing Tutorial on Github

When you start a new Angular CLI app it generates some end-to-end testing files for Protractor, but do actually use them? For most, the answer is no. This is a shame because end-to-end testing can be transformative for the app development process. In my experience, test-driven development is far more efficient at reducing bugs than strong-typing or using a redux-style state management library. All of these things can help, but e2e testing is unmatched in its ability to catch poor UX implementations and automate tedious UI edge-case tinkering.

Cypress.io will literally change your life as a developer - it’s that good. First of all, it’s easy to setup and learn. You can start writing meaningful tests with minimal effort or learning curves to get over. Each line of code is expressive, for instance, cy.get('button').click() is all you might need to ensure some button exists on a page. Second, it will catch user-experience issues much faster than you will manually - plus it gives you the ability to time travel directly to the problem area. Third, it’s free, so it’s really a no-brainer to give this a few hours of your time.

Demo of end to end tests with Cypress and Angular6

Initial Setup

At this point, I’ll assume you have an existing app - naturally my app is built with Angular 6 and Firebase.

npm install cypress --save-dev

Update the package.json scripts to run Cypress for E2E tests as an alternative to Protractor.

{
"scripts": {
"e2e": "cypress open"
}
}

Now you can run the following commands to complete the setup.

ng serve

npm run e2e

Adding Types and/or TypeScript

It is possible to setup Cypress with TypeScript with some extra config, but I find the benefits of doing so marginal. The API is very intuitive even with vanilla JS and we can use the typings without needing to transpile TS.

The easiest way to add types is to just import them into a plain JS file, for example in cypress/integration/hello.spec.js:

/// <reference types="cypress" />

Now you have most of the the benefits of TS in JS.

Controlling the Viewport Size

It is a good idea to test your UI on various device sizes. You can change the viewport size on individual specs with cy.viewport(...) or set global defaults in the cypress.json file.

{
"viewportWidth": 1920,
"viewportHeight": 1000
}

The Most Basic Spec

Let’s start with a basic test suite that looks for for an h1 tag on the home page. You should be able to read this code and understand what’s going on.


describe('Firestarter', () => {

it('has a heading', () => {

cy.contains('Welcome to Firestarter');
// or...
cy.get('h1').should('contain', 'Welcome to Firestarter');

});

});

A basic cypress test suite

Important Commands

Here’s a quick breakdown of the cy commands we’ll use in this lesson. This is a very, very small sample of what Cypress offers, so checkout the API docs.

You can select things with:

  • contains - Contains will look for matching text, super convenient for finding links and buttons.
  • get - Works just like the $ operator in jQuery. You pass it a selector, such as a CSS class, id, or element name and it returns that element from the DOM.
  • url - grabs the URL from the browser

Then test those things with:

  • should - Allows you to make an assertion based on an element you’ve selected.
  • and - works just like should, but is chained to add multiple assertions.

Testing the Firebase User Auth Process with Cypress

If using Firebase, I recommend setting up your tests to run in a dedicated testing project. Cypress is just like a real user, so your specs will read/write live data.

User auth is often the single most important aspect of end-to-end testing. Let’s take a look at how we might validate our email/password auth flow.

Random Text Fixtures

We need a fake user to sign up for the app. Let’s install the chance.js library (or Faker if you prefer) to randomly generate the dummy data on the fly.

Chance.js to generate dummy data for cypress.io login

npm i --save-dev chance @types/chance

Then import Chance into your project for access to an unlimited amount of random data.

import Chance from 'chance';
const chance = new Chance();

Filling out Forms with Cypress

Best practice: When selecting elements, try to use the component selector name or an attribute that is unlikely to change, such as input[name=foo]. Using CSS classes or ids will make your tests brittle because it’s common for developers to change their names.

A user can sign-up for our app by clicking around elements in the UI - let’s make sure we wired up our UI elements properly.

The type method can be chained to a form input to type into it. An added bonus is that the spec will fail your form cannot be typed into, so you get implicit testing without having to think about anything.

it('signs up a new user', () => {

// Dummy data fixture
const email = chance.email();
const pass = 'ValidPassword23';

// Click Login
cy.get('#navToggle').click();
cy.contains('Login').click();

// Assert URL
cy.url().should('include', 'login');

// Fill out the form
cy.get('input[name=email]').type(email);
cy.get('input[name=password]').type(pass);
cy.get('button[type=submit]').click();

// Assert welcome message
cy.contains('Welcome new user!');
cy.contains('Logout').click();
});

Chance.js to generate dummy data for cypress.io login

Creating Resuable Code in Cypress

There’s a good chance that will want to have a logged in user in multiple different test suites. Cypress allows you to override or create new commands on the cy namespace. Let’s create a login command in the cypress/support/commands.js file:

Cypress.Commands.add('login', (email, pass) => {
cy.visit('http://localhost:4200/login');
cy.get('input[name=email2]').type(email);
cy.get('input[name=password2]').type(pass);
cy.get('button[type=submit]').click();
})

This command is now available to use in any spec and will result in a logged-in user for this session.

it('can do logged-in user stuff', () => {

cy.login('[email protected]', 'password123')

// test stuff here

});

The End

Cypress has changed the way I think about end-to-end testing in Angular. I used to dread it, now I love it. Good e2e specs can be a huge time-saver on complex apps and will prevent regressions that cost you new users and/or revenue.