Skip to content
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 · 59 comments
Closed

Avoid waiting for $timeout()s ? #169

drhumlen opened this issue Oct 16, 2013 · 59 comments

Comments

@drhumlen
Copy link

@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
Copy link
Member

@juliemr 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
Copy link

@mackwic 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
Copy link
Author

@drhumlen 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
Copy link

@OliverJAsh 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
Copy link

@apuchkov 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
Copy link

@redders6600 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
Copy link

@redders6600 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-
Copy link

@vytautas-pranskunas- 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
Copy link
Member

@juliemr 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-
Copy link

@vytautas-pranskunas- 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
Copy link
Member

@juliemr 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-
Copy link

@vytautas-pranskunas- 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-
Copy link

@vytautas-pranskunas- 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
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
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
Basicall using the `$interval` module. More details: angular/protractor#169 (comment)
@smilechun
Copy link

@smilechun 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
@fullstackwebdev
Copy link

@fullstackwebdev 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
Copy link

@stephanebouget stephanebouget commented Jul 13, 2016

+1

@Shivaraj05
Copy link

@Shivaraj05 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
Copy link

@tot-ra 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
Copy link

@alexspot alexspot commented Oct 17, 2016

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

@colloqi
Copy link

@colloqi colloqi commented Oct 18, 2016

+1

@kusid
Copy link

@kusid 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
Copy link

@irushah irushah commented Dec 8, 2016

+1

@benjaminapetersen
Copy link

@benjaminapetersen 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
Copy link

@floribon 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
Copy link

@benjaminapetersen benjaminapetersen commented Dec 15, 2016

@floribon awesome, thanks!

@anjalisingh824
Copy link

@anjalisingh824 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
Copy link

@MuraliMolluru 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
Copy link

@SebDuf SebDuf commented Jul 31, 2017

+1, something should be added to handle this.

@stenmuchow
Copy link

@stenmuchow stenmuchow commented Aug 4, 2017

+1

5 similar comments
@ajambrovic
Copy link

@ajambrovic ajambrovic commented Sep 19, 2017

+1

@dannylui
Copy link

@dannylui dannylui commented Sep 20, 2017

+1

@nbabii
Copy link

@nbabii nbabii commented Jan 12, 2018

+1

@stcos
Copy link

@stcos stcos commented Feb 13, 2018

+1

@tgelu
Copy link

@tgelu tgelu commented Feb 26, 2018

+1

@activedecay
Copy link

@activedecay 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;
                })
            })
        });
    }
@Amrkamel1
Copy link

@Amrkamel1 Amrkamel1 commented Oct 23, 2019

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('thetester@thesemso.com submitted feedback');
					expect(email.headers.to)
						.toEqual('attidm@maestralsolutions.com');
					expect(email.html.includes('User feedback details: accountId: 12345, related To: dashboard, description: ' + D.feedbackMsg + ''))
						.toEqual(true);
					console.log(email.html);
					// done();
				});


	});`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet