Protractor

Luis Hernandez

Developer

lhernandez@nearsoft.com

What's Protractor?

Protractor is an open source E2E testing automation framework, designed specifically for AngularJS web applications. The Protractor automation tool is a Node.js program built on top of WebDriverJS.

Workflow

Protractor works as a Solution integrator, combining powerful technologies like Node.js, Jasmine, Selenium, Mocha, Cucumber, and Web driver.

Protractor is built on top of WebDriverJS 

Testing system (NodeJS, Java, etc)

|

Webdriver (a.k.a. Selenium)

|

Your AngularJS App

Why is testing so important?

" Testing is about gaining confidence that your code does what you think it should do"

@juliemr

Whats the idea behind E2E testing?

  • How would the users see my application?
  • Is my backend communicating with my frontend?
  • Can I release this code?
  • It does NOT replace Unit Testing!

The dark side of E2E testing

  • It needs a specific running environment
  • It's hard to write
  • It's difficult to debug
  • It's hard to keep the tests up-to-date

Install

  • Download Node.JS
  • $ sudo npm install protractor -g
  • $ sudo webdriver-manager update

Setup a conf.js file

exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',

  capabilities: {
    'browserName': 'chrome'
  },

  specs: ['example-spec.js'],

  jasmineNodeOpts: {
    showColors: true
  }
};

Write your tests using Jasmine and WebdriverJS

describe('by model', function() {
   it('should find an element by text input model', function() {
     var username = element(by.model('username'));
     var name = element(by.binding('username'));

     username.clear();
     expect(name.getText()).toEqual('');

     username.sendKeys('Jane Doe');
     expect(name.getText()).toEqual('Jane Doe');
   });
});

Protractor global variables

  • browser: browser.get()
  • element and by: element(by.model('yourName'))
  • protractor: protractor.Key

Basic example

// example-spec.js
describe('angularjs homepage', function() {
  it('should greet the named user', function() {
    browser.get('http://www.angularjs.org');

    element(by.model('yourName')).sendKeys('Julie');

    var greeting = element(by.binding('yourName'));

    expect(greeting.getText()).toEqual('Hello Julie!');
  });
});

Let's run it

First things first, open the terminal and start the webdriver server:

 

webdriver-manager start

 

After that, you can run Protractor in another terminal by typing:

 

protractor test/e2e/config.js // this is the relative path to your config.js file

Searching for elements on the page

element() vs element.all()

 Single element

element( by.binding('appName') );


 Collection of elements

element.all( by.css('[ng-click="openPage()"]') ).get(2).click();

by.binding

In your test

element( by.binding('myModel') );


In your application

<span ng-bind="myModel"></span>
<!-- or -->
<span>{{myModel}}</span>

by.css

In your test

element( by.css('[ng-click="sendMail()"]') );


In your application

<button ng-click="sendMail()">Send mail!</button>

Find out more in Protractor API

Executing Events

.click()

In your test

In your application

element( by.css('[ng-click="submit()"]') ).click();
<button ng-click="submit()"><button>

On Enter Press

In your test

In your application

element( by.model('commentText') ).sendKeys("Hi!", protractor.Key.ENTER);
<textarea ng-model="commentText"><textarea>

Promises and the Control Flow

Promises based

All Protractor methods are asynchronous and return promises.

 

 
// Example of getText() promise
element( by.model('zipcode') ).getText()
  .then(function(val) {
    var num = val.substring(0, 4);
    var isNum = !isNaN(num);
    expect( isNum ).toBeTruthy();
  });

Control Flow

WebDriverJS maintains a queue of pending promises, called the control flow, to keep execution organized. 

it('should find an element by text input model', function() {
  browser.get('#/home'); // (1) method browser.get

  // (2) method by.binding
  var login = element(by.binding('login'));
  // (3) method getText
  expect(login.getText()).toEqual('User');
});

Maintanable Tests

The big picture

  • Page Objects - These are the js files where you map the elements and write the functions to perform actions;
  • Exports and Require - This is how you connect your Page Objects to your Test Specs;
  • Test specs - These are the js files where you write your tests using jasmine syntax.

Tests directory structure

 

 
projectfolder/
  |-- css/
  |-- js/
  |-- img/
  |-- tests/
    |-- unit/
    |-- e2e/
    |    |-- homepage/
    |    |     |-- homepage.po.js
    |    |     |-- *.spec.js
    |    |-- profile/
    |    |     |-- profile.po.js
    |    |     |-- *.spec.js
    |    |-- config.js

Page Objects

var AngularHomepage = function() {
  this.nameInput = element(by.model('yourName'));
  this.greeting = element(by.binding('yourName'));

  this.get = function() {
    browser.get('http://www.angularjs.org');
  };

  this.setName = function(name) {
    this.nameInput.sendKeys(name);
  };
};

Node.JS exports and require

var AngularHomepage = function() {
  this.nameInput = element(by.model('yourName'));
  this.greeting = element(by.binding('yourName'));
  // ...
};
module.exports = AngularHomepage;
var AngularHomepage = require('./homepage.po.js');
describe('HomePage Tests', function() {
   var angularHomepage = new AngularHomepage();
   angularHomepage.nameInput.sendKeys('Rafael');
   //...
});

Your Page Object file

Your Test file

Separate your tests in various test suites

exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',
  capabilities: { 'browserName': 'chrome' },

  suites: {
    homepage: 'tests/e2e/homepage/**/*Spec.js',
    search: ['tests/e2e/contact_search/**/*Spec.js']
  },

  jasmineNodeOpts: { showColors: true }
};
protractor protractor.conf.js --suite homepage

Your Page Object file

Running specific suite of tests

 
 

Using Protractor in a non-AngularJS app

Using browser.driver

You only need to access the webdriver instance by using browser.driver: 
browser.driver.findElement(by.css('[data-ptor="submit-btn"]'));

It can be even more elegant

conf.js
onPrepare: function() {
   global.dvr = browser.driver;
}
test
dvr.findElement(by.css('[data-ptor="submit-btn"]'));

Protractor waits for Angular to finish its work

tests
beforeEach(function() {
   return browser.ignoreSynchronization = true;
});

Protractor waits for Angular to finish its work

conf.js
onPrepare: function() {
   global.isAngularSite = function(flag) {
      browser.ignoreSynchronization = !flag;
   };
}
tests
beforeEach(function() {
   isAngularSite(false); // isAngularSite(true), if it's an Angular app!
});

Protractor waits for Angular to finish its work

conf.js
onPrepare: function() {
   global.isAngularSite = function(flag) {
      browser.ignoreSynchronization = !flag;
   };
}
tests
beforeEach(function() {
   isAngularSite(false); // isAngularSite(true), if it's an Angular app!
});

Final thoughts

  • E2E testing is a complement to Unit testing

 

  • Write your tests with scalability in mind

Reference

http://ramonvictor.github.io/protractor/slides/

Thanks.

Protractor

By Luis Hernandez

Protractor

  • 772