hasMichaels / protractor_tutorial

Tutorial for Testing using Protractor

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Protractor Tutorial

Protractor is a E2E testing using Selenium. It will test using black box and design to simulate end user web page access perspective. Functionality of the page element and work flow of the application is the crucial part of the testing.

##Why Protractor Protractor is a match made in heaven for AngularJS. Besides having the selenium DOM targeting capability, it also has capability to select element based on the Angular binding attributes ( e.g. ng-model, ng-repeat, ng-bind, etc. )

##Installation You can install the protractor as global stand alone app using

$ npm -g install protractor

or locally

$ npm install protractor 

Protractor also require a webdriver to run it's testing, luckyly Protractor comes with the webdriver-manager to download the necessary browser driver file. Depending on your protractor installation you can either access your webdriver-manager inside your project node_modules/ to run this command:

$ ./node_modules/protractor/bin/webdriver-manager update

The script will download the files that needed to run Selenium. After download you can test if the webdriver properly downloaded and installed by typing:

$ ./node_modules/protractor/bin/webdriver-manager start

Once you have it running without error you already to do your testing.

##Configuration Protractor receive one input file as their config file to start testing. You can get the sample configuration file from inside protractor example folder, you can copy it as your base configuration.

$ cp ./node_modules/protractor/example/chromeOnlyConf.js protractor_conf.js

Here is the sample protractor config

exports.config = {
  // SauceLab Login, uncomment this area if you want to use Sauce Labs
  //sauceUser        : '<your_sauce_lab_user_name>',
  //sauceKey         : '<your_sauce_lab_generated_key>',

  // Use this for chrome
  //chromeOnly: true,
  //chromeDriver: './node_modules/protractor/selenium/chromedriver',

  // Use this for standalone selenium server
  seleniumAddress: 'http://0.0.0.0:4444/wd/hub',

  // Capabilities to be passed to the webdriver instance.
  capabilities   : {
    'browserName': 'chrome'
  },

  // Spec patterns are relative to the current working directly when
  // protractor is called.
  specs          : ['test/e2e/**/*_spec.js'],

  // Options to be passed to Jasmine-node.
  jasmineNodeOpts: {
    showColors            : true,
    isVerbose             : true,
    defaultTimeoutInterval: 30000
  }
};

For more available configuration options you can access this file ./node_modules/protractor/referenceConf.js

You can define the baseUrl attribute inside your config file and call it from your test code by using browser.baseUrl.

In your protractor_conf.js

// in your protractor_conf.js
exports.config = {
...
  baseUrl: 'http://localhost:9000',
...
}

and In your test code

...
browser.get(browser.baseUrl);
...

This will come handy if you want to use the same protractor_conf.js to test different environment. You can pass the override baseUrl in the command line.

./node_modules/protractor/bin/protractor protractor_conf --baseUrl <new env. url>

At the moment you start collecting a pile of spec in one folder, that the time you need to start thinking of separating your test specs into suite. By using suite you can easily test partial part of your whole test suites and focus just on certain part of the test one a time. Here is how to define suites in your config file.

...
  suites: [
    login    : 'test/e2e/login/**/*_spec.js',
    dashboard: 'test/e2e/dashboard/**/*_spec.js',
    myAccount: 'test/e2e/myAccount/**/*_spec.js',
    offers   : 'test/e2e/offers/**/*_spec.js',
    reports  : 'test/e2e/reports/**/*_spec.js',
    search   : 'test/e2e/search/**/*_spec.js',
    tools    : 'test/e2e/tools/**/*_spec.js'
  ]
...

After you setup your suites, you can test on individual suites using the command line:

./node_modules/protractor/bin/protractor protractor_conf --suite <suite_name>

It will just run login suite and ignoring other suite. The caveat of using suite is that you need to make sure that each suite is idempotent and not dependent on the previous suite to run before the current suite can be tested correctly.

##Running Your First Test In your terminal, you will need to run 3 things:

  • webdriver-manager Protractor comes with
  • your web server that you want to test ( in this tutorial I run it using grunt serve )
  • protractor

I am using tmux to manage my terminal instances like this:

terminal window

##Writing Your First Test

####protractor_conf.js Here is the protractor config file for this tutorial.

// An example configuration file.
exports.config = {
    // Use this for chrome 
    //chromeOnly: true,
    //chromeDriver: './node_modules/protractor/selenium/chromedriver',

    // Use this for standalone selenium server
    seleniumAddress: 'http://0.0.0.0:4444/wd/hub',
    
    // Capabilities to be passed to the webdriver instance.
    capabilities: {
        'browserName': 'chrome'
    },

    // Spec patterns are relative to the current working directly when
    // protractor is called.
    specs: ['test/e2e/**/*_spec.js'],
    // Options to be passed to Jasmine-node.
    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 30000
    }
};

###Basic Testing ####Testing For Correct Page Using Existing Element Let's test if the website show the correct home page. We want to test if the server is serving the right page, one way to test it is by checking for the home element ID in the page ( you can also check for page title ). Start with the standard jasmine test case

describe('E2E: main page', function(){
});

We are going to start by telling web driver to open the target website ( in this tutorial is http://127.0.0.1:9000 ). browser.get() will do that for you. Read more about browser.get here

browser.get('http://127.0.0.1:9000');

We wrap the line within beforeEach to prepare the page before we test it.

describe('E2E: main page', function(){
    beforeEach( function(){
        browser.get('http://127.0.0.1:9000');
    });
});

I added the #home element in my home page view for the purpose of this tutorial, we are going to check if this element exist on the page. To select the element, protractor has element() and by() function to help us. Now we can test for the element #home exist on the page by using browser.isElementPresent(). You can read more about browser.isElementPresent() here.

it('should load the home page', function(){
    var ele = by.id('home');
    expect(browser.isElementPresent(ele).toBe(true));
});

Run your protractor to test the script and see the Success message( or failure ).

Continue on, we can test other element, such as the input element. Here is the steps for the test:

  • We want to have and input button and a button - use by.input and sendKeys
  • When user type to the input button and click the button - use by.partialButtonText and sendKeys('\n')
  • It will remove the input box and replace it with a list of item - check using browser.isElementPresent()

In this example, we are selecting the button using by.partialButtonText(), you can read more about by.partialButtonText here.
We can use sendKeys to enter text to the selected element. Use \n to simulate the enter key. You can read more about sendKeys here.

it('the input box should go away on submit', function(){
    element(by.input('item.name')).sendKeys('Testing Input');
    element(by.partialButtonText('THE THING')).sendKeys('\n');
    expect(browser.isElementPresent(by.input('item.name'))).toBe(false);
});

You can now run the protractor again to see your passing / failing test.

If you've been following all the steps, here is the final code from this section of tutorial:

describe( 'E2E: main page', function(){
    beforeEach( function(){
        browser.get('http://127.0.0.1:9000');
    });

    it('it should load the home page', function(){
        var ele = by.id('home');
        expect(browser.isElementPresent(ele)).toBe(true);
    });

    it('the input box should go away on submit', function(){
        element(by.input('item.name')).sendKeys('Testing Input');
        element(by.partialButtonText('THE THING')).sendKeys('\n');
        expect(browser.isElementPresent(by.input('item.name'))).toBe(false);
    });
});

####Testing For List Element Using AngularJS Binding Protractor was created to help us do E2E testing on AngularJS application, it's equipped with additional selector to select element based on AngularJS specific binding / model. In this section we are going to write a test that utilized that capability.

In this example we are going to select the element that was created using ng-repeat. Protractor has a by.repeater selector that we can use.
When selecting multiple element, you need to use element.all to get an array of elements.

var elems = element.all(by.repeater('item in mockItems'));

Once you get the elements array you can do count() to check for array length.

Here is the code for this section.

describe( 'listing page', function(){
    beforeEach( function(){
        browser.get('http://127.0.0.1:9000');
        element(by.input('item.name')).sendKeys('Testing Input');
        element(by.partialButtonText('THE THING')).sendKeys('\n');
    });

    it('should have 4 items', function(){
        var elems = element.all(by.repeater('item in mockItems'));
        expect(elems.count()).toBe(4);
    });
});

####Testing For Page Navigation Using URL and Element Attribute Another way to check for a correct page being loaded into the browser is by checking their URL, you can achive this by using browser.getCurrentUrl() and match them using regular expression

expect(browser.getCurrentUrl()).toMatch(/\about/);

Checking for a class or atribute of an element also anoter way to check if a button clicked / hover or changed. You can interact with Element by clicking on it using .click() command.

...
var link = element(by.css('.header ul li:nth-child(2)'));
link.click();
...
expect(link.getAttribute('class')).toMatch(/active/);
...

This is the last describe section to test the url and element attributes.

describe( 'page navigation', function(){
    var link;
    beforeEach(function(){
        browser.get('http://127.0.0.1:9000');
        link = element(by.css('.header ul li:nth-child(2)'));
        link.click();
    });

    it('should navigate to the /about page when clicking', function(){
        expect(browser.getCurrentUrl()).toMatch(/\/about/);
    });

    it('should add the active class when at /about', function(){
        expect(link.getAttribute('class')).toMatch(/active/);
    });
});

###Steping Up A Notch As you can see from the code above, you can pile a bunch of page testing in one testing config file. It will grow nasty pretty quick and become unreadable. Another way to manage the the test case is by separating the selector and test into separate page class.

function IndexPage() {
  this.button = element(by.id('button1'));
  this.message = element(by.binding('messageText'));

  this.get = function () {
	browser.get('/#');
  };

  this.clickButton = function () {
	this.button.click();
  };

  this.getTitle = function () {
	return browser.getTitle();
  };

  this.getMessageText = function () {
	return this.message.getText();
  }
}

module.exports = IndexPage;

And in the main spec file you import the class in.

var IndexPage = require('./IndexPage');

describe("hello-protractor", function () {

  //Create the object from the imported class
  var page = new IndexPage();

  beforeEach(function() {
      // Call the testing you define in the class 
      page.get();
  });

  describe("index", function () {
    it("should display the correct title", function () {
    	// Another test from the class
      	expect(page.getTitle()).toBe('hello protractor');
    });

    it("should display the message when button clicked", function () {
  		// Testing the click button by calling the function in the class
      	page.clickButton();

      	expect(page.getMessageText()).toBe('button 1 clicked');
    });
  });
});

Using this method your testing code is more readable and you can extends similar page class to keep the same behaviour checks and not copy pasting a bunch of repeated code in multiple page files.

###Testing Protractor Interactively Protractor comes with this script, elementexplorer.js in the protractor bin folder, this code will run protractor testing in interactive mode:

node ./node_module/protractor/bin/elementexplorer.js <urL>

This one comes handy when you setup the test suite for the first time and studying the DOM selector.

###Stepping Through Protractor Test To step through your protractor testing you can use the debug mode in protractor

protractor debug <your-e2e-config-file.js>

Protractor has its own debugger command to pause the testing process. Use browser.debugger() to pause during the protractor test.

###Using Mock in Protractor //TODO: Write about using $httpBackend Proxy to pass the mock data to protractor test https://github.com/kbaltrinic/http-backend-proxy

##Protractor Cheat Sheet Here is some most common api that you can use to test your website E2E:

###Global Variables

###Selector ####Generic Selector

####AngularJS Selector

####Function Prototype

##More Settings ###Testing Against Multiple Browser If you are feeling confident with your code and want to test it against multiple browser you can set it up using:

multiCapabilities: [{
 	'browserName': 'firefox'
}, {
 	'browserName': 'chrome'
}]

You can also pass chrome-specific option by passing it as chromeOptions property:

capabilities: {
  	'browserName': 'chrome',
  	'chromeOptions': {
		'args': ['show-fps-counter=true']
  	}
},

###Testing Non-AngularJS Website Using Protractor Even if the website is not an AngularJS website, you can still test the web page using WebDriver instance in the browser object.

var driver = browser.driver;
// driver.findElement( ... );

###Taking Web Page Snapshots If you want to take a selfie of your website you can do it using browser.takeScreenshot().

Here is the sample usage:

// at the top of the test spec:
var fs = require('fs');

// ... other code

// abstract writing screen shot to a file
function writeScreenShot(data, filename) {
	var stream = fs.createWriteStream(filename);

	stream.write(new Buffer(data, 'base64'));
	stream.end();
}

// ...

// within a test:
browser.takeScreenshot().then(function (png) {
	writeScreenShot(png, 'exception.png');
});

##Automate With Grunt To make your life easier, you can get grunt-protractor-runner and you can add the protractor section in your Gruntfile.js

protractor: {
    options: {
          configFile: "protractor-config.js”, //your protractor config file
          keepAlive: true, // If false, the grunt process stops when the test fails.
          noColor: false, // If true, protractor will not use colors in its output.
          args: {
              // Arguments passed to the command
          }
      },
    chrome: {
        options: {
              args: {
                  browser: "chrome"
              }
          }
    },
    safari: {
        options: {
            args: {
                browser: "safari"
            }
        }
    },
    firefox: {
        options: {
            args: {
                browser: "firefox"
            }
        }
    }
}

You can then run the test individually or better yet you can run it concurrently using grunt-parallel. Install the concurrent plug-in in your project root directory using:

$ npm install grunt-concurrent --save-dev

Register each browser test as separate test

grunt.registerTask('protractor-chrome', ['protractor:chrome']);
grunt.registerTask('protractor-safari', ['protractor:safari']);
grunt.registerTask('protractor-firefox', ['protractor:firefox']);

Call the registered tasks inside the concurrent settings:

concurrent:{
	protractor_test: ['protractor-chrome', 'protractor-safari', 'protractor-firefox']
}

Add a new task in your Gruntfile.js

grunt.registerTask('protractor-e2e', ['concurrent:protractor_test']);

Now you can call the protractor test easily using grunt protractor-e2e and all of 3 browser will run the test concurrently.

###But, there is more ... You can even add the task inside the watch list, so every time you change the test case / code, the testing will be triggered automatically, but use it with caution. Install the watch plug in, if you have not already, using

$ npm install grunt-contrib-watch --save-dev

and add the e2e settings inside the watchsection:

...
e2eTest: {
	files: ['e2e/{,*/}*.js',
            '<%= yeoman.app %>/scripts/{,*/}*.js',
            '<%= yeoman.app %>/{,*/}*.html',
                '.tmp/styles/{,*/}*.css',
                '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'],
        tasks: ['protractor-e2e']
      }
...

and BOOM

##Testing Using Sauce Labs

###What is Sauce Labs Sauce labs is and online service where you can rent test server ( by the minutes ) to do your testing. You can do JS unit testing and E2E unit testing by sending the request to their Sauce Lab server. Sauce Lab also provides you with the recording and the snap shot of your E2E testing.

###Protractor + Sauce Labs === Awesome Sauce Thanks to protractor, testing using Sauce Lab is just as easy as adding the Sauce Lab credential in you protractor config file protractor_conf.js. You can get your unique key from your Sauce Lab Account Home. Don't forget to comment out the selenium address to start using Sauce Labs selenium server.

sauce labs key

// Don't forget to comment out this property
// seleniumAddress: 'http://127.0.0.1:4444/wd/hub',

sauceUser        : '<your_sauce_lab_user_name>',
sauceKey         : '<your_sauce_lab_generated_key>',

After you change the config file you can call protractor again from command line ( you can also use grunt ). You can watch the on going test by loging in to SauceLabs and click on the running sessions, you can watch it as a a streaming video. After a session is done, you can play the replay of the session or visit the screen shot of the testing process.

With Sauce Labs you can name your session and specify browser / os type by adding "name", "os" and "browserName" parameter in the testing capabilities object. For more accepted keys information you can read it here.

...
multiCapabilities: [
    {
        "name"       : "SuperAwesome Safari Testing",
        "os"         : "os X 10.9",
        "version"    : "7",
        "browserName": "safari",
        "avoid-proxy": false
    },
    {
        "name"       : "Fury Fiery Firefox Testing",
        "browserName": "firefox"
    },
    {
        "name"       : "Cromagnon Chrome Testing",
        "browserName": "chrome"
    }
],
...

###I was Stuck on ... ####My Protractor still connecting to my local web driver server not Sauce Labs Check your config file and make sure that you not decalring any webdriver address and only have sauce lab login credential in your config file.

####Safari can't open selfsign SSL When I tried to run my protractor on Sauce Lab using Safari, the test is failing because it always show error on the failing SSL and timing out since it never going through the testing steps.

Safari SSL Error

Solve this problem by adding "avoid-proxy" parameter in the browser capability object and only use Safari V7 and above.

...
{
    "name"       : "SuperAwesome Safari Testing",
    "os"         : "os X 10.9",
    "version"    : "7",
    "browserName": "safari",
    "avoid-proxy": false
},
...

###Tesing Using ES6 I will continue this tutorial on the next tag, by using ES6 for protractor testing and using real Classes for the Page Object. // TODO: Create Tag ES6 and start developing using ES6 in that tag.

##Additional Reading

  • Introduction to Protractor - Highlevel overview of Functional Test ( E2E Testing ) and how Protractor can help you to do that.
  • Practical Protractor Tutorial - Zero to somewhat-ProtractorHero in one page, I am using example from this resource to write this tutorial.
  • Protractor for AngularJS - Great slide presentation by Ramon Victor about protractor, longer than the previous tutorial but totally worth reading.
  • Egghead Tutorial - Fast and easy tutorial about protractor, only 9:16 long.
  • Protractor API - List of protractor available functions.
  • bonus video - This is not that kind of protractor tutorial.

About

Tutorial for Testing using Protractor