I am using jasmine/protractor to run test suites against multiple sites that my company manages. (The sites are order form/checkout sites) Currently, I have a separate test suite set up for each site that uses functions I've created and stored inside of a helper to fill out each form so that I can just call those functions in my specs, and run through the order process to place a test order, this currently works and calls to the helpers go through without issue.
Protractor: v5.1.2
Jasmine: v2.6
Node: v8.0.0
The trouble here is that each site tends to use any number of field identifiers for each field in a given form, and I have ended up writing a specific function for a specific form on a particular site. With each site using anywhere between 2 and 4 forms, the end result is hundreds of the same functions being repeated with only difference in the selector. So I am trying to refactor my code so that I can just have one function for each form used on every site.
Enter my issue: I am having trouble figuring out how to make my tests check the value of the elements loading on the page against a list of possible elements. basically what I need it to do is this:
What I have:
My Spec:
This is the actual spec in my test file.
it('Checks for fieldName and then fills in the form', function(done) {
cQualify();
done();
});
cQualify function:
The function is stored in a helper called formFill.js.
cQualify = function() {
findElementByFieldName(cartData.fName).sendKeys('Jimmy');
findElementByFieldName(cartData.cGender).sendKeys('Male');
findElementByFieldName(cartData.cAge).sendKeys('34');
findElementByFieldName(cartData.cZip).sendKeys('33071');
//more fields here and a submit button
};
findElementByFieldName function:
This function is stored in a helper called arrayLoop.js and is my latest attempt to make this work. Originally this was more along the lines of:
browser.driver.findElement(by.name('someName')).sendKeys('nameToSend');
//repeated for each field on the form
Here is the function:
findElementByFieldName = function(fieldName) {
if (fieldName.constructor === Array) {
console.log('Array found, looping values for array: ' + fieldName);
for(var i=0; i < fieldName.length; i++) {
expect(element(by.name(fieldName[i])).isDisplayed()).toBe(true);
console.log('Checking if page element ' + fieldName[i] + ' exists');
}
//some code that checks if the current value of fieldName[i] is in the page DOM
//and if so; returns that and exits the loop
} else {
return browser.driver.findElement(by.name(fieldName));
}
}
List of possible elements:
The possible elements are stored inside of a helper called formData.js (Note: only those with multiple possible values are in an array; the others I am not having trouble with)
cartData = {
fName: ['cShipFname', 'zang_fname', 'fname'],
lName: ['cShipLname', 'zang_lname', 'lname'],
cZip: ['cShipZip', 'zang_zip', 'zip'],
cGender: 'zang_gender',
cAge: 'zang_age',
cProblem: 'zang_problem'
//lots of additional values here
};
Result:
When I run this as is, the test loops through all values contained within cartData.fName, considers them all displayed, and then fails when trying to sendKeys with:
Failed: Cannot read property 'sendKeys' of undefined
So here is where I am stuck. Not only do I need the loop to check if the value from the array is on the page, I need it to stop looping once a match is found and return that so that I can use it the way it's laid out in the cQualify() function. I've tried a few different things such as using isDisplayed() inside of an if, but it seems this can only be used with expect. I've also tried putting my spec inside of a function and then looping that function directly in the test- but this had similar results and would also defeat the purpose of formFill.js
Update:
I found another question on SO that handles something similar: here
The code from the accepted answer is:
var link = element.all(by.css('a')).reduce(function (result, elem, index) {
if(result) return result;
return elem.getText().then(function(text){
if(text === "mylink") return elem;
});
}).then(function(result){
if(!result) throw new Error("Element not found");
return result;
});
Though I am not able to figure out (so far) how I might adapt it to suit my needs.
Instead of making it more complex, you can simply construct a dynamic XPath expression with multiple or conditions. Look at below example code.
function getElement(nameList) {
if(nameList.constructor != Array){
nameList=[nameList]
}
var xpathExpression = ".//*["
nameList.forEach(function(name,index){
xpathExpression += "@name='"+name+"'";
if(index != nameList.length-1){
xpathExpression+=" or ";
} else {
xpathExpression+= "]";
}
});
return element(by.xpath(xpathExpression));
}
So if you want to find the element for fName: ['cShipFname', 'zang_fname', 'fname']
, you can simply call getElement
and it will return you the web element based on the matched XPath expression.The XPath expression for fname
is,
.//*[@name='cShipFname' or @name='zang_fname' or @name='fname']