Protractor: 避免等待$ timeout()s?

创建于 2013-10-16  ·  59评论  ·  资料来源: angular/protractor

在我们的应用程序中,单击某些按钮后,我们会向用户显示“弹出”消息。 这些弹出消息仅可见约20秒钟(此后$ timeout会将其删除)。

问题在于,量角器似乎要等到$ timeout完成之后,才能执行.findElement()和.expect()的操作。 但是,当$ timeout为_timed out_时,该消息消失了,并且找不到该元素。

关于如何绕过/解决此问题的任何建议? (我们需要断言向用户显示正确的“弹出”消息)

谢谢

question

最有用的评论

Angular团队是否计划重新开放此问题,因为越来越多的人遇到此问题。 只是假装没有问题是不专业的,因为您可以将$ timeout替换为$ interval,因为不应编写适合测试的代码-应该将测试写入测试代码。

所有59条评论

您可以使用ptor.ignoreSynchronization ,但这可能会在测试中的其他地方导致片状。 另外,在Angular 1.2rc3上,现在有一个interval服务,量角器将不等待(请参见https://github.com/angular/angular.js/commit/2b5ce84fca7b41fca24707e163ec6af84bc12e83)。 您可以设置一个间隔,该间隔将重复1次。

@drhumlen ,您好,我们也遇到了同样的问题,我们得出结论,弹出窗口绝对是应该用于单元测试的东西。 因此,我们对通知系统进行了模拟,并期望该模拟已被调用(最终带有一些关键信息)。 我们还对该通知进行了单元测试,以确保没有任何中断。
希望对您有所帮助。

$interval帮了我们大忙。

  $interval(function() {
    // ...
  }, duration, 1);

现在效果很好。 但是我们必须非常谨慎,并确保在每次测试后都以其他方式删除弹出窗口(在我们的例子中,单击“ x”按钮):

  afterEach(function() {
    removeLogMessagesFromDOM();
  });

@mackwic :我想可以对许多应用程序进行单元测试来测试它,但是在我们的例子中,错误消息是由后端生成的,因此我们必须在e2e中对其进行测试。

谢谢@juliemr

量角器等待$timeout s似乎很奇怪。 大多数时候,用户将不得不对此进行应对吗?

还面临这个问题,并用$interval解决了它。 稍微扭曲一下,就可以像$timeout一样使用它。

我想测试保存方法并验证保存后是否显示成功通知。 $ timeout服务已隐藏此通知。 量角器正在等待$ timeout,并检查通知是否已隐藏后是否可见。

之前:

$timeout(function() {
    //hide notification
}, 3000);

后:

var intervalCanceller = $interval(function() {
    //hide notification
    $interval.cancel(intervalCanceller);
}, 3000);

这不是有效的问题吗? 我自己碰到了这个。 我真的看不到使用$interval代替$timeout是一个解决方案,更多的解决方法。

即使我从$timeout更改为$interval ,我也无法使用它。

我的烤面包在超时后消失了。 这是我用来测试的代码:

username = element(select.model("user.username"))
password = element(select.model("user.password"))
loginButton = $('button[type=\'submit\']')
toast = $('.toaster')
# Check the toaster isn't displayed initially
expect(toast.isDisplayed()).toBe(false)  # This passes
username.sendKeys("redders")
password.sendKeys("wrongpassword")
loginButton.click()
toastMessage = $('toast-message')
# Check the toaster is now displayed
expect(toast.isDisplayed()).toBe(true)   # This passes
expect(toastMessage.getText()).toBe("Invalid password")  # This fails

相关标记在这里(玉器)

.toaster(ng-show="messages.length")
  .toast-message(ng-repeat="message in messages") {{message.body}}

失败是toastMessage = $('toast-message')与任何元素都不匹配。 这是不是因为.toast-message不存在而在一开始就被调用了?

这个问题解决了吗? 因为使用间隔而不是超时比解决方案更能破解或提高工作效率。

我遇到同样的问题,并且browser.ignoreSynchronization = true; 没有帮助。 我可以将代码替换为$ interval,但是e2e测试应该测试真实代码,而不是测试采用的代码。

@ vytautas-pranskunas-听起来好像如果ignoreSynchronization无法正常工作对您来说是一个不同的问题。 如果您可以提供可复制的示例,请打开一个新的期刊。

我弄清楚了何时ignoreSynchronization无法正常工作:
我有一个负责显示错误消息的服务。 节目消失后,一旦显示消息停留在屏幕上五秒钟。 此过程由$ timeout控制。

我有两种情况-第一种ignoreSynchronization可以正常工作,第二种-不能

1)我知道消息应该在用户单击按钮后立即出现,在这种情况下,我会执行expec(message).tobe(...) ,而ignoreSynchronization会完成任务。

2)我知道消息可以在任何时间后出现(取决于服务器响应时间),并且我的进一步逻辑应仅在消息出现后才执行,因此在这种情况下,我必须在这里使用browser.wait(by.(....).isPresent(), n) ignoreSynchronization停止工作,流程如下(在等待时,我将显示browser.wait console.log输出)

waits
waits
waits (element appears)
browser waits freezes for 5 seconds
after element disappears it continue
wait
wait
wait

因此,如您所见,它永远不会看到元素存在,因为与$ timeout联系时等待会冻结

我确实没有太多时间来使它在plunker中可重现,但是如果您尝试这样做,那将是很好的,因为这种情况应该很普遍。

恶作剧

如果没有创建日志的代码上下文,您的日志确实不是很有帮助。 您能分享一下吗?

由于隐私原因,我无法共享我的代码,但是我可以显示一些伪代码来获得想法
(不编写HTML代码,因为有一个用于显示消息的按钮和ng-repeat)

constructor(){
$scope.$on('globalMessageAdded', () => {
            $scope.globalMessages = this.getMessages();
        });
}

callServer(){
//interval here acts like server delay
   $interval(()=> {
     $rootScope.messages.push('test messages');
      this.$rootScope.$broadcast('globalMessageAdded');
   }, 3000, 1);
}

getMessages = () => {
        var newMessages = <ISpaGlobalMessage[]>[];
        _.forEach(this.$rootScope.globalMessages, item => {
            newMessages.push(item);
//because of this timeout browser.wait stops waitng for 5 seconds
            this.$timeout(() => {
                this.remove(item);
                this.$rootScope.$broadcast('globalMessageRemoved');
            }, 5000);
        });

        return newMessages;
    }

和量角器部分

element('button').click();
browser.ignoreSynchronization = true; //tried with and without it
browser.wait(() => element(by.css('.alert-success')).isPresent(), 10000000);
browser.ignoreSynchronization = false;

other code that should be executed after successful message

browser.wait永远不会找到该元素,因为当元素存在时,browse.wait不会触发检查。

有什么进展或想法吗?

为什么不能在browser.wait()之后立即设置“ browser.ignoreSynchronization = false;”?

我的密码

        var el = element(by.css('.popup-title.ng-binding'));
        browser.ignoreSynchronization = true; 
        browser.wait(function (){
            return el.isPresent();
        }, 10000);
        var popupMsg = el.getText();
        expect(popupMsg).toEqual('something...');
        browser.ignoreSynchronization = false; // not work, if take out this statement, it works again

被同一个问题咬伤。 我们使用的toast模块是npm的通用组件,因此我们无法轻松地将其更改为使用$ interval。 我也看不出为什么量角器应该等待一个而不是另一个。 理想情况下,这将是可以自行配置的,因此它不会影响其他同步等待。

我们在angular-ui / bootstrap#3982遇到了这个问题,我们决定不将其交换为$ interval,所以我们来这里看看是否有什么办法可以解决此问题。

非常感谢你。

我本周使用烤面包通知组件解决了这个问题。

使用$interval代替$timeout不是一个可行的解决方案。 browser.ignoreSynchronization = true也不适合我。

有什么想法或想法吗?

Angular团队是否计划重新开放此问题,因为越来越多的人遇到此问题。 只是假装没有问题是不专业的,因为您可以将$ timeout替换为$ interval,因为不应编写适合测试的代码-应该将测试写入测试代码。

+1我们也需要修复。

同意测试应该是固定的。 +1

+1,也遇到了这个问题。 也许应该有一种方法可以告诉量角器超时不应该等待。

ignoreSynchronization对我不起作用,我认为它忽略了太多的事情。 我们需要一种忽略超时同步的方法,或者使用更复杂的API,例如browser.waitForTimeouts();

+1。 需要修复。 为什么我们要使用带有间隔的难看的解决方法。

+1

+1,发现很难处理易碎的测试。.为此需要修复

+1同意@ vytautas-pranskunas-我们不应该只为了使测试本身起作用而更改我们要测试的代码。

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

我认为许多人可能以错误的方式使用browser.ignoreSynchronization = true 。 如果我错了,请纠正我,但如果您写错,请纠正我:

browser.ignoreSynchronization = true;
expect( elem.getText() ).toEqual( "foo" );
browser.ignoreSynchronization = false;

然后它将不起作用:这仍然是纯JavaScript,并且将同步读取三行。 因此,当getTextexpect许诺解决时,同步将已重新打开。 您可以通过等待量角器解决最后的诺言并在其then回调中恢复同步来解决此问题。

量角器团队帮助人们的一种方式可能是提供对方法的访问,这些方法仅在promise缓冲区完成后才影响同步。 例如:

browser.setIgnoreSynchronization( true );
expect( elem.getText() ).toEqual( "foo" );
browser.setIgnoreSynchronization( false );

无论如何,由于量角器等待$超时,我也遇到了很多麻烦:在大型应用程序中,副作用太多,包括一些工具提示,模态,动画等。我们决定全局禁用与browser.ignoreSynchronization = true;同步

使用$ interval +1是一种技巧,而非解决方案

+1这个错误使我非常痛苦。 请让我们开始一个对话框,以解决此问题。 我在数百个(!)地方有数十个使用$ timeout的bower和npm角度模块,并且在我的代码中使用了45次。 更改我的代码是可行的(并且是合理的),但是,从逻辑上来说,更改第三方代码完全是不可能的:联系并尝试激励上游提供商或在本地本地派生第三方角度模块并维护并行补丁流是一个噩梦,当错误是由于量角器和$ timeout引起,而不是模块外部时,则是不可接受的。

请让我们一起努力。

+1

此错误在以下示例场景中产生了要进行e2e测试的问题,

1)填写字段并提交一些表格,该表格将广播多个事件,例如
*状态消息链接,附加了$ timeout,仅可见几秒钟
*新消息被添加到其他小部件的消息列表中
2)接下来,当您单击状态消息链接时,它应该打开该消息,并弹出带有详细信息的信息

因此,当我们尝试使场景自动化时,我们可以提交表单。 但是,当我们尝试单击状态消息链接时,我们需要使用browser.ignoreSynchronization = true;。 因为链接已附加到$ timeout。

该链接是可单击的,但是作为弹出窗口打开的消息不起作用。

当我检查Stackoverflow和一些Google答案时,我知道这是由于$ timeout引起的。 所以我检查了删除$ timeout为元素,现在它可以正常工作。

所以我的要求是每当没有$ timeout时我们都无法拥有一个元素。 因此,请考虑上述情况,并具有一个新功能,量角器可以忽略等待$ timeout的时间。

面对同样的问题,不得不使用window.setTimeout和window.clearTimeout来避免它。 不是很成角度的方式,可能会干扰摘要,但是$ interval和忽略同步不是解决方案

面对完全相同的问题,量角器团队的任何人都会对此做出回应吗?

+1

这让我发疯(这里是量角器小菜一碟),直到我得出如下肮脏的解决方案:

beforeEach(function () { browser.ignoreSynchronization = true; });
afterEach(function () { browser.restart(); });

工作正常,在无头浏览器上也不太慢。

+1

在这里也遇到了问题。 我的测试进行得很顺利,直到访问一个发出一些API请求并打开一些WebSocket的页面为止。 此时,量角器每次都超时。 我还没有找到一个好的解决方案。

如果您在全球范围内关闭browser.ignoreSynchronization = true; ,我会对您建议的帮助方法感到好奇

describe('something', function() {
  it('should do....', function() {
    var fooPage = new FooPage();
    fooPage.visit();
    var barPage = fooPage.clickCreate();  // does some API call, then redirects to barPage
    // at this point, enough things are happening that it is impossible to interact with barPage.
    // Protractor locks up.
    // barPage makes other API call & opens WebSocket
    barPage.clickBaz();  // nope.  will timeout every time.
  });
});

@benjaminapetersen我的助手看起来像这样:

btn1.click();         // Triggers many things and eventually displays page2
wait.forURL('page2'); // wait for page2 to be loaded before moving on
$('.btn1').click();   // this does some async job and eventually displays btn2
wait.forElement('.btn2');
$('.btn2').click();

例如,返回wait.forElement

browser.wait(() => elem.isPresent().then(p => p, () => {}), timeout, 'Not found');
// browser.wait(function() { return elem.isPresent().then(function(p) { return p;} , function() {}); }, timeout, 'Not found');

这个想法是模仿实际的用户行为:我单击一个元素,等到另一个元素可见后再单击,依此类推。这样就不必等待$ timeout了,它更快,更安全。

关键是要忽略内部诺言中的错误,并进行更多尝试,直到达到超时为止。

PS:您也可以为此使用ExpectedConditions,但是当angular重新构建DOM节点时(例如ng-if条件),我遇到了一些问题:

browser.wait(browser.ExpectedConditions.precenseOf(elem), timeout, 'Not found');

@floribon太棒了,谢谢!

@juliemr您能帮助我,请我在量角器中捕获吐司错误消息。请回复我的邮件ID anjali。 [email protected]

@juliemr使用$ interval服务调用长时间运行的http请求无效。 量角器仍在超时。
这是我的角度代码
this。$ interval(()=> this。$ http.get(this.prefix(url),config),0,1)
量角器仍在等待$ http任务完成。
我正在使用量角器5.x和angualrjs 1.6x。
你可以帮帮我吗?

+1,应该添加一些东西来处理。

+1

+1

+1

+1

+1

+1

这个规范对我有用。 太谢谢了! 欢呼声@floribon暗示执行是异步的。

    // screenshot-spec.js

    // a close button that appears on the md-toast template
    const closeToastButton = $('[data-automation="toast-close"]')
    const cond = protractor.ExpectedConditions
    function waitToastShow() {
        return browser.wait(cond.elementToBeClickable(closeToastButton), 5000)
    }
    function waitToastHide() {
        return browser.wait(cond.invisibilityOf(closeToastButton), 5000)
    }

    screenshot = name => browser.takeScreenshot().then(/* save fn */)

    describe('a suite ... ', () => {
        it('takes screenshots of an md-toast once shown and after hidden', function () {
            // ... actions that launch an md-toast using $mdToast.show({ ... })
            browser.ignoreSynchronization = true
            waitToastShow().then(() => {
                screenshot('toast-showing.png')
                waitToastHide().then(() => {
                    screenshot('toast-hidden.png')
                    browser.ignoreSynchronization = false;
                })
            })
        });
    }

伙计们,我如何为某个测试用例添加超时
`
它(“ 1-应使用发送到电子邮件的注册码登录”,功能(完成){

// setTimeout(function () {
            flow.execute(browser.params.getLastEmail)
                .then(function (email) {
                    expect(email.subject)
                        .toEqual('[email protected] submitted feedback');
                    expect(email.headers.to)
                        .toEqual('[email protected]');
                    expect(email.html.includes('User feedback details: accountId: 12345, related To: dashboard, description: ' + D.feedbackMsg + ''))
                        .toEqual(true);
                    console.log(email.html);
                    // done();
                });


    });`
此页面是否有帮助?
0 / 5 - 0 等级