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 this article:

End-To-End Testing of Angular Components with Cypress
Konstantin Denerz is architect and consultant at Thinktecture and focuses on Angular and User Experience improvement.

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Ā visit,Ā get, 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.

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

Related Articles
.NET
Roslyn Source Generators: Logging ā€“ Part 11
In previous part we lerned how to pass parameters to a Source Generator. In this article we need this knowledge to pass futher parameters to implement logging.
29.08.2023
.NET
Roslyn Source Generators: Configuration ā€“ Part 10
In this article we will see how to pass configuration parameters to a Roslyn Source Generator to control the output or enable/disable features.
29.08.2023
.NET
Roslyn Source Generators: Reduction of Resource Consumption in IDEs ā€“ Part 9
In this article we will see how to reduce the resource consumption of a Source Generator when running inside an IDE by redirecting the code generation to RegisterImplementationSourceOutput.
29.08.2023