среда, 19 августа 2015 г.

End to end testing with Protractor + Selenium


Intro

Protractor and Selenium is used as default e2e tools for creating and managing tests for Angular.js, in particular yeoman generator called generator-gulp-angular is using them.

Protractor is an e2e test framework for Angular apps.
Selenium is used to interact with the browser.
Jasmine is used for writing tests code.

I will assume that we are using yeoman generated angular application using generator-gulp-angular which automatically creates all necessary folder structure and downloads appropriate packages.

protractor.conf.js

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

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

    //*optional
    multiCapabilities: [
       {
         'browserName' : 'chrome'
       },
       {
         'browserName' : 'firefox'
       }
    ],

    //*optional
    // gives ability to run specific suite of tests with command-parameter:
    // protractor protractor.conf.js --suite homepage
    suites: {  
      homepage: 'tests/e2e/homepage/**/*Spec.js',
      search: ['tests/e2e/contact_search/**/*Spec.js']
    },

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

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

    jasmineNodeOpts: {
      showColors: true,
      realtimeFailure: true // show errors in a real time - immediately after happening 
    },  // This can be changed via the command line as:
    
    //*optional  
    // --params.login.user 'ngrocks'
    // can be accessed via browser.params in tests
    params: {
      login: {
        user: 'protractor-br',
        password: '#ng123#'
      }
    },
};
  

Writing tests: As in unit tests with Karma & Jasmine

describe('description', function() {
     it('test description', function() {

     });
  });
  
This is because Protractor uses Jasmine for its test syntax.

Writing Tests Code - Searching elements

element( 
     // each line is a possible variant:
     by.binding('appName')
     by.model('myModel')
     by.css('[ng-click="openPage()"]')
     by.repeater('user in users')
     by.xpath('following-sibling::span')
     // ...
  );

  // the same is for:
  element.all(...);
  
Best practices is to avoid using by.css and stick to other options of searching elements (if possible ofc).
Another option is t use protractor.findElement() or protractor.findElements instead of methods mentioned above correspondingly.
The main difference between protractor's methods and element is that any search of element will be started exactly AFTER the angular finish his work.
Another way to make protractor not to wait for Angular to finish its work is:
beforeEach(function() {
   return browser.ignoreSynchronization = true;
});

Writing Tests Code - Executing Events

element(...)
     // each line is a possible variant:
     .click();
     .sendKeys("Hi!", protractor.Key.ENTER);

  // the same is for:
  element.all(...);
  

Writing Tests Code - Protractor methods are return promises

// Example of getText() promise
  element(..).getText()
    .then(function(val) {
      expect(val).toEqual('23545');
    });

Writing Tests Code - Page Objects - Separate Test logic and operations on the elements

A good practice in writing e2e tests is to separate test logic from operations on the elements like getting/setting/clicking. For every view/page or group of views/pages it is possible to separate out some actions and create for them so called page objects: files with code containing methods related to interacting with page, for instance:
  • page.po.js - contains methods for navigating/logging in or out
  • home.po.js - defines methods for interacting with pagination and list of records
  • record.po.js - specifies methods for getting/setting/interacting with elements when doing CRUD actions with some record.


Protractor Test Control Flow

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

Debugging Protractor Tests

gulp-protractor-qa
It warns you on the fly whether all element() selectors could be found within your AngularJS view files.

Export xml results of your Automated Suites

//config.js
exports.config = {
  onPrepare: function() {
    var folderName = (new Date()).toString().split(' ').splice(1, 4).join(' ');
    var mkdirp = require('mkdirp');
    var newFolder = "./reports/" + folderName;
    require('jasmine-reporters');

    mkdirp(newFolder, function(err) {
      if (err) {
        console.error(err);
      } else {
        jasmine.getEnv().addReporter(new jasmine.JUnitXmlReporter(newFolder, true, true));
      }
    });
  },
};

Possible Errors You can face

UnknownError: Error Message => '[ng:btstrpd] App Already Bootstrapped with this Element
sollution:
before any browser.get use:
browser.driver.get('about:blank');
// source http://www.sebdangerfield.me.uk/2014/01/angularjs-protractor-app-already-bootstrapped-error/