使用 Protractor 测试 AngularJS - 测试失败,因为尽管等待 elementToBeClickable,但元素不可点击...?

Testing AngularJS with Protractor- test fails because element is not clickable despite waiting for elementToBeClickable...?

我在使用 Protractor 为 AngularJS 应用程序开发的测试套件中编写其中一个测试时遇到了一个奇怪的问题。

在最初开发测试时,我使用了对 browser.pause() 的调用以确保在 运行 测试脚本时我必须手动告诉测试执行每一行。我现在很高兴到目前为止我编写的测试都正确执行并且所有测试都根据他们的标准正确通过/失败。

我现在想删除对 browser.pause() 的调用,以便脚本可以 运行 自动通过每个测试,而无需任何用户交互。然而,因为应用程序是 Angular,这样做会导致一些时间问题/在执行引用它们的测试步骤之前等待元素加载 - 所以我目前正在经历每个测试,并试图确保他们允许元素加载所需的时间/在与元素交互之前等待元素等。

当我 运行 我的测试脚本时,它在第二次测试(导航到导出页面)时失败了,我写的如下:

it('should navigate to the Export page', function() {
    browser.waitForAngularEnabled(false);
    browser.wait(EC.elementToBeClickable(exportMenuBtn), 25000).then(
        browser.actions().mouseMove(exportMenuBtn).perform().then(
            exportMenuBtn.click().then(
                function() {
                    console.log("Export page test completed ");
                    expect(browser.getCurrentUrl()).toBe(VM + '/#/export');
                }
            )
        )
    );
});

我在 运行 运行我的脚本的命令行中收到的错误消息说:

Failed: unknown error: Element ... is not clickable at point (40, 301). Other element would receive the click:

<div tabindex="-1" role="dialog" class="modal fade ng-isolate-scope ult-loading modal-message ng-animate in-remove in-remove-active" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)" modal-window="" window-class="ult-loading modal-message" size="sm" index="0" animate="animate" style="z-index: 1050; display: block;; transition-property: opacity;transition-duration: 0.15s;">...</div>

(Session info: chrome=62.0.3202.89) (Driver info: chromedriver=2.33.506120 (e3e53437346286c0bc2d2dc9aa4915ba81d9023f),platform=Windows NT 10.0.16299 x86_64)

这向我表明 exportMenuBtn 元素在我的测试脚本试图对其调用 .click() 时不是 'clickable'。但这没有意义,因为我已经使用行 browser.wait(EC.elementToBeClickable(exportMenuBtn), 25000) 等待 exportMenuBtn 元素可点击,然后再尝试点击它...

我试过增加等待元素可点击的时间,但这似乎没有什么不同...

谁能解释为什么这个测试失败了?如何确保测试在尝试单击之前等待 exportMenuBtn 元素可单击?

编辑

所以我尝试按照@Ernst Zwingli 在他们的回答中的建议进行操作 - 添加了一个函数来关闭我的 spec.js 文件的对话框:

var pageLoaded = function() {
    var blockingElement = $('div[window-class="ult-loading modal-message"');
    return EC.invisibilityOf(blockingElement);
}
module.exports = new pageLoaded();

然后将测试脚本更新为:

it('should navigate to the Export page', function() {
    browser.waitForAngularEnabled(false);
    browser.wait(pageLoaded, 5000);
    exportMenuBtn.click();
    browser.wait(pageLoaded, 5000);
    browser.wait(EC.urlIs(VM + '/#/export'), 5000);
});

但我的测试脚本仍然失败,给出相同的错误信息:

Failed: unknown error: Element

<a href="#/export" target="_self">...</a> 

is not clickable at point (40, 301).

Other element would receive the click:

<div tabindex="-1" role="dialog" class="modal fade ng-isolate-scope ult-loading modal-message ng-animate in-remove in-remove-active" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)" modal-window="" window-class="ult-loading modal-message" size="sm" index="0" animate="animate" style="z-index: 1050; display: block;; transition-property: opacity;transition-duration: 0.15s;">...</div>
 (Session info: chrome=62.0.3202.89)
  (Driver info: chromedriver=2.33.506120 (e3e53437346286c0bc2d2dc9aa4915ba81d9023f),platform=Windows NT 10.0.16299 x86_64)

似乎导致问题的对话框在 Dialog/service.js 中定义为:

function wait(msg, progress, title) {
    // Create a wait dialog
    var dlg = dialogs.wait(
        title,
        msg,
        progress,
        {
            // Disable keyboard ESC to close dialogue instances
            keyboard: false,

            // lock the background window
            backdrop: "static",
            size: "sm",
            windowClass: "ult-loading modal-message",
            animation: false
        }
    );

    // Push the dialog into the pool
    dlgs.push(dlg);

    // Return the created dialog
    return dlg;
}

当 运行手动测试/浏览页面时,这个对话框实际上是不可见的,所以它似乎是某种 'background task',即浏览到 运行 / 加载新页面。我还应该注意,此对话框是在 factory:

中定义的
.factory('DialogMgr', function($rootScope, $document, dialogs) {
    ...
    function wait(...) {
        ...
    }
});

我想知道这是否与对话框中设置的 'fade' 动画有关?我该如何解决?

这是

怎么不满足你的问题?

我添加了(尽管有注释)行 browser.wait(EC.invisibilityOf(blockingElement), 5000); 作为您问题的答案。

此刻,一个元素isClickable并不自动意味着,它也在前面并且没有其他对象覆盖它。这只是意味着,该对象可能会收到一个 click(),因为...

element is visible and enabled such that you can click it

Read more about here。您甚至可以检查源代码。

更新

鉴于您的评论,您在页面加载时遇到了一个对话框弹出窗口阻止输入。因此,您可以创建自己的 ExpectedConditions 函数,它会一直等到阻塞对话框消失。

这里是要构建的可重复使用的函数,基于这样的假设,即这个阻塞元素会出现在很多地方:

var loaded = function(){
    var EC = protractor.ExpectedConditions;
    var blockerElm = $('div[window-class="ult-loading"');
    return EC.invisibilityOf(blockerElm);
}
module.exports = new loaded();

以下是如何在您的测试用例中调用它:

var loaded = require(pathToLoaded.js);
it('should navigate to the Charts page', function() {
    console.log("Start Charts page test");
    browser.waitForAngularEnabled(false);
    browser.wait(loaded, 5000);
    chartsMenuBtn.click(); //works, if the element is defined as you said
    browser.wait(EC.urlIs(site + '/#/charts'), 5000);
})

虽然我知道它不被考虑 'best practice',但我真正能够解决这个问题的唯一方法是在调用 browser.click() 之前插入一个 browser.sleep() 行:

it('should navigate to the Export page', function() {
    browser.waitForAngularEnabled(false);
    browser.wait(pageLoaded, 5000);
    browser.sleep(500);
    exportMenuBtn.click();
    browser.wait(EC.urlIs(site + '/#/export'), 5000);
});

使用 Ernst Zwingli 在他们的回答中提供的 pageLoaded() 功能:

var pageLoaded = function() {
    var blockingElement = $('div[window-class="ult-loading modal-message"');
    //console.log("blockingElement: ", blockingElement);
    return EC.invisibilityOf(blockingElement);
}
module.exports = new pageLoaded();

没有调用 browser.sleep(500);,页面没有时间在页面加载时在后台打开和关闭的对话框上完成动画 - 这意味着对话框收到了点击,而不是我想要的菜单按钮。