Protractor: Avoid waiting for $timeout()s ?

Created on 16 Oct 2013  ·  59Comments  ·  Source: angular/protractor

In our app we display "popup" messages to our user when certain buttons are clicked. These popup messages are only visible for about 20 seconds (after which $timeout removes them).

The problem is that Protractor seems to wait until the $timeout is completed before it .findElement() and .expect()s things. However, when the $timeout is _timed out_, the message is gone, and the element is not found.

Any suggestion on how to bypass/solve this? (We _need_ to assert that the correct "popup" message is displayed to the user)

Thanks

question

Most helpful comment

Is Angular team planing to reopen this issue because more and more people run into this. It is not professional just pretend that there is no issue because you can replace $timeout with $interval because code should not be written to fit tests - tests should be written to test code.

All 59 comments

You can use ptor.ignoreSynchronization, but this might cause flakiness elsewhere in your test. Alternatively, on Angular 1.2rc3, there is now an interval service, which Protractor will not wait for (see https://github.com/angular/angular.js/commit/2b5ce84fca7b41fca24707e163ec6af84bc12e83). You can set an interval that will repeat 1 time.

Hi @drhumlen , we had the same problematic and we conclude that the popup is definitely something that should go to unit testing. So, we mocked our notification system and expect that the mock has been called (eventually with some critical informations). We also unit tested the notification to be sure that nothing breaks.
Hope that help.

$interval did the trick for us.

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

It now works great. But we have to be very conscientious and make sure that the popup is removed in some other way after each test (in our case, clicking the "x"-button):

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

@mackwic: I suppose it's possible to test it with a unit test for many apps, but in our case, the error messages are generated by the backend, so we have to test them in the e2e.

Thanks @juliemr

It seems strange that Protractor waits for $timeouts. Most of the time the user would have to work against this?

Also faced this problem and solved it with $interval. With a little twist you can use it just like $timeout.

I wanted to test save method and verify that success notification is displayed after save. This notification was getting hidden by $timeout service. Protractor was waiting for $timeout and checking if notification is visible after it already became hidden.

Before:

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

After:

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

Is this not considered a valid issue? I just ran into this myself. I don't really see that using $interval instead of $timeout is a solution, more a workaround.

I also can't get this to work, even if I change to $interval from $timeout.

I have a toast that disappears after a timeout. This is the code I'm using to test it:

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

The relevant markup is here (jade)

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

The failure is that toastMessage = $('toast-message') doesn't match any elements. Is this failing because it's being called at the start, when .toast-message doesn't exist?

Was this issue fixed yet? Because using interval instead of timeout is hack or workarounf more than solution.

I face same issue and browser.ignoreSynchronization = true; does not help. I can replace code to $interval but e2e tests should test real code not the code adopted to them.

@vytautas-pranskunas- sounds like there's a different issue for you if ignoreSynchronization is not working. If you can provide a reproducible example, open up a new issue.

I figured out when ignoreSynchronization isnot working:
I have a service which is responsible for showing error messages. Once show message stays on screen for five seconds after it fades away. This process is controlled by $timeout.

I got two scenarios - in first ignoreSynchronization works fine, in second - does not

1) I know that messages should appear right away after user clicks button and I do expec(message).tobe(...) in this case ignoreSynchronization does the job.

2) I know that message can appear after any amount of time (depends on server response time) and my further logic should be executed only after it appears so in this case I have to use browser.wait(by.(....).isPresent(), n) here ignoreSynchronization stops working and the flow is following (I will show browser.wait console.log output while waiting)

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

So as you can see it never sees element present because wait freezes when in touch with $timeout

I really do not have so many time to make it reproducible in plunker but if you try to it would be great because this scenario should be quite common.

Thnaks

You log is really not very helpful without the context of the code that created it. Could you share that?

I cannot share my code because of privacy but i can show some pseudo code to get an idea
(not writing html code because there is a button and ng-repeat for displaying messages)

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

and protractor part

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 never finds that element because while element is present browse.wait is not triggering checks.

Is there any progress or thoughts on this?

Why "browser.ignoreSynchronization = false; " cannot be set immediately after the browser.wait()?

My Code

        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

Being bitten by the same problem. The toast module we use is a generic component for npm, so we cannot easily change it to use $interval. I also fail to see why protractor should wait for one and not the other. Ideally this would be configurable on it's own, so it doesn't affect other synchronization waits.

We have this issue at angular-ui/bootstrap#3982 and we decided not to swap it to $interval so we come here to see if there is anything that could be done to fix the issue.

Thank you very much.

I ran through this problem this week using a toast notification component.

Using $interval instead of $timeout is not a plausible solution. And browser.ignoreSynchronization = true does not work for me either.

Any thougths or ideas?

Is Angular team planing to reopen this issue because more and more people run into this. It is not professional just pretend that there is no issue because you can replace $timeout with $interval because code should not be written to fit tests - tests should be written to test code.

+1 We need a fix as well.

Agreed. Test should be fixed. +1

+1, also ran into this issue. Maybe there should be a way to tell protractor timeouts should not be waited for.

ignoreSynchronisation does not work for me, I think it ignores too much things. We need a way to ignore timeouts synchronisation, or have a more sophisticated API like browser.waitForTimeouts();.

+1. need a fix. why should we use ugly workarounds with interval.

+1

+1, finding it very difficult to handle flaky tests.. need a fix for this

+1 agree with @vytautas-pranskunas- we should not be required to change the code we want to test only to make test itself work....

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

I think many people may be using browser.ignoreSynchronization = true in a wrong manner. Please correct me if I'm wrong, but to me if you write:

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

Then it will not work: this is still pure JavaScript and the three lines will be read synchronously. So by the time the getText and expect promises resolve, the synchronization will already be turned back on. You could work around that by waiting for protractor to resolve the last promise and turn synchronization back on its then callback.

One way for the Protractor Team to help people may be to provide access to methods that only affect the synchronization when the promises buffer is done. For instance:

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

Anyway, I've also ran into many troubles due to Protractor waiting for $timeouts: in a large applications there are too many side effects, be it some tooltips, modals, animations, etc. We decided to globally disable the sync with browser.ignoreSynchronization = true; and write tests the way a user would test the product: wait for an element to be visible if you want to click on it, etc. Write yourself a few helper methods and it will become a breeze. This works pretty well for us, and make the tests run so much faster.

+1 using $interval is a hack, not a solution

+1 this bug has caused me so much misery. Please let's start a dialog on how to fix this. I have several dozen bower and npm angular modules that use $timeout, in hundreds(!) of places, as well as I use it 45 times in my code. Changing my code is feasible (and reasonable), however, changing third party code is totally out of the question logistically: contacting and trying to motivate upstream providers or locally forking third party angular modules locally and maintaining a parallel patch stream is a nightmare scenario that isn't acceptable, when the bug is with Protractor and $timeout, not outside modules.

Please let's work together on this.

+1

This bug is creating problems to have e2e test for below sample scenario,

1) Fill the fields and submit some form, which will broadcast multiple events like
* status message link, which is attached with $timeout and visible for only few seconds
* a new message gets added to the message list in different widget
2) Next when you click the status message link, then it should open the message as pop up with details

So when we are trying to automate the scenario, we are able to submit the form. But when we are trying to click the status message link, we need to have browser.ignoreSynchronization = true; because link is attached to $timeout.

The link is clickable, but a message opening as a pop up is not working.

When i checked in Stackoverflow and some google answers, i got to know that it is because of the $timeout. So i checked by removing $timeout for element, now it works properly.

So my request is everytime we cant have a element without $timeout. So please consider the above scenario and have a new feature where protractor can ignore waiting for $timeout.

Faced same issue, had to use window.setTimeout and window.clearTimeout to avoid it. Not very angular way and may interfere with digest, but $interval and ignoring synchronization is not a solution

Faced exact the same issue, will anyone from protractor team respond to this?

+1

This was driving me crazy (Protractor noob here) until I arrived at a dirty solution as below:

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

Works fine and is not too slow on a headless browser.

+1

Also running into an issue here. My tests were going smoothly until visiting a page that makes a few API requests & opens some WebSockets. At that point, Protractor times out, every time. I haven't figured out a good solution.

I'm curious @floribon about your suggested helper methods if you turn off browser.ignoreSynchronization = true; globally. Right now I'm using page objects, which tends to look like:

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 my helpers look like this:

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

With for instance wait.forElement returning

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

The idea is to mimic actual user behavior: I click on an element, I wait until another one is visible, then I click on it, etc. This way no need to wait for $timeout, it's faster and safer.

The key here is to ignore errors from the inner promise and give it a few more tries until timeout is reached.

PS: You can also use ExpectedConditions for that but I had a few problems with them when angular would rebuild the DOM node (for instance ng-if conditions):

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

@floribon awesome, thanks!

@juliemr can u help me please me to catch the toast error messages in protractor.Pls reply on my mail id anjali.[email protected]

@juliemr Using $interval service to invoke long running http requests is not working. Protractor still getting timeout.
Here is my angular code
this.$interval(() => this.$http.get(this.prefix(url), config), 0, 1)
protractor still waiting for $http task to complete.
I am using protractor 5.x and angualrjs 1.6x.
Could you help me?

+1, something should be added to handle this.

+1

+1

+1

+1

+1

+1

This spec worked for me. Big thanks! and cheers go out to @floribon for the hint that the execution is asynchronous.

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

guys how i can add timeout for a certain test case
`
it('1-should login with a registration code sent to an email', function (done) {

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


    });`
Was this page helpful?
0 / 5 - 0 ratings