Material: Replacing $timeout with $interval for $mdToast for protractor

Created on 6 May 2016  路  12Comments  路  Source: angular/material

Since the $mdToast service uses $timeout to do the hiding delay, you are unable to e2e test toasts that have a hide delay.

An example test that fails:
toast.js

$mdToast.show(
    $mdToast.simple()
        .content('Saved')
        .position('bottom right')
);

toast.spec.js

var saveAlert = element(by.css('md-toast'));
browser.wait(function(){
    return browser.isElementPresent(saveAlert);
});
expect(saveAlert.getText()).toBe('Saved');

But, if you don't let it hide, the test will pass

$mdToast.show(
    $mdToast.simple()
        .content('Saved')
        .position('bottom right')
        .hideDelay(0)
);

Protractor handles $interval's better and does not wait for the interval to finish before processing. Protractor issue reference: protractor/issues/169

This issue refers to the deprecated Issue: 2704. Note: Credits to @adamweeks for the code to reproduce the Issue

The $mdToast control is an important part of e2e tests because in many cases is the unique way to know the result of an operation.

Most helpful comment

In case anyone is still interested, this code works for me with no hacks to $timeout or $interval or Toast. The idea is to use the promises of click() and wait() to turn on and off synchronization. Click whatever to get to the page with the toast message, and immediately turn off sync, wait for the toast message, then dismiss it and then turn back on sync (INSIDE the promise).

    element(by.id('createFoo')).click().then(function () {
        browser.wait(EC.stalenessOf(element(by.id('createFoo'))), TIMEOUT);
        browser.ignoreSynchronization = true;
        browser.wait(EC.visibilityOf(element(by.id('toastClose'))), TIMEOUT).then(function () {
          element(by.id('toastClose')).click();
          browser.ignoreSynchronization = false;
       })
    });

All 12 comments

That timeout is triggered on the $$interimElement, but I'm not sure whether it's appropriate to replace it with an $interval, because this is supposed to run once (I'm aware that you can cancel it after the first run). I think this might open the door to having to replace all of the $timeout uses with $interval in order to get other e2e tests to pass. Also @DevVersion had a pretty valid point in the deprecated issue that the time in intervals isn't necessarily guaranteed.

Can't you flush the timeouts in your tests instead?

Thanks for your response, could you provide an example?

I haven't had to deal with Protractor tests in a while, but if you have angular-mocks, you can inject the $timeout service in your test case and call $timeout.flush whenever you're expecting it to be done.

The problem with flushing your timeouts, is that the $mdToast will be gone. There was no good way to test that the toast was present before the timeout because protractor waits for timeouts before being "ready". The only way to do it was with the .ignoreSynchronization hack.

use browser.driver.findElement() instead of protractors wrappers.

Great discussion. Let's move this to the Angular Material Forum.

Will not fix this issue in Angular Material.

In case anyone is still interested, this code works for me with no hacks to $timeout or $interval or Toast. The idea is to use the promises of click() and wait() to turn on and off synchronization. Click whatever to get to the page with the toast message, and immediately turn off sync, wait for the toast message, then dismiss it and then turn back on sync (INSIDE the promise).

    element(by.id('createFoo')).click().then(function () {
        browser.wait(EC.stalenessOf(element(by.id('createFoo'))), TIMEOUT);
        browser.ignoreSynchronization = true;
        browser.wait(EC.visibilityOf(element(by.id('toastClose'))), TIMEOUT).then(function () {
          element(by.id('toastClose')).click();
          browser.ignoreSynchronization = false;
       })
    });

@elubin awesome.. We added a helper function to our page that "expects toast to containXXX" and it worked great

  var home = this;
  this.expectToastToContain = function ( toastMessage ){
    return function(){
      browser.ignoreSynchronization = true;
      browser.wait( protractor.ExpectedConditions.visibilityOf( element( home.toast ) ), 3000 ).then(
        function(){
          expect( element( home.toast ).getText() ).toContain( toastMessage );
          browser.ignoreSynchronization = false;
        }
      );
    };
  };

@quiasmo Is that code working in phantomJS? All our tests with toasts pass in chrome but fail in phantom =(

@manasovdan we haven't tried phantomJS

@crisbeto There is a [count] parameter to the $interval(fn, delay, [count], [invokeApply], [Pass]);

$timeout(fn, delay) could be replaced by $interval(fn, delay, 1).

It doesn't make it any less of a hack though @antoinebrault

Was this page helpful?
0 / 5 - 0 ratings