Emulate user actions in Angular

A guide to end-to-end testing with Protractor & Jasmine.

Gergo Bence Szucs
Betsson Group
6 min readNov 5, 2020

--

Unit testing is really powerful to verify our components’ logic in an isolated environment by mocking their dependencies. However, sometimes we want exactly the opposite; to test how our components interact with each other. This is a convenient way to mimic and automate the actions of a real person within our application.

Stepping one level higher in abstraction, let’s talk about how end-to-end (e2e) testing works with the Protractor framework, why it’s useful and how you can utilize it in your Angular project.

Internals of the Protractor framework

Protractor provides a powerful framework to execute Angular e2e tests, by integrating the webdriver-manager package, which is used to start up a local selenium server. This server will be utilized to create a web-browser instance (e.g. a new Chrome window) and handle the Protractor requests sent to the website.

When doing e2e testing, most of our instructions will be asynchronous (because we actually need the page to handle our request, e.g. DOM changes, backend communication, navigation).

The best part of Protractor is the fact that it’s able to automatically wait for Angular requests to complete, allowing us to write test cases in a synchronous style while keeping the asynchronous behavior.

However, in the second part of the article, we’ll take a look at scenarios where this feature can’t be used and how it’s possible to still write simple unit tests, with the async/await style.

Getting started with Protractor

Using npm, you can set up Protractor in a blink of an eye. The official documentation advises to install the Protractor and webdriver-manager packages globally. However, it may be a better idea to allow Protractor to handle the selenium server internally. This isn’t only better because you can skip the global installation of the webdriver-manager, but Protractor will handle the starting and stopping of each instance automatically. 👍

Such configuration can be done via a configuration file, which is a mandatory parameter for Protractor to execute the test cases.

Looking at this configuration file, we can see that the actual test cases come from the specs/home folder. So when we execute protractor conf.js, the framework takes over control, launches a local selenium server and a Chrome browser instance (either headless or visible, based on the settings) and executes the specified test cases. Did you notice that we’re usingconf.js? You may ask, aren’t we working with TypeScript? Well, we’re, but the web browser isn’t. ⚠️

So before you could do any testing, all your TypeScript files must be compiled to JavaScript first. To do so, you have to execute the npm run tsc command while having a properly configured tsconfig.json file in your project’s root folder, something like this:

The important part is properly setting the files to include and exclude from compilation. Source mapping (mapping the JavaScript files to their TypeScript source, so breakpoints in the browser can be properly shown in TypeScript files) should be enabled if you would like to debug the tests (we’ll take a look at that at the end of the article). You may notice, that there is a specs and a pageObjects folder. Let’s see what exactly they are.

Organizing the test cases

To properly organize your test cases you should separate the DOM manipulation/queries from the actual validation.

A Protractor test has two major parts, a spec file and a page object.

Let’s imagine a simple test, where your page loads, and you want to navigate to the user’s profile. With Protractor, you don’t have to worry about when the page actually finished loading, your test won’t run until every Angular process has finished (just imagine your test trying to find the user profile link when the page hasn’t finished loading/rendering).

So as a first step, you would write a CSS query to locate the user profile button (let’s say it’s a button with the class ‘user-profile’). That part should go into the page object file (the file’s name should conventionally end with .po.ts) as it’s a DOM query.

Calling this function retrieves the profile button from the DOM, clicks on it, and waits until the onClick event is completed. Imagine that the click navigates to another page, where the URL will be in the format of /profile/{userid}. To test if this click event actually navigates the page, you could write the following test case in a spec file (the file’s name should end with .spec.ts).

Notice, that even Jasmine’s expect function can resolve promises while using Protractor.

Apart from creating page objects, it’s also a good habit to separate tests into different test suites. This is done by putting the files into separate folders (e.g. pageObjects/home/*.po.ts and specs/home/*.spec.ts). This allows us to run only some specific test suites and separate our test cases both logically and physically.

The async/await style

In cases when you have a non-angular page (or some part of your application, like a non-angular authentication page at startup) or you have constant polling to a backend (which means Angular will never actually finish its actions), you can’t take advantage of Protractor’s automatic await functionality. The good thing is, it’s not really hard to emulate manually.

First, the configuration file has to be modified, by adding the SELENIUM_PROMISE_MANAGER:false property, which will turn off the control flow.

Note that this functionality is being deprecated in the webdriver-manager, which could result in making the async/await style the only way to write Protractor tests in the future.

The same test could be rewritten to async/await style like this.

You would expect the test to run properly by simply adding the necessary async and await keywords, however, without Protractor’s automating waiting feature, calling the click function on an element, doesn’t do anything apart from firing the onClick event. Even if you’d await that call, it won’t be able to wait for the actual emitted event to complete. That part must be written manually in this case.

Debugging Protactor tests

With source mapping enabled, it’s relatively easy to debug Protractor tests. Using Visual Studio Code, you’ll have to create (or extend if you have it already) the launch.json file.

This configuration allows you to put breakpoints into your tests and due to the source mapping, it finds the appropriate TypeScript code, even when running in a browser.

To launch a debug session, open the debug screen in VS Code, select the recently added profile and click on the start button. It fires up the e2e tests according to your configuration and you can start debugging by adding some breakpoints to your code.

Now that you know the basics of Protractor and end-to-end testing, it’s time to use this knowledge, by opening up your favorite Angular code and creating some more advanced test cases. Have fun!

--

--