Avoiding Sleep, Modules, params and much more
Jason Mavandi
You should always start with a data file variable parameters in JSON (or something else if you prefer) .
{
"int":{
"url":"https://int.example.com",
"username":"un",
"password":"pw!"
}
}data.json
Things to store:
When you do not know how long it will take for a page to load, sleep is a quick way to get it working.
DO NOT USE SLEEP!
It does not ensure anything but long tests.
//Sleeps are given in microseconds
//Protractor sleep
browser.sleep(500);
//Selenium under protractor sleep
browser.driver.sleep(1000);Protractor has built-in synchronization that will wait for elements as long as it can find angular first.
If for some reason your application takes too long or cannot synchronize you can ignore synchronization.
NOTE: This is not recommended, but may be necessary
//Required every time a page changes
browser.ignoreSynchronization = true;If you are not not ignoring Synchronization and you want to wait longer than the default timeout time of 11 seconds, add "allScriptsTimeout" to your config file.
...
//Change default syncronization time of 11 seconds
allScriptsTimeout: timeout_in_milliseconds
...protractor.conf.js
When you need to do not know how long something will take, rather than using sleep, choose an element and wait for it.
el = element(by.css("#id"));
//Wait for it to be on the page
browser.wait(function() { return el.isPresent(); }, waitTimeoutMilliseconds);
//Then wait for it to visible
browser.wait(function() { return el.isDisplayed(); }, waitTimeoutMilliseconds);
//If you try to check just isDisplayed sometimes the element will not be foundIf you know text on a page is going to change you can wait till that happens. Example would be wait for "loading" to become useful text, on angular defaults {{<useful info> || "loading"}}.
var waitForTextToBecome = function(el, text){
waitForElement(el);
browser.wait(function() {
return el.getText().then(function(currentText){
return currentText === text;
});
},waitTimeoutMs);
};Protractor is built on javascript which is asynchronous. The way it is able to do ordered/synchronized things is because it adds each step to the control flow.
If you want any Javascript to be run in the correct place in your test, you need to put it into the control flow.
//Adding something to protractor control flow
browser.controlFlow().execute(function() {
console.log("This will be printed in order");
});If you want to know how long a test takes saves the time through the control flow.
var startTime, duration;
...
browser.controlFlow().execute(function() {
startTime = new Date().getTime();
});
...
browser.controlFlow().execute(function() {
duration = new Date().getTime() - startTime;
});
...
browser.controlFlow().execute(function() {
console.log("Duration:", duration);
});If you want to follow multiple times make this a function and stave it into an object.
var timing = {}, timingStarts = {};
var startTimer = function(name){
browser.controlFlow().execute(function() {
timingStarts[name] = new Date().getTime();
});
};
var endTimer = function(name){
browser.controlFlow().execute(function() {
timing[name] = new Date().getTime() - timingStarts[name];
});
};
var logDuration = function(name){
//There could be much better uses of this such as uploading somewhere
browser.controlFlow().execute(function() {
console.log("Timer " + name + ": " + timing[name] + "ms");
});
};
it("",function(){
...
startTimer("timer");
...
endTimer("timer");
..
logDuration("timer")
});Jasmine, the testing framework below Protractor, has ways to check if tests pass.
expect(true).toBeTruthy();
var text = "text"
expect(text).toEqual("text");
Sometimes the error messages from expect statements are cryptic. If it is helpful you can make your own.
describe('Matchers', function() {
beforeEach(function(){
this.addMatchers(matchers);
});
it('examples', function() {
expect(false).toBeTruthy();
expect(false).toEqualBecause(true,"things should be true");
});
});
var matchers = {
toEqualBecause: function( value, message ) {
this.message = function() {
return "Expected '" + this.actual + "' to equal '" +
value + "' because '" + message + "'.";
};
return this.actual === value;
},
};Since Protractor is built on NodeJS you can make your code easily shareable as node modules. They can be uploaded to an NPM repository such as npmjs.org
This is especially helpful with things like login automation that is be needed by everyone.
All that is needed is a file called "package.json" and then a ".js" file that exports your code. This is a basic setup.
{
"name": "shareable-demo-module",
"version": "0.0.1",
"main": "index.js"
}
var share = {};
share.configurable = function(object, name){
return browser.params[name] || process.env[name] || object[name];
};
module.exports = exports = share;index.json
package.json
Install the module then require it.
# Install the package from file
$ npm i /path/to/local/package
# Install the package from a repo
$ npm i shareable-demo-module var share = require('shareable-demo-module');
var data = require('/path/to/json/file');
var env = data[share.configurable({},"env")];
var url = share.configurable(data,"url");Sometimes you need you tests to be variable. This is helpful when using the same tests in different environments.
This is how to better use the data files we created.
Javascript can see use your environment variables. Use choose things like enviroments
var url = process.env.url;
browser.get(url); //Mac/Linux
$ export url="http://localhost:8080"
//Windows
$ SET url="http://localhost:8080"Another way is to pass the parameters into your protractor call from the command line.
var url = browser.params.url;
browser.get(url); $protractor --params.url="http://localhost:8080"You can set default protractor commands in the config file. This is helpful to have a default environment.
var data = require('/path/to/datafile')[browser.params.env]
browser.get(data.url); ...
params: {
env: "int"
},
...protractor.conf.js
You can set default protractor commands in the config file. This is helpful to have a default environment.
var configurable = function(object, name){
//This will check for a params then an enviroment variable then the object
return browser.params[name] || process.env[name] || object[name];
};
var data = {
"local":{
"url":"https://locahost:8080"
}
}
var env = data[configurable({},"env")];
var url = configurable(data,"url");
You can work with your test results.
afterEach(function(){
this.results_.description //Name of the test
this.results_.failedCount //Number of failures
this.results_.totalCount //Number of expect statements
});If protractor config file name is "protractor.conf.js" calling "protractor" in that directory finds the config.
#Specified
$ protractor protractor.conf.js
#Default
$ protractorUnderstanding everything going on is important.