检查数组中的任何值是否与 dom 中的元素匹配,然后将键发送到量角器中的该元素

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

我正在使用 jasmine/protractor 到 运行 测试套件针对我​​公司管理的多个站点。 (这些网站是订单 form/checkout 网站)目前,我为每个网站设置了一个单独的测试套件,它使用我创建并存储在助手内部的功能来填写每个表格,这样我就可以调用那些我的规格中的功能,并且 运行 通过订单流程来下测试订单,这目前有效并且对助手的调用没有问题。

量角器: v5.1.2
茉莉: v2.6
节点:v8.0.0

这里的问题是每个站点都倾向于为给定表单中的每个字段使用任意数量的字段标识符,而我最终为特定站点上的特定表单编写了特定函数。每个站点使用 2 到 4 个表单,最终结果是数百个相同的功能被重复,只是选择器不同。所以我正在尝试重构我的代码,以便我可以为每个站点上使用的每种表单只提供一个功能。

输入我的问题:我无法弄清楚如何让我的测试根据可能的元素列表检查页面上加载的元素的值。基本上我需要它做的是:

我有:

我的规格:

这是我测试文件中的实际规格。

    it('Checks for fieldName and then fills in the form', function(done) {
        cQualify();
        done();
    });

c限定函数:

该函数存储在名为 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
};

通过字段名查找元素函数:

此函数存储在一个名为 arrayLoop.js 的帮助程序中,这是我最近的一次尝试。最初这更像是:

    browser.driver.findElement(by.name('someName')).sendKeys('nameToSend');
//repeated for each field on the form

函数如下:

    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));
        }
    }

可能的元素列表:

可能的元素存储在一个名为 formData.js 的帮助程序中(注意:只有那些具有多个可能值的元素在数组中;其他我没有遇到麻烦)

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
};

结果:

当我按原样 运行 时,测试循环遍历 cartData.fName 中包含的所有值,认为它们都已显示,然后在尝试发送密钥时失败:

Failed: Cannot read property 'sendKeys' of undefined

这就是我被困的地方。我不仅需要循环来检查数组中的值是否在页面上,我还需要它在找到匹配项和 return 后停止循环,以便我可以按照它的布局方式使用它cQualify() 函数。我已经尝试了一些不同的事情,例如在 if 中使用 isDisplayed(),但似乎这只能与 expect 一起使用。我也试过将我的规范放在一个函数中,然后直接在测试中循环该函数 - 但这有类似的结果并且也会破坏 formFill.js

的目的

更新:

我在 SO 上发现了另一个处理类似问题的问题:
已接受答案的代码是:

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;
});

虽然我无法弄清楚(到目前为止)如何调整它以满足我的需要。

您可以简单地构造一个具有多个 or 条件的动态 XPath 表达式,而不是让它变得更复杂。请看下面的示例代码。

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));
}

所以如果你想找到 fName: ['cShipFname', 'zang_fname', 'fname'] 的元素,你可以简单地调用 getElement 它会 return 你基于匹配的 XPath expression.The 的网络元素] fname 的 XPath 表达式是,

.//*[@name='cShipFname' or @name='zang_fname' or @name='fname']

虽然已经回答了,但我并不是 XPath 的忠实粉丝。因此,我想提供一种使用 filter()indexOf()includes().

的替代方法

首先使用 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();
};

同样略有不同,这次使用includes()

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();
};