End to end tests with Angular

When releasing a new functionality, using end to end tests is a great way how to make sure a previous features still works from user's perspective. Protractor is a tool written especially for Angular apps and make writing end to end tests super easy using Jasmine syntax. Our team at FIIT is using it in our project and it works great!

Quick setup

Tools we are going to use:

First of all, we need to install Protractor. Let's follow instructions within tutorial on the Protractor home page:

npm install -g protractor  

Protractor will install Selenium and it's NodeJS driver webdriver for us automatically, so we can use it with command webdriver-manager. Run this command to update dependencies:

webdriver-manager update  

To run Selenium server, type

webdriver-manager start  

Okey, so we have a Selenium server working. Now we should run a protractor to test our app against the server, but first we need to create a configuration file. I prefere to create a separate directory for e2e tests in my test folder, to not mess up with unit tests. Config file is then placed in /test/e2e/conf.js

// test/e2e/conf.js
exports.config = {  
  seleniumAddress: 'http://localhost:4444/wd/hub', // selenium server
  baseUrl: 'http://localhost:9000', // our app
  specs: ['spec/*.js'],
  capabilities : {
    browserName : 'chrome',
    'chromeOptions': {
      args: ['--disable-web-security']
    }
  }
};

I've seen a great hint in the video by Arun Mahendrakar, to disable angular animations to make testing faster - Protractor don't have to wait until animations finish. The configuration file will then look like this:

// test/e2e/conf.js
exports.config = {  
  seleniumAddress: 'http://localhost:4444/wd/hub',
  baseUrl: 'http://localhost:9000',
  specs: ['spec/*.js'],
  capabilities : {
    browserName : 'chrome',
    'chromeOptions': {
      args: ['--disable-web-security']
    }
  },
  onPrepare: function () {
    // disable animations when testing to speed things up
    var disableNgAnimate = function () {
      angular.module('disableNgAnimate', []).run(function ($animate) {
        $animate.enabled(false);
      });
    };
    browser.addMockModule('disableNgAnimate', disableNgAnimate);
  }
};

Of course, now we need to write some tests. You may noticed that we specified 'spec/*.js' as a pattern for Protractor, where it should look for spec files.

When using protractor, you can use Jasmine syntax to write tests. Check out the official documentation for more info. As a sample for this article, I provide our login page tests (simplified):

// test/e2e/spec/login.js
describe('login page', function () {  
  beforeEach(function () {
    browser.get('#/login');
  });

  var formUsername = element(by.model('loginData.username'));
  var formPassword = element(by.model('loginData.password'));
  var button = element(by.css('.btn-primary.btn-block'));

  it('should show a login page', function () {
    var loginContainer = element(by.css('.login-container'));
    expect(loginContainer.isPresent());
  });

  it('should contain a form with cleared username and password fields', function () {
    expect(element(by.model('loginData.username')).getAttribute('value')).toBe('');
    expect(element(by.model('loginData.password')).getAttribute('value')).toBe('');
  });

  it('should disable the button when the username and password fields are empty', function () {
    formUsername.clear();
    formPassword.clear();

    expect(button.getAttribute('disabled')).toBe('true');
  });

  it('should show an error when incorrect username/password is given', function () {
    formUsername.clear();
    formPassword.clear();

    formUsername.sendKeys('test@test');
    formPassword.sendKeys('mypassword');

    button.click();

    expect(element(by.css('.alert.alert-danger')).isPresent());
  });

  it('should login the user when right credentials are used', function () {
    formUsername.clear();
    formPassword.clear();

    formUsername.sendKeys('test@test.com');
    formPassword.sendKeys('mypassword');

    button.click();

    expect(browser.getCurrentUrl()).toMatch('/#/app/projects');
  });

});

At this point, we could run our test (make sure the selenium server is running) with

protractor /tests/e2e/conf.js  

You should see a chrome window that will pop out with your application being tested. If everything is green, we are good to go! You may also get some error if you misspelled something - don't hesistate to post it in comments, I'll be glad to help!

Grunt task

Now, we could create a grunt task to make it more.. fluffy! We're using this grunt task. Installation is simple and described in the link above. Simply install the task with

npm install grunt-protractor-runner --save-dev  

then add grunt.loadNpmTasks('grunt-protractor-runner'); into your GruntFile and pass a task configuration object to grunt.initConfig() function. We are using:

protractor: {  
    options: {
        keepAlive: true,
        noColor: false,
    },
    all: {
        options: {
          configFile: "test/e2e/conf.js"
        }
    }
},

Now, you should be able to run e2e test with

grunt protractor  

However, we could add e2e tests to our test task with

grunt.registerTask('test', [  
    'clean:server',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma',
    // add this to test task
    'protractor'
]);

So you can run your test with your previous unit tests with

grunt test  

Good luck!