Testing Auth0 login on AngularJS with Protractor, Part 1

Getting Auth0 to work with AngularJS is straight forward. It's a matter of adding the auth0-angular dependency in Bower (bower install --save auth0-angular) and including the JavaScript file in your HTML index. There are a few differences between the standard AngularJS routing and angular-ui-router, so I might come with a follow-up post to show you how I got it working.

With the Auth0 service ready, I created an Authentication module where I injected it as a dependency. On the same module I defined a Login Controller:

var OlsAuth = angular.module( 'fadeitbp.olsAuth', ['auth0']);

OlsAuth.controller('LoginCtrl', ['$scope', 'auth', '$location', '$state', function($scope, auth, $location, $state){  
    $scope.auth = auth;

    $scope.login = function() {
      auth.signin({
        scope: 'openid profile'
      });
    };

    $scope.logout = function() {
      auth.signout();
    };
}]);

On the controller, 2 scope methods are defined: one for login and one for logout. (to configure and read more about these you can login to your Auth0 admin account on auth0.com, then go to Apps/APIs from the left menu and then click on Quickstart to the right side of your app)

On a template of your choice, instantiate the controller and add two buttons for login and logout.

<section ng-controller="LoginCtrl">  
  <button ng-click="login()" id="loginButton" ng-if="!auth.profile">Login</button>

  <span ng-if="auth.profile">
      Hi {{auth.profile.nickname}} 
      <button ng-click="logout()" id="logoutButton">Logout</button>
  </span>
</section>  

Note that each of the buttons has its own id definied. This will come in handy when we'll write our tests.
That's it for the login code (thanks auth0, you are awesome!), now let's write an e2e test with Protractor!

Setting up Protractor is not difficult if you follow the Quick Start. For me the setup had to be done on a Vagrant VM and it was a bit more complicated (perhaps I'll write a post on that later on).
With Protractor ready, be sure to create a config file (I named mine protractor.conf.js). It should look similar to this:

exports.config = {  
  baseUrl: 'http://localhost:8000',
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['./**/*.e2e.js'],
  capabilities: {
    browserName: 'phantomjs',
    version: '',
    platform: 'ANY'
  },
};

Because I am running my tests on a headless server, my browser of choice is PhantomJS, but you can choose Chrome of Firefox if you wish.
If you want selenium to automatically start and stop each time you run your tests, then comment out

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

It's just faster for me to start the selenium server manually by running webdriver-manager start, but again it might be so because of my Virtual Machine.

With the config in place, it's finally time to write our test!
Start by creating an auth0.e2e.js file.
If you are familar with Jasmine, the syntax will be the same. We'll create a 'describe' and 'it' block:

describe("login module", function(){  
    it("should login succesfully using auth0", function(){
        //test logic
    });
});

First we need to navigate to the page where the template containing the login/logout button is displayed. To make sure we won't get a "Element is not visible" error while using PhantomJS I also set the window size. Notice that to navigate we can use browser.baseUrl, which is defined in the Protractor config file.

describe("login module", function(){  
    it("should login succesfully using auth0", 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');
    });
});

For my convenience I've created a user in Auth0 with email test@user.com and password 0000. From this point on it will be a step-by-step interface navigation.
First we will create a wait block and check if the login button is displayed. To make this work we need a [*Exists] variable that will just return true/false if the element exists. (Note that the ids from the template are used)

I prefer checking to see if an element is on the DOM in a wait block. The interface might take some time to load and we don't want our tests to fail when we have a slow internet connection / slow hardware / poor server specs / etc. After we're sure it's displayed, it's safe to click it.

describe("login module", function(){  
    it("should login succesfully using auth0", 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();
    });
});

Edit 14-10-2014

As @thepose points out, if you are using Auth0 in pop-up mode, you need to jump to the newly opened window like so:

    var popupHandle = handles[1];
    browser.switchTo().window(popupHandle);

See a more detailed example on github.


The next step is to wait for the Auth0 pop-up to load (with the email & password fields). Although the same wait block is used, the fields are not displayed at the same time as they are loaded in the DOM. I figured an arbitrary timeout of 2000ms will make sure the email and password fields are visible.

describe("login module", function(){  
    it("should login succesfully using auth0", 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);
    });
});

Now it's time to 'type in' our account details. We can do that by using the sendKeys() method. After we typed in our credentials the access button can be clicked.

describe("login module", function(){  
    it("should login succesfully using auth0", 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();
    });
});

And that's it! To see if AngularJS did in fact get a response from Auth0 we can wait for the logout button to be displayed. If we can find it on the DOM than we are surely logged in.

describe("login module", function(){  
    it("should login succesfully using auth0", 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);
    });
});

To see if the test works type in the following:
(skip the first command if you removed the seleniumAddress from your Protractor conf)

webdriver-manager start  
protractor protractor.conf.js  

Final words

You might have noticed that I haven't actually created any assertion. That's because my intention with this test is to transform it into a common method that I can use in any Protractor test with the help of the Page Object pattern. Plus, each wait block would break the test if the watched element won't be in the DOM after the timeout has passed.

I'll show you how to refactor this test and use Page Objects to better organize Protractor tests in my next post!