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.


Navigation