Phantomjs: setTimeout ignores timeout when called from page.includeJs() callback

0

_[email protected] commented:_

Which version of PhantomJS are you using? Tip: run 'phantomjs --version'. => 1.7.0 win7 x64

What steps will reproduce the problem?
Run the attached JS file with phantomjs.
It will try and load google.com, include jQuery - both work fine and then tries do perform a setTimeout of 5 seconds. The function passed to setTimeout is called immediately.

What is the expected output? What do you see instead?
After the console log "BeforeTimeout" I expect 5 seconds to pass before "AfterTimeout" is written to the console. Instead, "AfterTimeout" is written instantly to the console.

Which operating system are you using?
Win7 x64

Did you use binary PhantomJS or did you compile it from source?
Binary

Please provide any additional information below.

Disclaimer:
This issue was migrated on 2013-03-15 from the project's former issue tracker on Google Code, Issue #832.
:star2:   6 people had starred this issue at the time of migration.

ariya picture ariya  ·  20 Oct 2012

Most helpful comment

3

Wow, this bug has been open for 4 years and still no resolution?

georgiosd picture georgiosd  ·  23 Mar 2017

All comments

0

_[email protected] commented:_

I also have the same problem. PhantomJS ignores setTimeout in main context (not in page.evaluate).

ariya picture ariya  ·  8 Jan 2013
0

_rob.[email protected] commented:_

Yes, seen with PhantomJS 1.8.1
setTimeout ignored in main context but not in page.evaluate

timecode picture timecode  ·  9 Feb 2013
0

_[email protected] commented:_

Doesn't work on W7x64: v1.8.1

ariya picture ariya  ·  3 Mar 2013
0

_[email protected] commented:_

same for me: phantomjs-1.8.2 W7x64

DGuidi picture DGuidi  ·  13 Mar 2013
0

i still have this in phantomjs 1.9 with ubuntu x64
any way we can progress this? need any help/tests ?

javascriptlove picture javascriptlove  ·  26 Mar 2013
0

Still not fixed in phantomjs 1.9.0 with Archlinux(Linux 3.8.5-1).

dotcink picture dotcink  ·  9 Apr 2013
0

This does work for me on Windows 7 (64-bit) with PhantomJS 1.9.0 (downloaded binary).

Is it only a bug on Linux OSes now? Haven't seen any MacOSX users chime in either.

JamesMGreene picture JamesMGreene  ·  9 Apr 2013
0

I just tested with 1000 setTimeout()'s in includeJs()'s callback environment, and about 80% failed (the timeout value seems to become 0).
BTW, In main context all succeed.

PhantomJS 1.9.0 in ArchLinux.

dotcink picture dotcink  ·  11 Apr 2013
0

I think there are some very very very rare occasions when setTimeout doesn't work in main context. I've just tried different combinations of page.open callbacks and some nested function closures, and it works in Ubuntu with PhantomJS 1.9! But... here is a script that fails

So perhaps it has to do something with the closures or callbacks or something else

var wp = require('webpage');
var page = wp.create();

page.viewportSize = { width: 1024, height: 768 };

page.onError = function (msg, trace) {
    //console.log(msg);
    //trace.forEach(function(item) {
        //console.log('  ', item.file, ':', item.line);
    //})
}

page.customHeaders = {
    'Accept': 'text/html,application/xhtml+xml,application/rss+xml,application/xml;q=0.9,*/*;q=0.8'
};

page.settings.userAgent = 'Googlebot-News';
var step = -1;
var output = {};

var data = function (s, e) {
    e = e || document;
    return e.querySelector(s).attributes.data.value;
};

var value = function (s, e) {
    e = e || document;
    return e.querySelector(s).nodeValue;
};

var steps = [
    function() {
        output = [123];
        nextstep();
    },
    function() {
        var loadNews = function(a) {
            //page.close();
            //page = null;
            //page = wp.create();
            console.log(a, output.length);

            if (a >= output.length)
                nextstep();
            else {
                page.open("http://google.com", function(status) {
                    (function(a) {
                        console.log('Before timeout', a);
                        setTImeout(function() {
                            console.log('ok'); // -- this never gets displayed!
                            //loadNews(a);
                        }, 100); // give a breeth
                    })(a+1);
                    console.log('After timeout', a);

                });
            }
        };
        loadNews(0);
    }
];

var nextstep = function() {
    if (step < steps.length-1) {
        step++;
        steps[step]();
    } else {
        console.log(JSON.stringify(output));
        phantom.exit();
    }
};

// run step by step
nextstep();
javascriptlove picture javascriptlove  ·  11 Apr 2013
1

@jahggler: You have an important typo: setTImeout instead of setTimeout.

JamesMGreene picture JamesMGreene  ·  11 Apr 2013
0

omg thanks, i thought it should display undefined or something, thanks a lot!
then everything regarding setTimeout in main context works

javascriptlove picture javascriptlove  ·  12 Apr 2013
0

@dotcink: Did you use a different repro script than the OP did?

JamesMGreene picture JamesMGreene  ·  12 Apr 2013
0

@JamesMGreene
I tested with the binary from https://phantomjs.googlecode.com/files/phantomjs-1.9.0-linux-i686.tar.bz2 .
I've also tryied compiling https://phantomjs.googlecode.com/files/phantomjs-1.9.0-source.zip , with the result not changed .

Here is my script test.js:

var page = require('webpage').create();

function test(from) {
    for (var n = 0; n < 1000; n++) {
        console.log(new Date() + " BeforeTimeout in " + from);
        setTimeout(function() {
            console.log(new Date() + " AfterTimeout in " + from);
        }, 10000);
    }
}
page.open('http://google.com', function(){
    page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js', function(){
        test("includeJs's callback");
    });
    test("main context");
});

Then output to test.log:

$ phantomjs test.js | tee test.log
$ wc -l test.log
4000 test.log

which shows that all _4000_ console.log's works.

$awk '{ print $5,$8 ; }' test.log | uniq 
20:20:34 BeforeTimeout
20:20:35 BeforeTimeout
20:20:35 AfterTimeout
20:20:44 AfterTimeout

shows that setTimeout's started at about 20:20:34 or 20:20:35, and worked at 20:20:35 or 20:20:44. (These time depend on when you run the test.)

$ grep -c "20:20:4.*AfterTimeout in main context" test.log 
1000

shows that all _1000_ console.log's in setTimeout's of main context works as expected.

$ grep -c "20:20:4.*AfterTimeout in includeJs's callback" test.log

192

shows that only _192_ (about 20%) console.log's in setTimeout's of includeJs's callback works well. (The result may vary a bit)

dotcink picture dotcink  ·  12 Apr 2013
0

setInterval produces a similar result. It runs 1 time without waiting and doesn't run again.

I'm on XP using 1.9

Example

var page = require('webpage').create(),
    pageLoadDelay = 700,
    loadInProgress = false; 

page.onConsoleMessage = function(msg) {
    console.log(msg);
};

page.onLoadStarted = function() {
    loadInProgress = true;
};
page.onLoadFinished = function() {
    console.log("Page Loaded")
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js", function(){

        console.log("Javascript Included");

        var count = 0;
        setInterval(function() {
            if(count <= 20)
                console.log("Interval" + count++)
            else
                phantom.exit();
        }, pageLoadDelay);
    });
    loadInProgress = false;
};

page.open("google.com");


curtisturner picture curtisturner  ·  26 Jul 2013
0

@curtisturner, your example works fine for me on ubuntu x64 with v1.9 (binary)

javascriptlove picture javascriptlove  ·  1 Aug 2013
0

We've noticed this happen outside of includeJS, maybe one in every ten attempts. I can't share code unfortunately, but this is definitely still a pretty crazy issue.

CarlQLange picture CarlQLange  ·  2 Aug 2013
0

I have also experienced the issues outlined above using version 1.9.1 in both setTimeout and setInterval - the latter of which kills the nice waitFor function outlined in some of the examples. Pretty frustrating. I can't share all of the code, but it looks something like the following:

// waitFor and others defined up here..

page.open('http://www.website.com', function () {
  page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {

    // attempt to submit a form
    pageloaded = false; // global variable which is changed in onLoadFinished callback
    page.evaluate(function (text, value) {
      // form submission stuff
    }, txt, val);

    // waitFor (setInterval) and basic setTimeout don't work here.

  });

});
astjohn picture astjohn  ·  21 Aug 2013
0

Confirmed in 1.9.2 on Win 7, in main context.

davedx picture davedx  ·  11 Sep 2013
0

setTimeout & setTimeInterval getting ignored in win 7/64 with phantom js 1.9.2. Is there a fix available for this? Any chance the fix is missing in the windows version?

badriub picture badriub  ·  25 Sep 2013
0

Nope, I have the same problem on a Mac.

uberbrady picture uberbrady  ·  25 Sep 2013
0

Thus why the issue is still open.

JamesMGreene picture JamesMGreene  ·  25 Sep 2013
0

We are also facing same issue with phantomjs 1.9.1 on Ubuntu 12.04 32 bit

amitpatelx picture amitpatelx  ·  20 Nov 2013
0

In case anyone else is struggling with this, I found that wrapping a setTimeout in another setTimeout works:

Example:

        setTimeout(function(){
            setTimeout(function(){
                // Get image data
                page.clipRect = page.evaluate(function() {
                    return document.querySelector("#Viewport").getBoundingClientRect();
                });
                // Sends back base64 image for direct display
                var imagedata = page.renderBase64('png');
                callback(imagedata);
                page.close();
            },700);
        },1);
emergedennis picture emergedennis  ·  18 Dec 2013
0

I can confirm the issue in Phantom.js 1.9.2 on Fedora. It looks like setTimeout is ignored both in the main context and page.evaluate calls.

katiejots picture katiejots  ·  7 Feb 2014
0

Any news about this? Anyone with time to check this out? just tested @emergedennis 's patch and didn't work either...

SergioCrisostomo picture SergioCrisostomo  ·  21 Feb 2014
0

a year later and, still not working on the main context and page evaluate (centos 6.4)

alvarolm picture alvarolm  ·  5 Mar 2014
0

I checked @emergedennis 's patch and it worked.
So did

setTimeout(function(){
    setInterval(function () {
        //do something...
    }, 1000);
}, 1);

I use PhantomJS 1.9.7 for windows on windows8

tom76kimo picture tom76kimo  ·  7 May 2014
0

I had the script for the phantomjs which works perfectly without any timeout workarounds on my mac:

// checking if the result is shown on the page
                (function() { 
                    var dfd = new $.Deferred(); 

                    // how many times to check before we go further
                    var checkNumber = 5;

                    var checkInterval = setInterval(function() {
                        checkNumber--;

                        var resultSuccess = page.evaluate(function() { return $('.class').is('*'); });
                        var resultEmpty = page.evaluate(function() { return $('div.class:contains(text to check)').is('*'); });

                        if (resultSuccess || resultEmpty) {
                            clearInterval(checkInterval);
                            dfd.resolve();
                        } else if(checkNumber == 0){
                            clearInterval(checkInterval);
                            dfd.reject();
                        }
                    }, 5000);

                    return dfd; 
                    })().then(/*success*/ function(){
                            var name = page.evaluate(function(){
                                return $('.class').text();
                            });

                            page.render('results_page.png');

                            var endTime = (new Date).getTime();
                            console.log(JSON.stringify({'status': 'success', 'name': name, 'executionTime': ((endTime - startTime)/1000)}));

                            phantom.exit();
                    }, /*failure*/ function(){
                            console.log(JSON.stringify({'status': 'error', 'message': 'Unable to submit'}));
                            phantom.exit();
                    });

(it uses the jQuery deferred objects)

However, today, when I tried to do the same on my mac but in the other script - I found that setInterval fails :( Current solution is to warp it in the setTimeout, but I'm not a fond of this.

p.s. I found why it was working, because before I called the page.open 2nd time (that was required by the logic of the script). More info - below.

PavelPolyakov picture PavelPolyakov  ·  14 Sep 2014
0

Here is another variant of making the setInterval and setTimeout work.

The minimum viable phantomjs script looks this way:

phantom.page.injectJs('./jquery-1.11.1.min.js');

var page = require('webpage').create();

page.open('http://yandex.ru', function() {
    page.includeJs("http://code.jquery.com/jquery-1.11.0.min.js", function() {

        (function() {
            var dfd = new $.Deferred();

            page.open('http://yandex.ru', function(status) {
                dfd.resolve();
            });

            return dfd.promise();
        })().then(function() {

            var checkNumber = 5;
            var checkInterval = setInterval(function() {
                    checkNumber--;

                    console.log(checkNumber);

                    if (checkNumber == 0) {
                        clearInterval(checkInterval);
                        console.log("exit");
                        phantom.exit();
                    }
                }, 5000);
        });

    });
});

The funny part of it is, that as soon as we would remove another page.open from the code - it would stop working.

So, somehow, 2nd page.open, makes the setTimeout and setInterval works.

This solutions is way worth from the setTimeout(function(){},1); , but both of them are illogical, because I expect that behaviour to work without any workarounds. Would hope that in the next versions of the phantomjs it would work by default.

p.s.
Any ideas why the 2nd page.open makes the timers work?

PavelPolyakov picture PavelPolyakov  ·  14 Sep 2014
0

I have this issue as well. No delay on the timeout callback.

simonklb picture simonklb  ·  28 Jan 2015
0

@simonklb Which version of phantomjs are you working with? I thought it was already resolved in one of the recent releases.

apendua picture apendua  ·  28 Jan 2015
0

@apendua I got the build in the Ubuntu repo. Version 1.9.0. Which version is this solved in?

simonklb picture simonklb  ·  28 Jan 2015
0

In that case you're using pretty outdated version. I guess you should try 1.9.8.

apendua picture apendua  ·  28 Jan 2015
0

Looks like it's working, thanks! This should perhaps be marked as resolved then?

simonklb picture simonklb  ·  28 Jan 2015
0

Just a note for users. 1.9.8 still had the setTimeout bug when I tried it. I updated to 2.0 and setTimeout works properly.

jeffreytu picture jeffreytu  ·  4 Feb 2015
0

The setTimeout still doesn't work on my Fedora 21, phantomjs version 2.0

richthofen911 picture richthofen911  ·  6 Mar 2015
0

Still happening for me - PhantomJS 2.0 built from source on RHEL 6.3.

ashafer01 picture ashafer01  ·  2 Jun 2015
0

@ashafer01
use http://casperjs.org/ , which laverages the phantomjs. No issues with setTimeout

PavelPolyakov picture PavelPolyakov  ·  2 Jun 2015
0

Still no working for me with Win 7 and phantom 2.0

MartelBenjamin picture MartelBenjamin  ·  9 Jun 2015
0

Still facing the issue on Mac with phantomjs 2.0.1-dev!!
The mentioned workaround doesn't help either!
My usecase is page.evaluate(), seems to be the case for all callbacks in general!!

srm09 picture srm09  ·  20 Jul 2015
0

Bump. I have some code under test which uses setTimeout. The interval is ignored, the callback executes immediately. I have the callback recurse on itself (it's a classic "wait for" pattern), but the recursion always dies after 3 iterations. Versions:

  • PhantomJS 2.0.0
  • poltergeist 1.9.0 (Ruby gem)
  • Capybara 2.7.1 (Ruby gem)

The exact same spec passes 100% of the time when I have Capybara execute in Firefox instead of PhantomJS.

Pistos picture Pistos  ·  28 Jun 2016
0

In fairness, I have tried to set up a minimal example, and could do it. Something is particular to my code (websockets?) which is causing the problem.

Pistos picture Pistos  ·  29 Jun 2016
3

Wow, this bug has been open for 4 years and still no resolution?

georgiosd picture georgiosd  ·  23 Mar 2017
0

Solution:

       setTimeout(function(){
            setTimeout(function(){
                //Your function here
            },700);
        },1);

Double timeouts and it works ¯_(ツ)_/¯

frazras picture frazras  ·  28 Apr 2017
0

@frazras I remember using this fix ~3 years ago. It worked for some time and then it broke again with another release of phantomjs. Glad to hear it's working again, but I am also surprised that this issue has not been resolved for so long.

apendua picture apendua  ·  28 Apr 2017
0
apendua picture apendua  ·  2 May 2017
0

I have been struggling with this issue as well. Did some quick tests to see if it was related to load.

Which version of PhantomJS are you using? Tip: run 'phantomjs --version
2.1.1

Which operating system are you using?
CentOS7

Did you use binary PhantomJS or did you compile it from source?
Binary

What steps will reproduce the problem?

Construct a HTML document with a number of Divs. I did it in PHP (see attachment)
generateHTML.php.txt

start a setTimeout loop and have PhantomJS open the html we just generated, vanilla page.open logic:

var count = 0;
function counter()
{
  setTimeout(function()
  { 
    count++;
    counter();
  }, 100);
}
function openPage()
{
  counter();

  var webpage   = require('webpage');
  var page        = webpage.create();
  var sTime       = (new Date).getTime() / 1000;
  var url            = http://devserver.example.com/test.htm
  page.open(url,  function(status) {
    var duration =  sTime  - (new Date).getTime() / 1000;
    console.log(duration);
    console.log(count);
  });
}

openPage();

The count is always 1. I then increase the number of Divs and measure how long it takes PhantomJS to open on localhost + memory consumption. CPU is locked at 100% when opening the page, i get memory from bash.

Here are the results:

2017-06-24_201423

As you can see there is not a linear relationship between the size of the document and load time or memory consumption. Also it appears page.open blocks all other processing.

Opening HTML with ~1200 divs causes PhantomJS to consume 10GB+ memory and almost 5 minutes just to open the page on a gigabit network.

This is the root cause of many exceptions when opening pages. I do not know C++ or i would help find the structure that causes memory and execution time to grow exponentially.

  1. What might be the reason PhantomJS does not scale linearly with the size of the document?
  2. Is it possible to push certain functions off the main thread?
merlinthemagic picture merlinthemagic  ·  24 Jun 2017
0

Due to our very limited maintenance capacity (see #14541 for more details), we need to prioritize our development focus on other tasks. Therefore, this issue will be automatically closed. In the future, if we see the need to attend to this issue again, then it will be reopened. Thank you for your contribution!

stale[bot] picture stale[bot]  ·  28 Dec 2019