Protractor

End to end testing for Angular

Kudos to @ramonvictor

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

@juliemr

Layers of abstraction

Testing system (Node.js, Java etc)

Webdriver (a.k.a. Selenium)

Protractor

Installation

  1. npm install protractor -g
  2. webdriver-manager update

Create config.js

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

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

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

  onPrepare: function() {      
    //pretty self explanatory
  },

  jasmineNodeOpts: {
    //Jasmine options
  }
};

Protractor = Jasmine + WebdriverJs

describe('Demo test', function() {
  it('should have a title', function() {
    browser.get('http://local.liligo.com');

    expect(browser.getTitle())
      .toEqual('Compare flights with Liligo.com', 'The title is not correct.');
  });
});

Let's run it!

We need a server to run our code on.

webdriver-manager start

In an other terminal, we can run the test.

protractor tests/e2e/config.js

Searching for elements on the page

The basic building block: element

element(by.binding('appName'))
element.all(by.css('ng-click="open page"')).get(0)

Single

Collection

Find elements by: binding

<span ng-bind="myModel"></span>
<!-- or -->
<span>{{myModel}}</span>
element(by.binding('myModel'))

Find elements by: model

<input ng-model="myModel" />
element(by.model('myModel'))

Find elements by: CSS

<button ng-click="sendMail()">Send mail!</button>
element(by.css('[ng-click="sendMail()"]'))

Find elements by: CSS

<input id="address" type="text" ng-model="address">
<button class="btn" ng-click="sendMail()">Send mail!</button>
$('#address')
$('.btn')

Execute events: .click()

<button class="btn" ng-click="sendMail()">Send mail!</button>
$('.btn').click()

Execute events: submit

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

Protractor magic: promises

All Protractor methods are asynchronous, and return promises.

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

Protractor magic: control flow

To keep execution organized, a queue of pending promises is maintained: the control flow.

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');
});

Test different screen sizes

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

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

  onPrepare: function() {
     browser.driver.manage().window().setSize(1600, 800);
  },

  jasmineNodeOpts: {
    showColors: true
  }
};

Test different screen sizes

beforeEach()
beforeEach(function(){
    //iPhone sized window
    browser.driver.manage().window().setSize(320, 568);
})

Adjust according to environment

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

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

  params: {
    login: {
      user: 'protractor-br',
      password: '#ng123#'
    }
  }
};

Test the non-Angular parts of your application

Just get the WebDriver instance!


browser.driver.findElement(by.css('[data-ptor="submit-btn"]'));
//in your configuration

onPrepare: function() {
   global.wbdvr = browser.driver;
}

//in your test

wbdvr.findElement(by.css('[data-ptor="submit-btn"]'));

DRY code: exports and require

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

A look at the dark side of Protractor

Still quite hard to maintain

Promises are nice, but...

"Error while waiting for Protractor to sync with the page: {}"

$timeout, $http
$interval

Conclusion

  • take into account the time required for maintenance
  • if you rely heavily on async operations, think about it twice

If your app is based on Angular, your first choice should be Protractor, but...

Thank you!

Hit me up @BenedekGagyi!

Made with Slides.com