End-To-End Testing of Angular Components with Cypress

In this article you will see how to enrich your test suite with functional and visual end-to-end tests for your Angular applications.

In diesem Artikel:

kd
Konstantin Denerz ist Software Architekt und Consultant bei Thinktecture mit Fokus auf Angular und der Verbesserung von User Experience.

Setting the Scene

In the previous article, Visual Testing Of Angular Components With Storybook & Chromatic, I described how to use visual tests to test Angular components with Storybook and Chromatic by creating image snapshots. Chromatic tests visual regression on components that are described in Storybook’s stories.

This is both an advantage and a disadvantage. On the one hand, you can write stories with components that use mocked dependencies to develop in isolation and to use visual tests based on those components. On the other hand, you can use all required dependencies (i.e. no isolation) like in the real Angular application. As a result, these stories‘ configuration becomes more complex, and some kind of micro applications arise.

As you can see, testing all application views with Storybook and Chromatic may not be trivial. That is why I would only implement and visually test the core components as well as simple views, the integration of multiple components, in Storybook. But if other complex views are to be covered by a test with less effort for mocking, then we need to test views from one end (the UI) to another end (the database) in our real Angular application, end-to-end.

End-to-End Testing

With end-to-end (e2e) testing, you try to replicate real user scenarios/workflows and interactions. These e2e tests require a running Angular application and a test runner. The test runner visits a specific route of the application and executes functional and visual regression tests.

Functional Testing

The functional e2e tests test a whole workflow, e.g.:

  1. 🚀 Start application
  2. 👤 Login
  3. 🧭 Navigation to a specific application route
  4. 📝 Work with forms and controls
  5. 🧪 Validate the result
  6. 🧭 Navigation to a specific application route
  7. 📝 Work with forms and controls
  8. 🧪 Validate the result

Visual Regression Testing

Visual e2e tests create a snapshot (DOM or image) after a specific workflow and compare them against the baseline, which is an older snapshot.

The small green area in the above image shows the difference between the baseline and the current snapshot.

Protractor or Cypress

There are several frameworks with which you can do e2e testing. The Angular CLI generates new applications per default with the e2e framework Protractor, which was originally developed for AngularJS.

However, if you look at the Angular Roadmap, you can see that the e2e test tool strategy is currently being updated:

To ensure we provide a future-proof e2e testing strategy, we want to evaluate the state of Protractor, community innovations, e2e best practices, and explore novel opportunities.

Thus, it makes sense to look beyond our own noses. In this article, I describe how to use the modern framework Cypress as a popular alternative for writing and executing e2e tests.

An exemplary comparison of Protractor and Cypress is available in this article.

Cypress in Action

Cypress is a frontend tool to set up, write, run, and debug tests. It consists of a test runner and a dashboard service, and it supports e2e, integration, and unit tests.

The current browser support includes Chrome, MS Edge, Firefox, and Electron.

Demo Project

Before we start with the setup and how to write tests, let me introduce the demo application. It contains two routes, the home and the form route. I will demonstrate how to test the user workflow between both routes with Cypress.

The source code for the sample used in this article is available in this GitHub repo.

The user workflow could look like this:

  • Visit home route
  • Click on the add button
  • Check the URL. It should be /form
  • Focus on the form control
  • Enter title: 🚀 test
  • Click on the floating action button (FAB) to save and navigate back to the home route
  • Check the URL. It should be /home
  • Check the items list if the new item with the title 🚀 test is available

The default route is the home route with the HomeComponent:

The form route:

The home route with a new item:

Setup

The Cypress setup in an Angular workspace is easy to do because of the available Cypress schematic library. The @briebug/cypress-schematic project adds Cypress to your workspace and configures the angular.json file.

I created some pull requests during my recent Hacktoberfest contributions to enable workspace (multiple projects) support in the @briebug/cypress-schematic project. Here you can find my merged pull requests.

Run this command to add Cypress to your Angular workspace:

				
					npx ng add @briebug/cypress-schematic
				
			

The schematic library generates a spec.ts file. This is the first e2e test:

Run

Now we can start the runner (an Electron application) for our foo-app demo application:

				
					npx ng run foo-app:cypress-open
				
			

You should now see the test runner with the default spec.ts test:

The runner starts a browser and visits the application URL if you click on the spec. The generated test failed because it expected the Replace me with something relevant string on the home screen. Let’s take a look at the Cypress API before we rewrite the test with something more meaningful.

API

The spec.ts uses the global cy variable that contains the Cypress API. I will use these commands to navigate, interact, and validate the application: Some commands like visitget, or contains wait a while and try again if the execution failed because e.g., DOM elements are not available yet.

Command
Description
visit
Try to visit the path
get
Try to find elements, it’s like document.querySelectorAll
contains
Check if the given text is available on the page
type
Enter the text in the input field
click
Click on the DOM element

Implement the User Workflow to Test

To resemble the user’s interactions with our app, I changed the generated spec.ts to the following:

  • Line #3 changes the viewport before each test
  • #8 visits the home screen and throws an error if the path is not available
  • #9 query the first element with the given attribute selector and click it
  • #10, #13 assert the URL
  • #11 enter the title in the input field
  • #14 validate the home screen

The test result looks like this:

You can see the command log on the left. Click on the log item to get more log information on the console or to see the DOM snapshot associated with the command. Feel free to read more about the test runner on the Cypress page.

Continuous Integration

If you want to add Cypress into your CI, use this command to run the test runner in your continuous integration environment:

				
					npx ng run foo-app:cypress-run
				
			

The test runner can create a screenshot on failure or record a video of the test run for debugging purposes. Both features are interesting in continuous integration environments because you obviously don’t see the visual progress during the test run on the remote server.

Visual Testing

Sometimes you want to check the visual state of your component in your workflows. For example, whether a component is visible or not. Further visual tests could relate e.g., to size and color.

Of course, you could extend the existing functional end-to-end test and check your DOM elements‘ visual state. This would result in complex tests, but there is a better way. Instead, you could create some image snapshots of your components or application and compare them against the baseline snapshots.

Setup

There is a snapshot plugin for Cypress that we can install with this command:

				
					npm i --save-dev cypress-image-snapshot @types/cypress-image-snapshot
				
			

Register and configure the image snapshot plugin in cypress/plugins/index.js:

				
					const cypressTypeScriptPreprocessor = require("./cy-ts-preprocessor");
const {
  addMatchImageSnapshotPlugin,
} = require('cypress-image-snapshot/plugin');

module.exports = (on, config) => {
  on("file:preprocessor", cypressTypeScriptPreprocessor);
  addMatchImageSnapshotPlugin(on, config); // configure plugin
};
				
			

Register the command that should be available on the global cy variable in the cypress/support/index.ts:

				
					import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';

addMatchImageSnapshotCommand();
				
			

Create and Match Image Snapshots

Now you can update the spec with the following statement:

				
					// cypress/integration/spec.ts

describe('create new items', () => {

  beforeEach(() => {
    cy.viewport(300, 600);
  });

  it('should create a new item and show it on the home page', () => {
    const expectedTitle = '🚀 test';
    cy.visit('/home');
    // ...more commands...
    cy.matchImageSnapshot(); // create image snapshot and compare with baseline
  });
});
				
			

After the first run, the snapshots are generated in the cypress/snapshots folder. This is the baseline now.

In the next step, I will change the template of the home component to demonstrate a visual change. This change could be a part of a new feature you have to implement in your project.

The CI build, that depends on the feature branch, could run Cypress again and would retrieve this error:

The test runner generates the diff images in the cypress/snapshots/{spec file name}/diff_output folder. Take a look at my change. The part in the middle shows the difference. The red pixels in the image below visualize changes.

Now you can decide whether it is a bug or a feature. You can run the test runner with an additional --env updateSnapshots=true parameter to update snapshots if the visual change is wanted.

Some Advice

To get started with Cypress, I have two pieces of advice for you:

  • Write tests only for your application. Do not test external applications or systems, like an identity provider, because they can (unnecessarily) change and break your tests.
  • Do not use complex DOM selectors to find elements. Use data-* attributes to specify identifiers. Use this [data-test=title] instead of this selector .mat-form-field > .mat-form-field-wrapper > .mat-form-field-flex > .mat-form-field-infix > #mat-input-0 to find the input. This allows for easier refactoring without breaking tests.

More best practices are available on the Cypress documentation page.

Summary

In this article you have seen, how to enrich your test suite with functional and visual end-to-end tests for your Angular applications. These tests cover user workflows and help to find visual issues at an early stage. Cypress is a viable and well-known tool for building end-to-end tests. The tests‘ execution time is longer than e.g. for unit tests, so it is advisable to run them on a build server. Feel free to contact me if you want to know more about e2e testing with Cypress. For example, to test your application with an external identity provider.

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Angular
SL-rund

View Transition API Integration in Angular—a brave new world (Part 1)

If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
Low-angle photography of metal structure
AI
cl-neu

AI-Funktionen zu Angular-Apps hinzufügen: lokal und offlinefähig

Künstliche Intelligenz (KI) ist spätestens seit der Veröffentlichung von ChatGPT in aller Munde. Wit WebLLM können Sie einen KI-Chatbot in Ihre eigenen Angular-Anwendungen integrieren. Wie das funktioniert und welche Vor- und Nachteile WebLLM hat, lesen Sie hier.
26.02.2024
Angular
SL-rund

Konfiguration von Lazy Loaded Angular Modulen

Die Konfigurierbarkeit unserer Angular-Module ist für den Aufbau einer wiederverwendbaren Architektur unerlässlich. Aber in der jüngsten Vergangenheit hat uns Angular seine neue modullose Zukunft präsentiert. Wie sieht das Ganze jetzt aus? Wie konfigurieren wir jetzt unsere Lazy-Komponenten? Lasst uns gemeinsam einen Blick darauf werfen.
03.08.2023
Angular
yb

Using EntityAdapter with ComponentStore: @ngrx/entity Series – Part 3

As someone who enjoys the ComponentStore on an average level, I have written simple reactive CRUD logic several times. While storing a vast number of entities in the component state might not be a frequent use case, I will briefly illustrate the usage of the EntityAdapter with the @ngrx/component-store.
14.02.2023
Angular
yb

Multiple Entity Collections in the Same Feature State: @ngrx/entity-Series – Part 2

After introducing the @ngrx/entity package, I am often asked how to manage multiple entity types in the same feature state. While I hope that the previous part of this article series has made this more apparent, I will further focus on this question in the following.
07.02.2023
Angular
yb

Managing Your Collections With the EntityAdapter: @ngrx/entity-Series – Part 1

This three-part series of blogposts is targeted at developers who have already gained experience with NgRx but still manage their collections themselves. In the first part I introduce the Entity Adapter, in the second part I show you how to connect it to NgRx and in the third part how to do it with the Component Store as well.
31.01.2023