简体   繁体   中英

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.

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

Though it's answered, I'm not really a fan of XPath. Therefore I'd like to provide an alternative approach using filter() and indexOf() or includes() .

First with using indexOf()

function getElement(nameList) {
    //keeping it an array for all calls (thumbs up for this thought of @SudharsanSevaraj)
    if(nameList.constructor != Array){
        nameList=[nameList]
    }
    //selecting all elements with a name-attribute
    //then filter the one(s), that is/are present in nameList and use first
    return $$('[name]').filter(function(elem, index){
        return elem.getAttribute('name').then(function(elName){
            //return the element, if its name-attribute is present in nameList
            return nameList.indexOf('elName') !== -1
        });
    }).first();
};

And the same slightly different, using includes() this time.

function getElement(nameList) {
    //keeping it an array for all calls (thumbs up for this thought of @SudharsanSevaraj)
    if(nameList.constructor != Array){
        nameList=[nameList]
    }
    //selecting all elements with a name-attribute, 
    //then filter the one(s), that is/are present in nameList and use first
    return $$('[name]').filter(function(elem, index){
        return elem.getAttribute('name').then(function(elName){
            return nameList.includes(elName);
        });
    }).first();
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM