网站搜索

html - 我如何用 Protractor 返回这个复选框元素?


我正在编写一个 Typescript/Protractor 测试,需要评估并选择网格上的复选框。这些复选框没有 ID。由于我可以获取与这些复选框同一行的输入元素的句柄,我正尝试通过组合元素查找器(by.id、by.xpath 和 by.css)来获取它们。但我的尝试都失败了。

我想要编写的是一个小函数,它能够根据行上的一个远端“兄弟”元素和行上复选框的索引,返回该行的目标复选框元素。我已经尝试了几种方法,我认为最有希望的尝试已经贴在这里。

这是我正在针对的网格中的一部分标记。为了清晰起见,省略了几列信息。这就是我所面对的情况。我无法更改标记,我只被要求编写 Protractor 测试。

<div ng-repeat="(rowRenderIndex, row) in rowContainer.renderedRows track by $index" class="ui-grid-row ng-scope" ng-style="Viewport.rowStyle(rowRenderIndex)">
    <div ui-grid-row="row" row-render-index="rowRenderIndex" class="ng-isolate-scope">
        <div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.uid" class="ui-grid-cell ng-scope ui-grid-coluiGrid-09T" ng-class="{ 'ui-grid-row-header-cell': col.isRowHeader }" ui-grid-cell="">
            <div ng-click="grid.appScope.rowColumnSelect(row, 'rating')" ng-class="{'px-grid-row-selected': grid.appScope.highlightCell(row, 'rating'), 'px-can-edit': grid.appScope.clickableCell(row.entity, 'rating'), 'px-grid-total-row': row.entity.isTotal, 'px-grid-notable-row': row.entity.isNotable}" class="ng-scope">
                <div class="ui-grid-cell-contents ng-hide" ng-hide="grid.appScope.showCell(row, 'rating')">
                </div>
                <div ng-show="grid.appScope.showCell(row, 'rating')" name="loanLossAssumption3" class="ui-grid-cell-contents">
                    <div ng-if="grid.appScope.haveData(row)" class="text-right ng-scope">
                        <div error-display-strategy="grid" px-input-field="" id="loanLossAssumptionrating3" name="loanLossAssumptionrating3" ng-if="grid.appScope.inEditMode(row, 'rating')" input-type="number" px-input-required="true" px-round-value="0" ng-model="row.entity.rating" class="ng-pristine ng-untouched ng-valid ng-scope ng-isolate-scope">
                            <div ng-class="{'row': !vm.isGridErrorDisplay &amp;&amp; !vm.autoWidth}">
                                <div>
                                    <div tooltip="" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip-trigger="open" class="ng-scope">
                                        <div px-validation="loanLossAssumptionrating3InputField" ng-class="{'col-xs-12': !vm.isGridErrorDisplay &amp;&amp; !vm.autoWidth}" min="" max="" input-type="number" maxlength="" minlength="" error-display-strategy="grid" px-tooltip="" class="ng-isolate-scope">
                                            <div class="row">
                                                <div ng-class="vm.inputFieldClass()" class="col-xs-12">
                                                    <div class="input-group px-input-group-overrides px-no-grid-padding" ng-hide="vm.pxInputDisabled">
                                                        <span id="loanLossAssumptionrating3Prefix" name="loanLossAssumptionrating3Prefix" class="input-group-addon input-left-addon ng-binding ng-hide" ng-show="vm.prefix" ng-class="{'px-auto-width': vm.autoWidth}">

                                                        </span>
                                                        <div class="inner-addon">
                                                            <input id="loanLossAssumptionrating3InputField" name="loanLossAssumptionrating3InputField" class="form-control ng-pristine ng-untouched ng-valid ng-valid-px-email ng-valid-required ng-valid-unique" ng-class="{'px-input-with-tooltip': !!vm.pxTooltip, 'px-auto-width': vm.autoWidth}" ng-required="vm.pxInputRequired" px-round-value="0" px-edit-rate="false" type="text" ng-model="vm.ngModel" px-email-validator="" ng-disabled="vm.pxControlDisabled" placeholder="" ng-change="vm.onChange()" ng-focus="vm.showTooltip(true)" ng-blur="vm.showTooltip(false)" px-unique-field="" autocomplete="off" required="required">
                                                        </div>

                                                        <span class="fa fa-exclamation-circle inline-error form-control-feedback" ng-style="{left:vm.feedbackIconLeft()}" style="left: 0px;">
                                                        </span>


                                                        <span class="input-group-addon input-empty-addon" ng-hide="vm.postfix" ng-class="{'px-auto-width': vm.autoWidth}">
                                                        </span>
                                                    </div>

                                                </div>
                                                <span ng-transclude="" class="px-vertical-middle">
                                                </span>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div px-tooltip-view="" px-tooltip="vm.pxTooltip" ng-click="vm.applyAdjustment()" class="ng-isolate-scope">
                                </div>
                            </div>
                        </div>
                        <span ng-hide="grid.appScope.inEditMode(row, 'rating')" ng-class="{'px-font-highlighted': grid.appScope.showHighlighted(row, 'rating'), 'px-font-enabled': grid.appScope.showEnabled(row, 'rating'), 'px-font-disabled': grid.appScope.showDisabled(row, 'rating'), 'px-cannot-edit': !grid.appScope.clickableCell(row.entity, 'rating')}" class="ng-binding px-font-enabled px-cannot-edit ng-hide">3</span>
                    </div>
                </div>
            </div>
        </div>
        <div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.uid"
                class="ui-grid-cell ng-scope ui-grid-coluiGrid-09Y" ng-class="{ 'ui-grid-row-header-cell': col.isRowHeader }" ui-grid-cell="">
            <div ng-click="grid.appScope.rowColumnSelect(row, 'isDefault')"
                    ng-class="{'px-grid-row-selected': grid.appScope.highlightCell(row, 'isDefault'), 'px-can-edit': grid.appScope.clickableCell(row.entity, 'isDefault'), 'px-grid-total-row': row.entity.isTotal, 'px-grid-notable-row': row.entity.isNotable}"
                    class="ng-scope">
                <div class="ui-grid-cell-contents ng-hide" ng-hide="grid.appScope.showCell(row, 'isDefault')">
                </div>
                <div ng-show="grid.appScope.showCell(row, 'isDefault')" name="loanLossAssumptionfalse" class="ui-grid-cell-contents"> <!-- Changes to loanLossAssumptiontrue-->
                    <div ng-if="grid.appScope.haveData(row)" class="text-center ng-scope">
                        <input ng-disabled="!grid.appScope.inEditMode(row, 'isDefault')" type="checkbox" ng-input="row.entity.isDefault" ng-model="row.entity.isDefault"
                                class="ng-pristine ng-valid ng-touched">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

在这种情况下,我已经获得了一个 ID 为 loanLossAssumptionrating3InputField 的输入字段的句柄。从这里,我需要从这个 ui-grid-row 中获取其他复选框。在这个例子中,一个复选框出现在标记的末尾附近,在 6 个闭合的 div 标签之前。

随着时间的推移,我们构建了一个函数库,所以在这段代码中有对“lib.”的引用。这只是对我们库的一个工作引用。这段代码位于一个 pageObject 中。

lib.getCheckboxInRow(fields.ratingFieldId, 0).then(function (checkboxElement) {
   // The fields.ratingFieldId value = "loanLossAssumptionrating3InputField"
   checkboxElement.click();
});

这只是一次“点击”尝试,但我还需要获取该元素并确定复选框是否被点击。在我的情况下,通过标记语言实现这一点的唯一方法是读取父元素的一个属性。我知道如何做到这一点,但提供这个细节是为了说明我为什么尝试返回元素本身。

这是我的目标函数。下面显示了两次尝试。这两次都失败了。这是在我的库中。

// Attempt A
public getCheckboxInRow(siblingElementId: string, checkboxIndex: number): webdriver.promise.Promise<protractor.ElementFinder> {
    var xPathToRow : string = "./ancestor::div[contains(concat(' ', @class, ' '), ' ui-grid-row ')][1]";
    return element(by.id(siblingElementId)).element(by.xpath(xPathToRow)).then(function (row) {
        return row.all(by.css("input[@type='checkbox']")).filter.then(function (checkboxArray) {
            return checkboxArray[checkboxIndex];
        });
    });
}

// Attempt B
public getCheckboxInRow(siblingElementId: string, checkboxIndex: number): webdriver.promise.Promise<protractor.ElementFinder> {
    var xPathToRow : string = "./ancestor::div[contains(concat(' ', @class, ' '), ' ui-grid-row ')][1]";
    var cssToCheckbox : string = "input[@type='checkbox']";
    return element(by.id(siblingElementId)).element(by.xpath(xPathToRow)).all(by.css(cssToCheckbox)).filter(function(checkbox) {
        return true;
    }).then(function (checkboxArray) {
        return checkboxArray[checkboxIndex];
    });
}

使用“尝试 A”时发生以下错误。

类型错误:element(...).element(...).then 不是一个函数

使用“尝试 B”时错误更加明显。

InvalidSelectorError:无效选择器:指定了无效或非法的选择器(会话信息:chrome=47.0.2526.111)(驱动程序信息:chromedriver=2.19.346078(6f1f0cde889532d48ce8242342d0b84f94b114a1),平台=Windows NT 6.3 x86_64)(警告:服务器未提供任何堆栈跟踪信息)命令持续时间或超时:30 毫秒。有关此错误的文档,请访问:http://seleniumhq.org/exceptions/invalid_selector_exception.html 构建信息:版本:'2.47.1',修订:'411b314',时间:'2015-07-30 03:03:16' 系统信息:主机:'PSIDEVKPALM',IP:'10.226.128.27',操作系统名称:'Windows Server 2012 R2',操作系统架构:'x86',操作系统版本:'6.3',Java 版本:'1. 8.0_66 驱动信息: org.openqa.selenium.chrome.ChromeDriver 功能 [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=false, chrome={userDataDir=C:\Users\kpalmer\AppData\Local\Temp\2\scoped_dir7352_8503}, takesHeapSnapshot=true, databaseEnabled=false, handlesAlerts=true, hasTouchScreen=false, version=47.0.2526.111, platform=WIN8_1, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true}] 会话 ID: 12959162beeed374185734ef29b03b57 *** 元素信息: {使用=css 选择器, 值=input[@type='checkbox']}

在这上面花了很多小时后,我不明白为什么会失败。

最终,我想要一个函数,它将返回一个复选框元素,该复选框元素基于复选框索引位于该行中另一个元素的同一行上,这样我们就可以点击复选框或确定该元素是已选中还是未选中。

谢谢你的帮助。

=== 2016/1/2 下午编辑 ===

谢谢你 @alecxe,你帮了大忙。这是我采用的最终实现方法。这两种方法都可行。

这两者中的任何一个在我的页面对象中都可以使用。

lib.getCheckboxInRow(fields.ratingFieldId, 0).click();

lib.getCheckboxArrayInRow(fields.ratingFieldId).filter(function (checkboxArray, index) {
    return (index === 0);
}).first().click();

这些是库中针对的功能。我必须调整的一件事是返回类型,从 Promise 改为元素查找器。这些工作得非常好。

public getCheckboxInRow(siblingElementId: string, checkboxIndex: number): protractor.ElementFinder {
    var xPathToRow : string = "./ancestor::div[contains(concat(' ', @class, ' '), ' ui-grid-row ')][1]";
    return element(by.id(siblingElementId)).element(by.xpath(xPathToRow)).all(by.css("input[type=checkbox]")).filter(function (checkboxArray, index) {
        return index === checkboxIndex;
    }).first();
};

public getCheckboxArrayInRow(siblingElementId: string): protractor.ElementArrayFinder {
    var xPathToRow : string = "./ancestor::div[contains(concat(' ', @class, ' '), ' ui-grid-row ')][1]";
    return element(by.id(siblingElementId)).element(by.xpath(xPathToRow)).all(by.css("input[type=checkbox]"));
};

答案#1

让我们回顾一下你的尝试。

  • Attempt A

你收到 TypeError: element(...).element(...).then is not a function 错误,因为自 Protractor 2.0.0(更新日志)起,你不能使用 then() 来解析 ElementFinder。

在这种情况下,你可以继续链式调用 element 和 all():

public getCheckboxInRow(siblingElementId: string, checkboxIndex: number): webdriver.promise.Promise<protractor.ElementFinder> {
    var xPathToRow : string = "./ancestor::div[contains(concat(' ', @class, ' '), ' ui-grid-row ')][1]";
    return element(by.id(siblingElementId)).element(by.xpath(xPathToRow)).all(by.css("input[type=checkbox]")).filter(function (checkboxArray, index) {
        return index === checkboxIndex;
    }).first();
}

我也修正了你使用 filter() 函数的方式。并注意 first() 调用——这将帮助我们获取单个过滤后的元素,而不是一个数组。

  • Attempt B

你收到 An invalid or illegal selector was specified 错误,是因为你实际上使用了一个无效的 CSS 选择器 input[@type='checkbox']——在 CSS 中不需要在属性名之前加 @ ——将其替换为 input[type=checkbox](也不需要单引号)。

而且,你在这次尝试中也没有正确使用filter()函数。修正版本:

public getCheckboxInRow(siblingElementId: string, checkboxIndex: number): webdriver.promise.Promise<protractor.ElementFinder> {
    var xPathToRow : string = "./ancestor::div[contains(concat(' ', @class, ' '), ' ui-grid-row ')][1]";
    var cssToCheckbox : string = "input[type=checkbox]";
    return element(by.id(siblingElementId)).element(by.xpath(xPathToRow)).all(by.css(cssToCheckbox)).filter(function(checkbox, index) {
        return checkboxIndex === index;
    }).first();
}

这本质上变得和尝试 A 相同。