E2E testing AngularJS with Protractor

In the beginning, there was JSTestDriver. It was a dark time, with much wailing and gnashing of teeth.

Then came Testacular: The spectacular test runner. For a time, once everyone stopped sniggering like teenagers, it was good.
Unit tests ran quick as lightning on any browser that could call a web page.

Finally, to please the squeamish who were too embarrassed to speak of Testacular to colleagues and managers, the creator moved heaven and earth to rename it Karma. And it was, and still is, good.

But there was still unrest. While unit tests were as quick as the wind, E2E (end-to-end) tests were constrained from within the Javascript VM.
"Free me from this reverse proxy! Treat me as though I were a real user!" And thus Protractor was born.

ahem
Protractor is the official E2E testing framework for AngularJS applications, working as a wrapper around Web Driver (ie. Selenium 2.0) which is a well established and widely used platform for writing functional test for web applications. What makes it different from Karma is that Karma acts as a reverse proxy in front of your live AngularJS code, while Web Driver accesses the browser directly. So your tests become more authentic in regards to the user's experience.

One cool thing about Web Driver, which I didn't realise till recently, is that it's API is currently being drafted up as a W3 standard. We're also seeing a number of services appear for running your selenium tests using their VMs, which is useful for doing CI and performance testing without taking on the operational overhead yourself.

Let's go!

The App

I've created a simple application to write tests for. So first we'll clone the application from github, install the local NodeJS modules, and then install the required bower components:

git clone https://github.com/rolaveric/protractorDemo  
cd protractorDemo  
npm install  
node node_modules/bower/bin/bower install  

Now you should have a copy of the application with node modules 'bower' and 'protractor' installed, and AngularJS installed as a bower component.

The application is dead simple. It has a button with the label "Click to reverse". When you click it, it (you guessed it) reverses the label. So our tests should look something like this:
Load App
Click button
Assert that label is now reversed
Click button again
Assert that label is now back to normal

Installing Selenium

Protractor comes with a utility program for installing and managing a selenium server locally: webdriver-manager
Calling it with "update" will download a copy of the selenium standalone server to run.

node node_modules/protractor/bin/webdriver-manager update

Setting up for tests

First thing we need is a configuration file for protractor. It tells protractor everything it needs to know to run your tests: Where to find or how to start Selenium, where to find the web application, and where to find the tests.

Since we're using webdriver-manager to run selenium server, we'll tell it the default address to find it: http://localhost:4444/wd/hub
Optionally you could give it the location of the selenium server JAR file to start itself, or a set of credentials to use SauceLabs.

The tests we'll place in "test/e2e", and npm start spins up a local web server at "http://localhost:8000/". So the basic configuration file stored in "config/protractor.conf.js" looks like this:

exports.config = {  
 seleniumAddress: 'http://localhost:4444/wd/hub',
 specs: ['../test/e2e/*.js'],
 baseUrl: 'http://localhost:8000/'
};

There's actually a lot more you can do with the configuration file, but this is all we need to get going. Check out the reference config in protractor's github for more options. You can do things like pass parameters to selenium, your testing framework, and even to your tests (eg. login details).

Writing tests

In "test/e2e/click.js" is a simple test for the "Click to reverse" behaviour:

describe('click to reverse', function () {  
  var btn;

  it('Reverses the button label on click', function () {
    // load the page
    browser.get('http://localhost:63342/protractorDemo/index.html');

    // Find the button
    btn = element(by.binding('click.btnText'));

    // Click it
    btn.click();

    // Test the label
    expect(btn.getText()).toEqual('esrever ot kcilC');
  });

  it('Changes the label back on the next click', function () {
    // Clicks it again
    btn.click();

    // Test the label
    expect(btn.getText()).toEqual('Click to reverse');
  });
});

The process behind writing E2E tests is pretty simple: Perform an action, get some data, then test that data. Generally each action or query also involves finding a particular element on the page, either by CSS selector, ng-model name, or template binding.

First it opens the "index.html" file (which it finds relative to the baseUrl in the configuration file), finds the button by it's binding, clicks the button, then gets the button's text value and tests it. Then we click the button again, get it's text value, and test that it's changed back to normal.

vRunning tests

Now for the payoff - the running of the tests.

First we'll start up the web and webdriver servers:

npm start  
node node_modules/protractor/bin/webdriver-manager start  

Then we tell protractor to run the tests according to our configuration file:

node node_modules/protractor/bin/protractor config/protractor.conf.js  

If everything's gone well, you should soon be rewarded with the following result:

Finished in 2.061 seconds  
2 tests, 2 assertions, 0 failures  

And there you have it. Webdriver tests for your AngularJS application with minimum pain. If you already have angular-scenario based tests, converting them to Protractor should be a trivial "search & replace" exercise with the right regular expressions.

Cheers,
Jason Stone

(First posted 16 May 2014 at legacytotheedge.blogspot.com.au)

Jason Stone

Jason is an accidental front end developer with delusions of grandeur.

Sydney, Australia