Testing is about gaining confidence that your code does what you think it should do
Testing system (NodeJS, Java, etc)
|
Webdriver (a.k.a. Selenium)
|
Your AngularJS App
Reference: Testing AngularJS apps with Protractor
sudo npm install protractor -g
sudo webdriver-manager update
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: {
'browserName': 'chrome'
},
specs: ['example-spec.js'],
jasmineNodeOpts: {
showColors: true
}
};
describe('by model', function() {
it('should find an element by text input model', function() {
var username = element(by.model('username'));
var name = element(by.binding('username'));
username.clear();
expect(name.getText()).toEqual('');
username.sendKeys('Jane Doe');
expect(name.getText()).toEqual('Jane Doe');
});
});
browser
: browser.get()
element
and by
: element(by.model('yourName'))
protractor
: protractor.Key
// example-spec.js
describe('angularjs homepage', function() {
it('should greet the named user', function() {
browser.get('http://www.angularjs.org');
element(by.model('yourName')).sendKeys('Julie');
var greeting = element(by.binding('yourName'));
expect(greeting.getText()).toEqual('Hello Julie!');
});
});
First things first, open the terminal and start the webdriver server:
webdriver-manager start
After that, you can run Protractor in another terminal by typing:
protractor test/e2e/config.js // this is the relative path to your config.js file
element()
vs element.all()
Single element
element( by.binding('appName') );
Collection of elements
// clicks the 3rd element
element.all( by.css('[ng-click="openPage()"]') ).get(2).click();
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( by.repeater('user in users').row(0).column('name') );
<ul>
<li ng-repeat="user in users">
<span>{{user.name}}</span>
</li>
</ul>
by.css
element( by.css('[ng-click="sendMail()"]') );
<button ng-click="sendMail()">Send mail!</button>
by.select()
by.partialButtonText()
elementArrayFinder.each()
by.model
, by.repeater
, etc.click()
element( by.css('[ng-click="submit()"]') ).click();
<button ng-click="submit()"><button>
element( by.model('commentText') ).sendKeys("Hi!", protractor.Key.ENTER);
<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.
webdriver-manager start
/usr/local/lib/node_modules/protractor/bin/elementexplorer.js http://angularjs.org
projectfolder/
|-- css/
|-- js/
|-- img/
|-- tests/
|-- unit/
|-- e2e/
| |-- homepage/
| | |-- homepage.po.js
| | |-- *.spec.js
| |-- profile/
| | |-- profile.po.js
| | |-- *.spec.js
| |-- config.js
var AngularHomepage = function() {
this.nameInput = element(by.model('yourName'));
this.greeting = element(by.binding('yourName'));
this.get = function() {
browser.get('http://www.angularjs.org');
};
this.setName = function(name) {
this.nameInput.sendKeys(name);
};
};
var AngularHomepage = function() {
this.nameInput = element(by.model('yourName'));
this.greeting = element(by.binding('yourName'));
// ...
};
module.exports = AngularHomepage;
Your Test file
var AngularHomepage = require('./homepage.po.js');
describe('HomePage Tests', function() {
var angularHomepage = new AngularHomepage();
angularHomepage.nameInput.sendKeys('Rafael');
//...
});
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
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 = {
seleniumAddress: 'http://localhost:4444/wd/hub',
multiCapabilities: [
{
'browserName' : 'chrome'
},
{
'browserName' : 'firefox'
}
],
specs: ['example-spec.js'],
jasmineNodeOpts: {
showColors: true
}
};
onPrepare
Set window size before starting the tests
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: {
'browserName': 'chrome'
},
onPrepare: function() {
browser.driver.manage().window().setSize(1600, 800);
},
jasmineNodeOpts: {
showColors: true
}
};
onPrepare
Export xml results of your Automated Suites
First, install jasmine-reporters:
npm install jasmine-reporters
And to keep xml results in Timestamp directories, install mkdirp package:
npm install mkdirp
onPrepare
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));
}
});
},
};
params
Your config.js
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
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 = {
seleniumAddress: 'http://localhost:4444/wd/hub',
capabilities: { 'browserName': 'chrome' },
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
isVerbose: true,
includeStackTrace: true
}
};
Sorry, you can't! :(
You only need to access the webdriver instance by using browser.driver
:
browser.driver.findElement(by.css('[data-ptor="submit-btn"]'));
onPrepare: function() {
global.dvr = browser.driver;
}
dvr.findElement(by.css('[data-ptor="submit-btn"]'));
Though you can tell it not to be that smart about your non-Angular app:
beforeEach(function() {
return 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
It warns you on the fly whether all element() selectors could be found within your AngularJS view files.
Thanks to @rafaelbattesti contributions!