Reputation: 91
I'm writing a function to wait for an element, here is my function:
function waitForElement(query){
var res="null";
var start=Date.now();
do{
res=page.evaluate(function(query) {
return document.querySelector(query)+"";
}, query);
} while (res==="null" && Date.now()-start<=100000);
console.log(Date.now()-start);
console.log(res.toString());
return res!=="null";
}
In page.open()
, I call this function and the result is "null". But if I put the function call in a setTimeout()
, it works.
setTimeout(function(){
page.render('afterLogin.png');
waitForElement('ul.coach li');
console.log('Exit');
phantom.exit();
}, 50000);
Can anybody explain to me what happened here?
Upvotes: 0
Views: 2361
Reputation: 61892
JavaScript is single-threaded. Since you're doing a busy wait, you're also blocking the execution of the page loading and page JavaScript. It is not possible to wait synchronously in PhantomJS. You have to use a recursive and asynchronous approach such as shown in waitFor.js the PhantomJS examples folder:
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 250); //< repeat check every 250ms
};
And you can use it like this:
function waitForElement(selector, callback, timeout){
waitFor(function check(){
return page.evaluate(function(selector){
return !!document.querySelector(selector);
}, selector);
}, callback, timeout);
}
setTimeout(function(){
page.render('afterLogin.png');
waitForElement('ul.coach li', function(){
console.log('Exit');
phantom.exit();
}, 100000);
}, 50000);
Upvotes: 2
Reputation: 1351
The page.open callback is invoked only when the page is loaded. This does not means that everything has been loaded in your page and the js has been fully executed.
In addition, the js on the web page may not be executed immediately, especially if the site uses MVC client frameworks like AngularJS or Backbone.js. A lot of stuff is done after the page load event.
Using setTimeout give you a little delay to be sure that your page is fully rendered.
Upvotes: 0