How can I make Protractor NOT wait for $timeout?
Asked Answered
P

2

7

I'm testing my angular application with Protractor. Once the user is logged in to my app, I set a $timeout to do some job in one hour (so if the user was logged-in in 13:00, the $timeout will run at 14:00). I keep getting these failures:

"Timed out waiting for Protractor to synchronize with the page after 20 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md. The following tasks were pending: - $timeout: function onTimeoutDone(){....."

I've read this timeouts page: https://github.com/angular/protractor/blob/master/docs/timeouts.md so I understand Protractor waits till the page is fully loaded which means he's waiting for the $timeout to complete...

How can I make Protractor NOT wait for that $timeout? I don't want to use:

browser.ignoreSynchronization = true;

Because then my tests will fail for other reasons (other angular components still needs the time to load...)

Polypoid answered 26/10, 2015 at 7:40 Comment(1)
Have you tried flushing that timeout via your test immediately after setting it?Senhauser
D
3

The solution will be to flush active timeouts (as @MBielski mentioned it in comments), but original flush method itself is available only in anuglar-mocks. To use angular-mocks directly you will have to include it on the page as a <script> tag and also you'll have to deal with all overrides it creates, it produces a lot of side effects. I was able to re-create flush without using angular-mocks by listening to any timeouts that get created and then reseting them on demand.

For example, if you have a timeout in your Angular app:

$timeout(function () {
    alert('Hello World');
}, 10000); // say hello in 10 sec

The test will look like:

it('should reset timeouts', function () {

    browser.addMockModule('e2eFlushTimeouts', function () {

        angular
        .module('e2eFlushTimeouts', [])
        .run(function ($browser) {

            // store all created timeouts
            var timeouts = [];

            // listen to all timeouts created by overriding
            // a method responsible for that
            var originalDefer = $browser.defer;

            $browser.defer = function (fn, delay) {
                // originally it returns timeout id
                var timeoutId = originalDefer.apply($browser, arguments);
                // store it to be able to remove it later
                timeouts.push({ id: timeoutId, delay: delay });
                // preserve original behavior
                return timeoutId;
            };

            // compatibility with original method
            $browser.defer.cancel = originalDefer.cancel;

            // create a global method to flush timeouts greater than @delay
            // call it using browser.executeScript()
            window.e2eFlushTimeouts = function (delay) {
                timeouts.forEach(function (timeout) {
                    if (timeout.delay >= delay) {
                        $browser.defer.cancel(timeout.id);
                    }
                });
            };

        });

    });


    browser.get('example.com');

    // do test stuff

    browser.executeScript(function () {
        // flush everything that has a delay more that 6 sec
        window.e2eFlushTimeouts(6000); 
    });

    expect(something).toBe(true);
});

It's kinda experimental, I am not sure if it will work for your case. This code can also be simplified by moving browser.addMockModule to a separate node.js module. Also there may be problems if you'd want to remove short timeouts (like 100ms), it can cancel currently running Angular processes, therefore the test will break.

Difficulty answered 26/10, 2015 at 16:4 Comment(2)
nice solution. However, if I understand correctly, this solution cancels all timeouts, so I cannot test an assertion while the timeout is going.Danny
@BiAiB, Timeouts only get cancelled when you call browser.executeScript(...) part, I believe you can make assertions first and then kill the timeouts if required. Maybe placing browser.executeScript(...) part in the afterEach will work too.Difficulty
C
0

The solution is to use interceptors and modify the http request which is getting timeout and set custom timeout to some milliseconds(your desired) to that http request so that after sometime long running http request will get closed(because of new timeout) and then you can test immediate response.

This is working well and promising.

Cityscape answered 10/7, 2017 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.