Based on the slides by @ramonvictor available here
Testing is about gaining confidence that your code does what you think it should do
Testing system (NodeJS program)
|
Webdriver (a.k.a. Selenium)
|
Your AngularJS App
Reference: Testing AngularJS apps with Protractor
npm install protractor -gwebdriver-manager updateexports.config = {
directConnect: true,
capabilities: {
'browserName': 'chrome'
},
specs: [
'e2e/specs/*.js'
],
baseUrl: 'http://localhost:9000/',
framework: 'jasmine2',
jasmineNodeOpts: {
defaultTimeoutInterval: 20000
}
};
The reference config file
We will use describe, it and expect syntax from the Jasmine framework.
And be using browser variable, which is a wrapper around an instance of WebDriver
browser: browser.get() browser.param ..element and by: element(by.model('yourName'))protractor: protractor.Key <div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<h1>Hello {{yourName}}!</h1>
</div>
describe('by model', function() {
it('should find an element by text input model', function() {
browser.get('https://angularjs.org/');
var yourName = element(by.model('yourName'));
var greeting = element(by.binding('yourName'));
yourName.clear();
expect(greeting.getText()).toEqual('Hello !');
yourName.sendKeys('Jasmine');
expect(greeting.getText()).toEqual('Hello Jasmine!');
});
});
You can run Protractor on a terminal by typing:
protractor test/e2e/conf.js // this is the relative path to your config file
element() vs element.all()Single element
element(by.binding('latest'));
Collection of elements
// get elements count
element.all(by.repeater('result in memory')).count();
by.bindingelement( by.binding('myModel') );
<span ng-bind="myModel"></span>
<!-- or -->
<span>{{myModel}}</span>
by.modelelement( by.model('myModel') );
<input ng-model="myModel" />
by.repeaterelement.all( by.repeater('user in users').column('user.name') );
<ul>
<li ng-repeat="user in users">
<span>{{user.name}}</span>
</li>
</ul>
by.csselement( by.css('[ng-click="sendMail()"]') );
or
$('[ng-click="sendMail()"]');
<button ng-click="sendMail()">Send mail!</button>
by.id()by.options()by.partialButtonText()element.all(locator).each(eachFunction)by.model,by.binding, by.repeater, etc.click()element( by.css('[ng-click="submit()"]') ).click();
<button ng-click="submit()"><button>
.sendKeys()element( by.model('commentText') ).sendKeys("Hi!");
<textarea ng-model="commentText"><textarea>
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();
});
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');
});
In the example above, the control flow would execute the queue following the sequence we see in the comments. Basically method by.binding would only run once browser.get promise is resolved, and so on.
So, you don't need to worry about calling runs() and waitsFor() blocks.
protractor --elementExplorer
./node_modules/protractor/bin/elementexplorer.js
//homepage.po.js
var AngularHomepage = function() {};
AngularHomepage.prototype = Object.create({}, {
nameInput: { get: function() { return element(by.model('yourName')); }},
greeting: { get: function() { return element(by.binding('yourName')); }},
setName: { value: function(value) { return this.nameInput.sendKeys(name);}}
});
module.exports = AngularHomepage;
var AngularHomepage = require('./homepage.po.js');
describe('HomePage Tests', function() {
var angularHomepage = new AngularHomepage();
angularHomepage.setName('Julie');
//...
});
projectfolder/
|-- css/
|-- js/
|-- img/
|-- tests/
|-- unit/
|-- e2e/
| |-- authentication/
| | |-- *.spec.js
| |-- jobs/
| | |-- *.spec.js
| |-- pages/
| | |-- home.page.js
| | |-- login.page.js
| | |-- register.page.js
| | |-- jobs.list.apply.page.js
| | |-- *.page.js
| |-- protractor.conf.js
exports.config = {
directConnect: true,
capabilities: { 'browserName': 'chrome' },
suites: {
homepage: 'tests/e2e/homepage/**/*Spec.js',
search: ['tests/e2e/contact_search/**/*Spec.js']
},
jasmineNodeOpts: { showColors: true }
};
Running specific suite of tests
protractor protractor.conf.js --suite homepage
multiCapabilitiesexports.config = {
directConnect: true,
multiCapabilities: [
{
'browserName' : 'chrome'
},
{
'browserName' : 'firefox'
}
],
specs: ['example-spec.js'],
jasmineNodeOpts: {
showColors: true
}
};
onPrepareSet window size before starting the tests
exports.config = {
directConnect: true,
capabilities: {
'browserName': 'chrome'
},
onPrepare: function() {
browser.manage().window().setPosition(0, 0);
browser.manage().window().setSize(1600, 800);
browser.manage().window().maximize();
},
jasmineNodeOpts: {
showColors: true
}
};
onPrepareGet HTML report and Capture a screenshot of failed specs of your Automated Suites.
First, install protractor-jasmine2-screenshot-reporter:
npm install protractor-jasmine2-screenshot-reporter --save-dev
onPrepareGet HTML report and Capture a screenshot of failed specs of your Automated Suites.
//protractor.conf.js
var HtmlScreenshotReporter = require('protractor-jasmine2-screenshot-reporter');
exports.config = {
// ...
onPrepare: function() {
jasmine.getEnv().addReporter(
new HtmlScreenshotReporter({
dest: 'test/report',
filename: 'test-report.html',
captureOnlyFailedSpecs: true,
reportOnlyFailedSpecs: false
})
);
}
};
paramsYour protractor.conf.js
exports.config = {
directConnect: true,
capabilities: { 'browserName': 'chrome' },
// This can be changed via the command line as:
// --params.login.user 'ngrocks'
params: {
login: {
user: 'protractor-br',
password: '#ng123#'
}
},
jasmineNodeOpts: { showColors: true }
};
paramsYour test
describe('login page', function() {
var params = browser.params;
it('should login successfully', function() {
element( by.model('username') ).sendKeys( params.login.user );
element( by.model('password') ).sendKeys( params.login.password );
element( by.css('[ng-click="login()"]') ).click();
expect( element(by.binding('username') ).getText() ).toEqual( params.login.user );
});
});
jasmineNodeOptsexports.config = {
directConnect: true,
capabilities: { 'browserName': 'chrome' },
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
isVerbose: true,
includeStackTrace: true
}
};
Sorry, you can't! :(
Though you can tell it not to be that smart about your non-Angular app:
beforeEach(function() {
browser.ignoreSynchronization = true;
});
onPrepare: function() {
global.isAngularSite = function(flag) {
browser.ignoreSynchronization = !flag;
};
}
beforeEach(function() {
isAngularSite(false); // isAngularSite(true), if it's an Angular app!
});
Reference: Protractor - Testing Angular and Non-Angular Sites
Demo ngapp repo available on github.
Reference: http://watirmelon.com/2011/06/10/yet-another-software-testing-pyramid/
Reference: http://watirmelon.com/2011/06/10/yet-another-software-testing-pyramid/
Thanks to WHG team 
for the opportunity!