A speed date with end to end testing
An end to end testing framework of frameworks
Do what the users do
* The realness of the data obviously depends on the enviornment
Yes. But it's gotten better.
You could use WebDriver JS, but…
For Angular sites
For non-Angular sites
Not to mention...
The protractor global wraps the webdriver namespace.
elem.sendKeys(protractor.Key.ENTER);
browser.driver.manage().deleteAllCookies();
The browser global extends / contains webdriver.WebDriver. The raw driver is accessible through browser.driver.
Protractor vs WebDriverJS
describe('angularjs demo page', function() {
it('should add one and two', function() {
//Global browser wraps already built WD
browser.get('http://bit.ly/1tUzecs');
//Global 'by' means shorter syntax
element(by.model('first')).sendKeys(1);
element(by.id('gobutton')).click();
//Global 'expect' unpacks Promises
expect(element(by.binding('latest'))
.getText()).toEqual('3');
});
});
var assert = require('assert'),
test = require('selenium-webdriver/testing'),
wd = require('selenium-webdriver');
test.describe('Google Search', function() {
test.it('should work', function() {
var driver = new wd.Builder()
.withCapabilities(
wd.Capabilities.chrome())
.build(); //Manual WD build
driver.get('http://www.google.com');
driver.findElement(wd.By.name('q'))
.sendKeys('foo');
driver.findElement(wd.By.name('btnG'))
.click();
driver.getTitle().then( function(title) {
assert.equal(title, 'foo - Google');
});
driver.quit(); //Note manual quit
});
});
WebDriverJS Control Flow + Protractor upgrades
To avoid callback hell, WebDriver uses Control Flow to queue commands.
element.click()
element.sendKeys('foo')
//is the same as
element.click().then(function() {element.sendKeys('foo') })
//Jasmine expect has been modified (jasminewd) to unwrap promises
expect(browser.getTitle()).toEqual('Protractor ');
Protractor uses this same system to it's advantage.
node_modules/protractor/[bin|selenium]
Protractor
Selenium Server
Web Server
localhost
localhost
localhost
CI server
Dev, QA, Stage, Prod
Source: "Testing AngularJS apps with Protractor"
Test
Server
Browser
npm install -g protractor
describe('angularjs homepage', function() {
it('should have a title', function() {
browser.get('http://juliemr.github.io/protractor-demo/');
expect(browser.getTitle()).toEqual('Super Calculator');
});
});
exports.config = {
directConnect: true,
specs: ['spec.js']
}
Write a test spec //spec.js
Install with npm
Configure your setup //protractor.config.js
Text
Text
protractor protractor.config.js
Text
Run
Highlights
Seriously. The reference config is great.
Okay, maybe a little more down below
//config.js
onPrepare: function(){
browser.ignoreSynchronization = true;
}
//Otherwise in your specs
beforeEach(function() {
return browser.ignoreSynchronization = true;
});
//config.js
onPrepare: function(){
global.isAngularSite = function(flag){
browser.ignoreSynchronization = !flag;
};
}
//*.spec.js
beforeEach(function() {
isAngularSite(false);
});
Text
//config.js
onPrepare: function(){
browser.ignoreSynchronization = true;
}
//Otherwise in your specs
beforeEach(function() {
return browser.ignoreSynchronization = true;
});
Basic setup
Gettin' fancy
//base.config.js
exports.config = {
baseUrl: http://nebraskajs.com/
multiCapabilities: [{"browserName": "internet explorer"},{'browserName': 'chrome'}]
}
//local.chromeOnly.config.js
exports.config = (function(){
var r = require('./protractor.base.config.js'); //Use require to pull in your base config
var tmpConfig = r.config;
tmpConfig.chromeOnly = true;
tmpConfig.chromeDriver = './node_modules/protractor/selenium/chromedriver';
tmpConfig.baseUrl = 'https://localhost:3000/';
//Use capabilities instead of multiCapabilities for grunt reasons
tmpConfig.capabilities = {
'browserName': 'chrome',
'chromeOptions': { 'args': ['show-fps-counter=true'] }
};
/* Null or clear out fields that might conflict with the above statements */
tmpConfig.multiCapabilities = [];
return tmpConfig;
})();
//Just to give you some ideas
onPrepare: function () {
var jasmineReporters = require('jasmine-reporters');
var fs = require('fs-extra');
browser.driver.manage().window().setSize(1024, 768);
browser.manage().timeouts().pageLoadTimeout(5000);
browser.manage().timeouts().implicitlyWait(3000);
// The IE driver doesn't like get('#/foo')
// This allows us to call getRoute('#/foo');
global.getRoute = function(route) {
return browser.get('/appContext/' + route);
};
var dir = 'out/tests/protractor/xmloutput';
fs.ensureDir(dir, function (err) {
if (err) {
console.log(err);
}
//dir has now been created, including the directory it is to be placed in
});
var capsPromise = browser.getCapabilities();
capsPromise.then(function (caps) {
var browserName = caps.caps_.browserName.toUpperCase();
var browserVersion = caps.caps_.version;
var prePendStr = browserName + '-' + browserVersion + '-';
//Do something cool
});
jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
/**
* NOTE: Changing versions of protractor or jasmine might require this to change.
* https://github.com/larrymyers/jasmine-reporters#protractor
*/
jasmine.getEnv().addReporter(
new jasmine.JUnitXmlReporter(dir, true, true)
);
}
Avoid multiple config files
grunt protractor:full:prod:ie
keepAlive: false //Needed for Jenkins
Overriding your config file
An e2e testing best practice
var AngularDemoPage = function() {
this.firstInput = element(by.model('first'));
this.secondInput = element(by.model('second'));
this.goButton = element(by.id('gobutton'));
this.latest = element(by.binding('latest'));
this.get = function() {
browser.get('http://juliemr.github.io/protractor-demo/');
};
this.setInputs = function(val1, val2) {
this.firstInput.sendKeys(val1);
this.secondInput.sendKeys(val2);
}
this.clickGoButton = function() {
return this.goButton.click();
}
/** @return Promise resolves answer value **/
this.getLatestText = function() {
return this.latest.getText();
}
};
module.exports = AngularDemoPage;
IDE autocomplete based on JSDocs makes Page Objects even better.
var AngularDemoPage = require('./demo.po.js');
var errorUtils = require('./../common/errors.util.js');
describe('angularjs demo page', function() {
it('should add one and two', function() {
AngularDemoPage.get();
AngularDemoPage.setInputs(1,2);
AngularDemoPage.clickGoButton();
expect(AngularDemoPage.getLatestText()).toEqual('3');
expect(errorUtils.hasErrors()).toBe(false); //Global page utils
});
});
|-- e2e/
| |-- components/
| | |-- datepicker.component.js
| |-- homepage/
| | |-- homepage.po.js
| | |-- *.spec.js
| |-- profile/
| | |-- profile.po.js
| | |-- *.spec.js
|-- e2e/
| |-- components/
| | |-- datepicker.component.js
| |-- pages/
| | |-- homepage.page.js
| | |-- profile.page.js
| |-- specs/
| | |-- homepage/
| | | |-- hompage.login.spec.js
| | |-- profile/
| | | |-- profile.social.spec.js
| | | |-- profile.pwdChange.spec.js
| | |-- *.spec.js
Pick one and stick with it
But keep your specs grouped by area. It helps in defining test suites.
Locator <= by.*(string)
ElementFinder <= element[.all](locator)
Promise <= foundElem.takeSomeAction()
An ElementFinder will not contact the browser until an action method has been called. Since all actions are asynchronous, all action methods return a promise.
element(by.css('some-css')).element(by.tagName('tag-within-css')).click();
element.all(by.css('some-css')).first().element(by.tagName('tag-within-css'));
ElementFinders can be chained
Just instructions until you pull the trigger
The thing that might drive you mad
e2e testing - better, but not awesome
Testability API - A bit of explaination
var testability = angular.getTestability(document.body);
testability.allowAnimations(false); //Faster tests
//Get notified when pending async ops known to Angular have been completed.
testability.whenStable(myCallback); //This is what protractor expect() uses
$compileProvider.debugInfoEnabled(false) //prod - significant performance boost
https://www.linkedin.com/in/derekeskens
https://plus.google.com/+DerekEskens
These slides available on slides.com
Demo code on Github
Laptop designed by B. Agustín Amenábar Larraín from the Noun Project
Server designed by aLf from the Noun Project
Server designed by Norbert Kucsera from the Noun Project
Website designed by Max Miner from the Noun Project
Banana Peel designed by Liliane Lass Erbe from the Noun Project
Ramon Victor - Protractor for AngularJS
The Noun Project for kick ass icons
ng-newsletter - Practical End-to-End Testing with Protractor
Thiago Felix - Using Page Objects to Overcome Protractor's Shortcomings