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 -g
webdriver-manager update
exports.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.binding
element( by.binding('myModel') );
<span ng-bind="myModel"></span>
<!-- or -->
<span>{{myModel}}</span>
by.model
element( by.model('myModel') );
<input ng-model="myModel" />
by.repeater
element.all( by.repeater('user in users').column('user.name') );
<ul>
<li ng-repeat="user in users">
<span>{{user.name}}</span>
</li>
</ul>
by.css
element( 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
multiCapabilities
exports.config = {
directConnect: true,
multiCapabilities: [
{
'browserName' : 'chrome'
},
{
'browserName' : 'firefox'
}
],
specs: ['example-spec.js'],
jasmineNodeOpts: {
showColors: true
}
};
onPrepare
Set 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
}
};
onPrepare
Get 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
onPrepare
Get 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
})
);
}
};
params
Your 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 }
};
params
Your 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 );
});
});
jasmineNodeOpts
exports.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!