Search code examples
javascriptarraysprotractorjasmine-node

Check if any values in an array match an element in the dom and then sendkeys to that element in protractor


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:

  • Open page
  • check element(by.name('someName') against the list I have
  • once a match is found, use the match as the value for someName
  • sendKeys('someValue') to that value
  • repeat for all fields on the form

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.


Solution

  • 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']