Testing Auth0 login on AngularJS with Protractor, Part 2: Refactoring

If you haven't seen part 1 of Testing Auth0 login on AngularJS with Protractor, this post would make a lot more sense if you do!

With the setup from the previous post, we would see our test fail the second time. That would be correct because when we attempt to run our test again, authentication data would be in place in the client's Local Storage. In other words, we need to logout first!
There are many ways you can do this, but I prefer executing a short command that clears the Local Storage:

    browser.driver.get(browser.baseUrl + '/#/home');
    browser.executeScript('window.localStorage.clear();');

Don't forget to navigate to a valid path before clearing the local storage, otherwise Protractor would give you the following error: "Failed to read the 'localStorage' property from 'Window'"

Organizing our helper methods

We'll be using logins and logouts often throughout our tests, therefore it'll be nice to have them handy whenever we need them.

First add one more spec (I called it 'helpers.e2e.js') to the Protractor config:

exports.config = {  
  ...
  specs: ['./helpers.e2e.js', './**/*.e2e.js'],
  ...
};

This will make sure that our helper methods will be loaded before any other test files.
The contents of the helpers test would be structured like so:

var helpers = function helpers(){  
    this.login = function(){};

    this.logout = function (){};
};

module.exports = new helpers();  

In any of our test files, we can then just easily call these helpers:

var helpers = require('PATH_TO_HELPERS/helpers.e2e.js');

describe("Any profile page", function(){  
    ...
    helpers.logout();
    ...
});

Putting it all together

Now we need to move the code inside the helpers file.

var helpers = function helpers(){  
    this.login = function(){
        //set window size and navigate to the path where the template is loaded
        browser.driver.manage().window().setSize(1500, 1000);
        browser.driver.get(browser.baseUrl + '/#/home');

        //check if login button is present & visible
        var loginButtonExists = by.id('loginButton');            
        browser.driver.wait(function() {
            return browser.driver.isElementPresent(loginButtonExists); 
        }, 5000);
        var loginButton = element(by.id('loginButton'));
        loginButton.click();

        //check if email field exists to see if the pop-up has been succesfully loaded
        var emailFieldExists = by.id('a0-signin_easy_email');
        browser.driver.wait(function() {
            return browser.driver.isElementPresent(emailFieldExists); 
        }, 5000);
        //wait for pop-up fields to be displayed (they are on the page but might be hidden initially)
        browser.driver.sleep(2000);

        //type credentials and click the 'access' button to log in
        var emailField = element(by.id('a0-signin_easy_email'));
        emailField.sendKeys('test@user.com');
        var passWordField = element(by.id('a0-signin_easy_password'));
        passWordField.sendKeys('0000');
        var accessButton = element(by.css('.a0-notloggedin .a0-primary'));
        accessButton.click();

        //verify that the login was succesfull by checking if the logout button is displayed
        var logoutButtonExists = by.id('logoutButton');         
        browser.driver.wait(function() {
            return browser.driver.isElementPresent(logoutButtonExists); 
        }, 5000);
    };


    this.logout = function(){
        browser.driver.get(browser.baseUrl + '/#/home');
        browser.executeScript('window.localStorage.clear();');
    }
};

module.exports = new helpers();  

Going back to our original test file (auth0.e2e.js), it now looks very tidy & clean:

var helpers = require('./helpers.e2e.js');

describe("login module", function(){  
    it("should login succesfully using auth0", function(){
        helpers.login();
    });
});

If you wish, logout can be used in a beforeEach block so you'll be sure to start clean.

var helpers = require('./helpers.e2e.js');

describe("login module", function(){  
    beforeEach(function () {
        helpers.logout();
    });

    it("should login succesfully using auth0", function(){
        helpers.login();
    });
});

Benefits of the approach - testing out routes

Now that we did all this prep work, we have to put it to good use. To see how it would help, I'll continue to build on this example.
Consider we add 2 new private routes:

/#/profile

/#/change-password

Our home route remains /#/home and whenever a user is not logged in and tries to access a private page, we would like to redirect to it.

It's really easy to redirect with Auth0. If we were to create a profile module, we can set up a redirect in the states block by setting requiresLogin to true.

var Profile = angular.module( 'profile', ['ui.router']);

Profile.config(function config( $stateProvider ) {  
  $stateProvider.state( 'profile', {
    url: '/profile',
    views: {
      "main": {
        controller: 'ProfileCtrl',
        templateUrl: 'profile/profile.tpl.html'
      }
    },
    data:{ 
      pageTitle: 'Profile page',
      requiresLogin: true
    }
  });
});

Note that I am using ui-router in this example, with the default Angular routing system we'll have to place requiresLogin in the root of our route definition.

$routeProvider
    .when( '/profile', {
      controller: 'ProfileCtrl',
      templateUrl: 'profile/profile.tpl.html',
      pageTitle: 'Profile page',
      requiresLogin: true
    });

Now that the redirect is set up (we need to do the same for /change-password), let's go ahead and test it.
First of all, we should test that if we are not logged in, we'll be redirected to /#/home.

var helpers = require('./helpers.e2e.js');

describe("login module", function(){  
    describe("/profile", function(){
      beforeEach(function () {
          helpers.logout();
      });

      it("should redirect to /home if not logged in", function(){
          browser.driver.get(browser.baseUrl + '/#/profile');
          expect(browser.getLocationAbsUrl()).toMatch('/#/home');
      });
    });

    describe("/change-password", function(){
      beforeEach(function () {
          helpers.logout();
      });

      it("should redirect to /home if not logged in", function(){
          browser.driver.get(browser.baseUrl + '/#/change-password');
          expect(browser.getLocationAbsUrl()).toMatch('/#/home');
      });
    });
});

We can already see the benefits of organizing our tests this way, but let's go further and test a simple login scenario: when we are logged in, we shouldn't be redirected to /#/home):

var helpers = require('./helpers.e2e.js');

describe("login module", function(){  
    describe("/profile", function(){
      beforeEach(function () {
          helpers.logout();
      });

      it("should redirect to /home if not logged in", function(){
          browser.driver.get(browser.baseUrl + '/#/profile');
          expect(browser.getLocationAbsUrl()).toMatch('/#/home');
      });

      it("should stay on /profile if logged in", function(){
          helpers.login();
          browser.driver.get(browser.baseUrl + '/#/profile');
          expect(browser.getLocationAbsUrl()).toMatch('/#/profile');
      });
    });

    describe("/change-password", function(){
      beforeEach(function () {
          helpers.logout();
      });

      it("should redirect to /home if not logged in", function(){
          browser.driver.get(browser.baseUrl + '/#/change-password');
          expect(browser.getLocationAbsUrl()).toMatch('/#/home');
      });

      it("should stay on /change-password if logged in", function(){
          helpers.login();
          browser.driver.get(browser.baseUrl + '/#/change-password');
          expect(browser.getLocationAbsUrl()).toMatch('/#/change-password');
      });
    });
});

We'll end up with a fairly simple test that can prove to be very useful. Nice and clean, isn't it?

What to keep in mind

This test itself will not suffice to check our private routes, because the tests can still pass if Angular throws an exception. (I'll write up a post on how to check that and make the tests fail)

Using Grunt to automate your Protractor tests

It's very handy to configure a Grunt task that runs the Protractor tests. Luckily there's Grunt Protractor Runner, a neat little plugin to easily achieve it.

After loading the npm task, it's easy to configure:

grunt.loadNpmTasks('grunt-protractor-runner');

...
    protractor: {
        options: {
            configFile: "./protractor.conf.js",
            keepAlive: true, // If false, the grunt process stops when the test fails.
            noColor: false,
            args: {}
          },
          run: {}
    }
...

grunt.registerTask( 'ptor', ['protractor:run']);  

Now we can run grunt ptor anytime we want to test or configure this task in a watch block.

One remark I have to make here is that it's quite difficult to set it up and have the Selenium server start automatically. I eventually gave up and had to add the selenium address to the Protractor config:

...
  seleniumAddress: 'http://localhost:4444/wd/hub',
...

In this case remember that you have to either manually start Selenium by running webdriver-manager start or configure your server's init.d file to automatically start running the Selenium server. (I went for the later because it's more convenient)

Final words

I hope these two posts make it easy to start with AnguarJS, Auth0 and Protractor. If any of you will find it helpful, I could also create a Github repo with a sample app.

Have any suggestions? Please leave a comment and I will be happy to update the post.