When automating browser checks, user journeys, end to end checks and the like, there is plenty written about assertion frameworks and page objects. But one thing that’s incredibly useful to aid with automation are helpers.
/ What are helpers?
Helpers are small pieces of functionality that perform simple user interactions that can be reused. These keep the checks simple and further abstracted ensuring they are both readable and robust.
Example:
'use strict';
var loginHelper = function loginHelper(){
this.login = function(user, password){
browser.manage().window().setSize(1500,1000);
browser.get('/login');
var userNameField = element(by.model('username'));
var passwordField = element(by.model('password'));
var loginButton = element(by.css('button'));
userNameField.sendKeys(user);
passwordField.sendKeys(password);
loginButton.click();
};
};
module.exports = new loginHelper();
So within your conf.js, within onPrepare, or within your spec in a beforeAll() to log in, it is simply a case of using this function.
loginHelper.login('Dan','mypassword' );
Now, that’s a basic helper. It gets the job done. But what about more complex helpers.
/ Increasing the complexity
At Scott Logic, we have an application built specifically for assessing testing skills in interviews. Here’s a peak.Hopefully, you've already spotted numerous issues.
We have also written some automated checks for this to validate positively that some basic functions work.
One being to make multiple transfers. Here’s the helper for it:
'use strict'
var transferPage = require("../pages/transferpage");
var listCountHelper = require("../helpers/list-count-helper");
var tpage = new TransferPage();
function transfer(noOfTransfers){
var accountFrom = 'Account From';
var accountTo = 'Account To';
var transferAmountArray = [];
var i = 0;
while (i < noOfTransfers){
var transferAmount = Math.floor(Math.random()*100);
transferAmountArray.push(transferAmount);
tpage.transferButton.click();
tpage.typeFrom(accountFrom);
tpage.typeTo(accountTo);
tpage.typeAmt(transferAmount);
tpage.confirmTransfer.click();
i++;
}
return transferAmountArray;
}
function validateTransfers(noOfTransfers){
var expectedTransfers = noOfTransfers;
var startingNoOfTranfers = listCountHelper.countList();
var amountArray = transfer(noOfTransfers);
var totalTransfers = listCountHelper.countList();
if (startingNoOfTranfers < totalTransfers) {
expectedTransfers = totalTransfers - startingNoOfTranfers;
}
return expectedTransfers;
};
module.exports = {
transfer: transfer,
validateTransfers: validateTransfers
};
This helper has two functions. One that can be used to make a number of transfers using the noOfTransfers variable input.
The other, validates the number of transfers you would expect to see on the page.
It's good practice to collate related helper functions to minimise the number of required files.
/ Combining Helpers
Helpers can be as simple, or complex as you like. And you can use the helpers within helpers to chain actions so that they are abstracted from the automated check.
So, if I wanted to Log in to the application, perform some transfers, delete some transfer and update some transfers , I could create a helper that would do that on its own, taking in the number of transactions that we wanted to perform.
Or we could utilise 3 different helpers; logIn, transfer(n), deleteTransfer(n), updateTransfer(n). That way, we can reuse those individual functions elsewhere.
Which way you organise these is up to you and will be entirely dependent on the need and speed at which you need them. It may be you start with one monolothic function as it's a particularly important journey that you want to automate. But then you can slowly abstract pieces into separate functions. The end result either way would be that the check would read something like:
it('should log in, perform x transfers, delete y transfer, update z transfer'), function() {
var transfersDisplayed = helper.transferJourney(x,y,z);
expect(transfersDisplayed).toEqual(n);
});
In this instance, the variables x,y and z could be defined within the page. Or, you could create a data object for this specific test within a test-data config file.
Example: exports.testData = {
multiTransfer:{
x:10,
y:2,
z:1
}
}
Which would change the automated check to pass in the data object instead. So at the top of your spec, you would have a require for the test-data config file.
td = require(''../../data/test-data-conf');
Then in the check, you reference this object to pass into the function.
it('should log in, perform x transfers, delete y transfer, update z transfer'), function() {
var transfersDisplayed = helper.transferJourney(td.multiTransfer);
expect(transfersDisplayed).toEqual(n);
});
This keeps the checks simple and readable but abstracts both data and functions so should you need to update the data or flow, then that is possible whilst keeping the check valid.
If you need a starting point for Protractor, then I highly recommend reading Hannah's articles on Protractor for Beginners, Part 1 and Part 2