New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid waiting for $timeout()s ? #169

Closed
drhumlen opened this Issue Oct 16, 2013 · 58 comments

Comments

Projects
None yet
@drhumlen

drhumlen commented Oct 16, 2013

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

@juliemr

This comment has been minimized.

Member

juliemr commented Oct 16, 2013

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 angular/angular.js@2b5ce84). You can set an interval that will repeat 1 time.

@mackwic

This comment has been minimized.

mackwic commented Oct 18, 2013

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.

@drhumlen

This comment has been minimized.

drhumlen commented Oct 23, 2013

$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

@juliemr juliemr closed this Oct 23, 2013

@OliverJAsh

This comment has been minimized.

OliverJAsh commented May 2, 2014

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

@apuchkov

This comment has been minimized.

apuchkov commented Jul 15, 2014

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);
@redders6600

This comment has been minimized.

redders6600 commented Aug 1, 2014

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.

@redders6600

This comment has been minimized.

redders6600 commented Aug 1, 2014

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?

@vytautas-pranskunas-

This comment has been minimized.

vytautas-pranskunas- commented May 20, 2015

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.

@juliemr

This comment has been minimized.

Member

juliemr commented May 20, 2015

@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.

@vytautas-pranskunas-

This comment has been minimized.

vytautas-pranskunas- commented May 21, 2015

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

@juliemr

This comment has been minimized.

Member

juliemr commented May 21, 2015

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

@vytautas-pranskunas-

This comment has been minimized.

vytautas-pranskunas- commented May 21, 2015

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.

@vytautas-pranskunas-

This comment has been minimized.

vytautas-pranskunas- commented May 27, 2015

Is there any progress or thoughts on this?

vlajos added a commit to vlajos/bootstrap that referenced this issue Jul 21, 2015

make alert protractor testable
Basicall using the `$interval` module. More details: angular/protractor#169 (comment)

vlajos added a commit to vlajos/bootstrap that referenced this issue Jul 21, 2015

make alert protractor testable
Basicall using the `$interval` module. More details: angular/protractor#169 (comment)

vlajos added a commit to vlajos/bootstrap that referenced this issue Jul 21, 2015

make alert protractor testable
Basicall using the `$interval` module. More details: angular/protractor#169 (comment)
@smilechun

This comment has been minimized.

smilechun commented Jul 23, 2015

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
@zeitos

This comment has been minimized.

zeitos commented Jun 27, 2016

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

@fullstackwebdev

This comment has been minimized.

fullstackwebdev commented Jul 11, 2016

+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.

@stephanebouget

This comment has been minimized.

stephanebouget commented Jul 13, 2016

+1

@Shivaraj05

This comment has been minimized.

Shivaraj05 commented Sep 21, 2016

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.

@tot-ra

This comment has been minimized.

tot-ra commented Sep 28, 2016

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

@alexspot

This comment has been minimized.

alexspot commented Oct 17, 2016

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

@colloqi

This comment has been minimized.

colloqi commented Oct 18, 2016

+1

@kusid

This comment has been minimized.

kusid commented Oct 19, 2016

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.

@irushah

This comment has been minimized.

irushah commented Dec 8, 2016

+1

@benjaminapetersen

This comment has been minimized.

benjaminapetersen commented Dec 14, 2016

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.
  });
});
@floribon

This comment has been minimized.

floribon commented Dec 14, 2016

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

This comment has been minimized.

benjaminapetersen commented Dec 15, 2016

@floribon awesome, thanks!

@anjalisingh824

This comment has been minimized.

anjalisingh824 commented Apr 20, 2017

@juliemr can u help me please me to catch the toast error messages in protractor.Pls reply on my mail id anjali.smartsensesolutions@gmail.com

@MuraliMolluru

This comment has been minimized.

MuraliMolluru commented Jun 29, 2017

@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?

@SebDuf

This comment has been minimized.

SebDuf commented Jul 31, 2017

+1, something should be added to handle this.

@stenmuchow

This comment has been minimized.

stenmuchow commented Aug 4, 2017

+1

5 similar comments
@ajambrovic

This comment has been minimized.

ajambrovic commented Sep 19, 2017

+1

@dannylui

This comment has been minimized.

dannylui commented Sep 20, 2017

+1

@nbabii

This comment has been minimized.

nbabii commented Jan 12, 2018

+1

@stcos

This comment has been minimized.

stcos commented Feb 13, 2018

+1

@tgelu

This comment has been minimized.

tgelu commented Feb 26, 2018

+1

@activedecay

This comment has been minimized.

activedecay commented Mar 5, 2018

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;
                })
            })
        });
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment