Protractor – Automated Testing with Angular

Critical errors that become evident only in live operation constitute a major financial risk, and they also mean negative publicity both for the product and the companies involved. This is why testing is a fundamental, integral part of modern software development. High test coverage and prompt feedback of the test results allow for the quality and maturity of the product to be appropriately documented and confirmed.

Using test automation tools constitutes a solution that enables quick execution of such tests and meets the requirements of modern development projects. These tools work according to the principle of tool-based collection of information via the graphic interface of the product to be tested, which enables the automated execution of scripted interactions, and, as a result, the assessment of the respective application.

Test automation tools ensure quick and continuous feedback regarding the quality of the software to be tested. But there are some points that have to be observed when using them. There are various tools available on the market which use different approaches as to how they are integrated into the development and testing process, or which technologies they support. The efficient use of a test automation solution depends primarily on the engine used to control the graphic interface. This engine has to optimally support the technology to be tested. Development projects using “new” technologies such as Angular2 in particular face the problem that available and familiar tools are not always the same state of the art as the technology on which they are used.

CLINTR project and testing with Protractor

We use Angular2 as the development framework for our current software development project, Clintr, and we wanted a high frequency of automated test cases from the start. Clintr is a web application that alerts service providers to prospective customers in their contact network. For this purpose, it uses and analyzes data of the provided XING API to derive a demand for services in companies according to defined criteria in a fully automated manner. If a demand for services in a company has been identified, Clintr searches the service provider’s network of contacts (e.g. XING or CRM systems) for contact paths to the prospective customer. Spring Boot-based micro-services with Kubernetes are used as container cluster manager in the back-end, while Angular (>2) is used in the front-end. In order to be able to release new versions of the application at a high frequency, a continuous delivery pipeline into the Google cloud was established, both for the test and the production environment.

Because we used Angular2, we chose the automation test tool Protractor. Protractor is based on Selenium and the WebDriver framework. As usual, the interface tests are done in the browser, simulating the behavior of a user using the application. Since Protractor was written directly for Angular, it can access all the Angular elements without limitation. Furthermore, additional functions for waiting for components such as “sleeps” or “waits” are not necessary because Protractor recognizes the state the components are in and whether they are available for the intended interaction.

How to

For the execution, you need AngularCLI and NodeJS. Then the interface tests (end-to-end or e2e) can be created in the project. To prepare for the local test run, you use the console to switch to the project directory and enter “ng serve”. After entering “ng e2e”, the test cases are then run on the localhost.

The end-to-end tests consist of type script files with the extension .e2e-spec.ts, .po.ts, or just .ts. The test cases are described in the .e2e-spec.ts files. Only tests contained in these files are executed. The following example shows the header of a .e2e-spec.ts file:

    import { browser, by, ElementFinder } from 'protractor';
    import { ResultPage } from './result-list.po';
    import { CommonTabActions } from './common-tab-actions';
    import { SearchPage } from './search.po';
    import { AppPage } from './app.po';
    import { CardPageObject } from './card.po';
    import * as webdriver from 'selenium-webdriver';
    import ModulePromise = webdriver.promise;
    import Promise = webdriver.promise.Promise;
     
    describe('Result list', function () {
     
     let app: AppPage;
     let result: ResultPage;
     let common: CommonTabActions;
     let search: SearchPage;
     
     beforeEach(() => {
     app = new AppPage();
     result = new ResultPage();
     common = new CommonTabActions();
     search = new SearchPage();
     result.navigateTo();
     });

Like the other file types, it starts with the imports. Then the test cases start with describe. The string in brackets specifies which area is to be tested. Below that, the individual .po.ts files required for the subsequent tests are created and instantiated. The beforeEach function allows for preconditions for the test to be defined. For the purpose of reusability, the tests can also be exported to modules (see the following code example):

    it('should display the correct background-image when accessing the page', require('./background'));
    it('should send me to the impressum page', require('./impressum'));
    it('should send me to the privacy-policy page', require('./privacy-policy'));
     
    it('should open the search page after clicking clintr logo', require('./logo'));

The following code lists common e2e tests. First, it specifies what is expected, and then the test is executed. You should keep in mind that the e2e tests in the .e2e-spec.ts only calls the methods of the .po.ts and then waits for the result to be returned. The executing methods belong in the .po.ts.

    it('should still show the elements of the searchbar', () => {
     expect(result.isSearchFieldDisplayed()).toBe(true);
     expect(result.isSearchButtonDisplayed()).toBe(true);
    });
     
    it('should show the correct Search Term', () => {
     expect(result.getSearchTerm()).toBe(result.searchTerm);
    });

The following code example shows the .po.ts relating to the .e2e-spec.ts above. Each .e2e-spec.ts does not necessarily have its own .po.ts or vice versa. A .po.ts can, for example, contain tab-related actions such as switch or close tabs. As long as a .e2e-spec.ts only uses methods from other .po.ts, it does not necessarily need its own .po.ts. As mentioned above, the .po.ts starts with the imports, and then the class (ResultPage in the example) is created.

When called up, the navigateTo method causes the test to navigate to the specified page. Since the test is not supposed to do this directly in the present case, it navigates to the Search page first. There, a search term is entered and the search is started. Thus, the test arrives at the result_list page where the tests are subsequently run.

    import { element, by, ElementFinder, browser } from 'protractor';
    import { SearchPage } from './search.po';
    import * as webdriver from 'selenium-webdriver';
    import { CardPageObject } from './card.po';
    import ModulePromise = webdriver.promise;
    import Promise = webdriver.promise.Promise;
     
    export class ResultPage {
     
     public searchTerm: string = 'test';
     
     search: SearchPage;
     
     navigateTo(): Promise<void> {
     this.search = new SearchPage();
     return this.search.navigateTo()
     .then(() => this.search.setTextInSearchField(this.searchTerm))
     .then(() => this.search.clickSearchButton());
     }

Each of the following three methods queries an element of the page. The first two tests have a Union Type return value. This means they can either return a boolean or a Promise<boolean>, i.e. either a Boolean or a promise of a Boolean. When using Promise as the return value, it should always be followed by a then; otherwise, asynchronous errors may occur.

    isSearchButtonDisplayed(): Promise<boolean> | boolean {
     return element(by.name('searchInputField')).isDisplayed();
    }
     
    isSearchFieldDisplayed(): Promise<boolean> | boolean {
     return element(by.name('searchButton')).isDisplayed();
    }
     
    getSearchTerm(): Promise<string> {
     return element(by.name('searchInputField')).getAttribute('value');
    }

Example

An implementation example of a test case in ClintR is the test of the link to the legal notice. First, it is supposed to click on the link. Then, the test is supposed to switch to the newly opened tab and confirm that the URL contains /legal-notice. Lastly, it is supposed to close the tab. This test was initially created only for the home page.

    it('should send me to the impressum page',() => {
     impressum.clickImpressumLink();
     common.switchToAnotherTab(1);
     expect(browser.getCurrentUrl()).toContain('/legal-notice');
     common.closeSelectedTab(1);
    })

As, according to the acceptance criteria, the legal notice has to be accessible from every subpage, the test was later adopted into all the other specs. To keep the code clear, it was decided to export this test into a module (impressum.ts).

    import { browser } from 'protractor';
    import { AppPage } from './app.po';
    import { CommonTabActions } from './common-tab-actions';
     
    module.exports = () => {
     let common: CommonTabActions = new CommonTabActions();
     new AppPage().clickImpressumLink().then(() => {
     common.switchToAnotherTab(1);
     expect(browser.getCurrentUrl()).toContain('/legal-notice');
     common.closeSelectedTab(1);
     });
    };

It is used in the e2e-spec.ts this way:

    it('should send me to the impressum page', require('./impressum'));

Particularities, notes & problems

Certain given functions can be written in each e2e-spec.ts, e.g. beforeEach, beforeAll or afterEach and afterAll. As the names suggest, the code contained in one of these functions is executed before or after each test or all the tests. In our example, each test should include its own page view. Accordingly, the navigateTo method can, for example, be written in the beforeEach function. afterEach can, for example, be used to close tabs that were opened during the tests.

Each test starts with the word it. If you add an x before this word, i.e. xit, this test will be skipped in the test run. But, contrary to a commented-out test, notice will be given that one or more tests have been skipped in the test run. If you write a test case with f, i.e. fit, only those tests starting with fit will be taken into account in the test run. This is expedient when you have a large number of test cases and only want to run some of them.

When working with Promise, which you get from some methods, you should keep in mind that incorrect handling can cause asynchronous errors. Many events, such as pressing a button or querying whether an element is displayed, return such a Promise. Even opening a page returns a Promise<void>. To avoid errors, each Promise that entails further actions such as pressing a button or outputting a resulting value should explicitly be followed by a then. For example:

    pressButton().then( () => {
     giveMeTheCreatedValue();
    });
    //If this value is again a Promise, which should trigger something, then the whole thing would look like this:
    pressButton().then( () => {
     giveMeTheCreatedValue().then( () => {
     doSomethingElse();
     });
    });
    // or slightly shorter
    pressButton()
     .then(giveMeTheCreatedValue)
     .then(doSomethingElse);

Further information on Promises is available here.

Conclusion

Protractor is highly suitable for the automation of interface tests in a software development project using Angular2. The documentation on the project side is very detailed and comprehensive. Using Selenium allows the tests to be easily integrated into the build process.

How to build your Angular project on Travis CI

Recently, I created an Angular App with current Angular CLI 1.0.0-rc.0 using Yarn instead of NPM. When I pushed some code to the GitHub repo and opened a pull request for my teammates to review, an information box popped up on GitHub suggesting to connect to a GitHub Integrations tool. Then with every push or pull request the app is built and tested by the integration tool, allowing for a quick feedback about whether it is safe to integrate the code. Let’s give it a try, I thought and decided to go with Travis CI. An hour later I had everything up and running. Here is what you need to do:

First of all, authorize Travis CI to access your GitHub repos and hook into push events. Thanks to GitHub Integrations this is done by the push of two buttons. Second, for Travis CI to build your app it looks for a .travis.yml configuration file in your application’s root folder. This is what it looks like:

 dist: trusty
 sudo: required
 language: node_js
 node_js:
 – “7”
 os:
 – linux
 env:
 global:
 – DBUS_SESSION_BUS_ADDRESS=/dev/null
 – DISPLAY=:99.0
 – CHROME_BIN=chromium-browser
 before_script:
 – sh -e /etc/init.d/xvfb start
 install:
 – yarn
 script:
 – yarn run lint
 – yarn run test — –watch=false
 – yarn run pree2e
 – yarn run e2e
 cache: yarn

travis.yml hosted with ❤ by GitHub

# .travis.yml
dist: trusty
sudo: required
language: node_js
node_js:
  - "7"
os:
  - linux
env:
  global:
    - DBUS_SESSION_BUS_ADDRESS=/dev/null
    - DISPLAY=:99.0
    - CHROME_BIN=chromium-browser
before_script:
  - sh -e /etc/init.d/xvfb start
install:
  - yarn
script:
  - yarn run lint
  - yarn run test -- --watch=false
  - yarn run pree2e
  - yarn run e2e
cache: yarn

Travis CI starts up a virtualization environment that will execute your build scripts. Stick with the defaults and select an Ubuntu Trusty on Linux with sudo permissions. Configure the environment to use Node.js and set the required version to at least version 6. I required version 7 because I remembered we had problems with Node.js 6 in a different Angular project. But for a newly created Angular CLI project version 6 should be fine as well.

Now, before running the build scripts a couple of environment variables have to be set. This is to allow running tests that require a graphical user interface, in our case for Karma and Protractor to run their tests in Google Chrome. The xvfb command will launch an X-Server with a virtual display.

As I said earlier, this Travis CI configuration is for Angular CLI projects using the Yarn package manager in favor of NPM. As you might know, Yarn enables us to lock down the project dependencies in a lock file named yarn.lock which mitigates the risk of indeterminate dependency versions on different systems. Travis CI comes with Yarn support and will install Yarn automatically if the project contains both a package.json and a yarn.lock file. To fetch all dependencies add a yarn install command to the Travis CI configuration. You can even omit install as this is the default command in Yarn. To speed up builds, the Yarn cache can be saved across builds. See the cache: yarn property at the bottom of the config file.

Then add build scripts for linting, Karma tests and Protractor e2e tests. Make sure to disable the watch mode in Karma by adding -- --watch=false, otherwise the tests won’t finish and the build will be stuck forever.

The Protractor e2e tests are likely to fail because Protractor tries to update the webdriver before execution. While this works with NPM it is different with Yarn as it can’t load the webdriver from a deep link. This is a known issue in Angular CLI that is supposed to be fixed soon. For the time being, disable the webdriver update adding --no-webdriver-update to the e2e script. Then add a preceding script to update the webdriver before test execution. As this is not a Travis CI related problem, I put these modifications in package.json instead of .travis.yml.

That’s all, you’re done! Push your code to GitHub and watch Travis CI build your app. As a bonus you can display the build status as a badge on your GitHub page or README file. See this basic project on GitHub and the CI build for reference. Feel free to contact me for any questions.