Navigation
CATEGORY: Protractor

Test an AngularJS Directive with Protractor

When you write an awesome AngularJS directive, you want to share it with the world. So you put it in its own repository on Github and now everyone else can use it. And because others are using your directive, you want to make sure it’s well tested. Fortunately that’s easy to do with Karma, but what if Karma is not enough to run all the test scenarios? What if you need to use Protractor to test some UI interactions with your directive?

It’s not difficult to use Protractor for your UI tests on a full fledged application, but now that you have separated out and isolated a single directive in its own repository, you no longer have a full fledged application to test against. You only have a single directive. You don’t even have a simple html page to display the directive in.

I ran into this same issue myself a few weeks ago, and after a lot of trial and error I was finally able to figure out how to run Protractor tests against a standalone directive that’s in its own repository. So I’ll share my findings to make it easier for others dealing with the same situation.

Create the smallest AngularJS website

Before you create your first Protractor test, you first need to get your directive to run within a website. So create the smallest website possible with the least amount of JavaScript code needed to run your directive. That typically means creating a single html file that references AngularJS and your directive along with including the html element that initializes the directive.

Here’s an example directive to show you what I mean. This directive just toggles a class on an element when it is clicked.

angular.module('click-toggle', []).directive(
'clickToggleClass', function() {
  return function(scope, elem, attrs) {
    var classToAppend = attrs['clickToggleClass'];
    var classAppended = false;
    elem.on('click', function() {
      classAppended = !classAppended;
      elem.toggleClass(classToAppend, classAppended);
    });
  });
});

And here’s the html file that initializes an AngularJS app and uses the directive.

<html>
<head>
  <script src='angular.js'></script>
  <script src='click-toggle-class.js'><script>
</head>
<body ng-app='click-toggle'>
  <div class='alert' click-toggle-class='alert-info'>Click Me to Toggle the class</div>
</body>
<html>

Serve up the website using ExpressJS

Now we need to serve up the html page through a website. It’s not enough to just open the html directly from the filesystem because Protractor won’t work.

There’s plenty of web frameworks to choose from to serve up an html page through a website, but most of them are overkill because we only have a single html file and a couple JavaScript files that need to be served. So let’s use a minimalist approach and have ExpressJS serve up these files.

ExpressJS is a micro web application framework for NodeJS, and it makes it super simple to serve up files on your computer inside a website. For example, let’s say you have the following directory structure, and you want to serve up the index.html file at http://localhost:3000/index.html

app.js
/public
  index.html
  click-toggle-class.js
  angular.js

The app.js file is the configuration file for the ExpressJS web server. And since we’re only serving up static files, we only need 4 lines of code in our app.js file

var express = require('express');
var app = express();
app.use('/', express.static(__dirname + '/public'));
app.listen(3000);

Now install ExpressJS and startup the server with the following command:

# install expressjs
npm install express

# run the expressjs server
node app.js

And you can now view the html page from your browser at http://localhost:3000/index.html

Run the Protractor tests

And finally we’ll create our protractor tests and run it against the directive.

// click-toggle-class_spec.js

describe('click-toggle-class', function() {
  it('adds the class on click when it did not previously have the class', function() {
    browser.get('');
    var el = element(By.css('div'));
    el.click()
    expect(el.getAttribute('class')).toBe('highlight');
  });
  it('removes the class on click when it previously had the class', function() {
    browser.get('');
    var el = element(By.css('div'));

    // first add the class by performing a click
    el.click()
    expect(el.getAttribute('class')).toBe('highlight');

    // now click again and confirm the class has been removed
    el.click()
    expect(el.getAttribute('class')).toBe('');
  });
});

Our Protractor config file will look like this:

// protractor.conf.js

exports.config = {
  specs: [ 'click-toggle-class_spec.js' ],
  capability: { browserName: 'chrome' },
  baseUrl: 'http://localhost:3000/index.html'
};

And we can now run our Protractor tests with the following command:

protractor protractor.conf.js

Conclusion

As you can see, it’s not completely straightforward to run Protractor tests against a standalone directive. But with the help of ExpressJS and creating a minimal website to run the directive, it is possible.

Run Protractor Tests in Parallel

My team has been using Protractor for our end to end tests for a couple months now, and the amount of time the test suite takes to finish has grown as we add more tests. So we started looking for ways to speed our tests up. We already have a Selenium grid running with a few browser instances connected to it. Ideally we would like to split the tests out to be ran in parallel among the browser instances in the grid.

The first configuration setting we tried was setting the count within the capabilities section of the configuration file.

exports.config = {
  capabilities: {
    browserName: 'chrome',
    count: 2
  },

  /* ... more protractor config values ... */
}

We ran Protractor and noticed both browser instances ran the tests. But after closer examination, we realized each browser instance was running all the tests. So the tests were being ran in parallel, but it wasn’t saving us any time because each test was ran twice.

I then dug into the source code for protractor in Github and found an example configuration file with comments about each setting. The comment for the count setting confirmed what we were seeing: ‘Number of times to run this set of capabilities (in parallel, unless limited by maxSessions)’. I continued reading the example configuration file and found what I was looking for right below the count setting.

The comment for shardTestFiles reads: ‘If this is set to be true, specs will be sharded by file (i.e. all files to be run by this set of capabilities will run in parallel). Default is false.’

So then I updated my config to the following and ran Protractor again.

exports.config = {
  capabilities: {
    browserName: 'chrome',
    shardTestFiles: true
  },

  /* ... more protractor config values ... */
}

However, this time only one browser instance ran the tests. They were not sharded between the 2 browser instances connected to the Selenium grid.

I went back to the example config file and then found the maxInstances setting, and read its comment that said this is only needed if shardTestFiles is true. So I added maxInstances to the config and ran Protractor again.

exports.config = {
  capabilities: {
    browserName: 'chrome',
    shardTestFiles: true,
    maxInstances: 2
  },

  /* ... more protractor config values ... */
}

And this time the test files were successfully sharded between the 2 browser instances.

Our tests were taking around 3 minutes to complete when they weren’t split up, and then they took around 2 minutes to complete after we split them among 2 browsers. So we weren’t able to realize a 50% reduction in the overall test time by splitting the tests between two browsers, but our overall test time was significantly improved.

PS

The shardTestFiles configuration was introduced in version 0.24. So if you’re running an earlier version then your test files will not be sharded.

There are no more results.