how to wait for element visibility in phantomjs
Users click this link:
<span onclick="slow_function_that_fills_the_panel(); $('#panel').show();">
Now I'm simulating the click in phantomjs:
page.evaluate(
function() { $("#panel").click(); }
);
console.log('SUCCESS');
phantom.exit();
Phantom exits before the slow function ends its execution and the DIV becomes visible. How can I implement waiting?
Solution 1:
Heres a spin of Cybermaxs's answer:
function waitFor ($config) {
$config._start = $config._start || new Date();
if ($config.timeout && new Date - $config._start > $config.timeout) {
if ($config.error) $config.error();
if ($config.debug) console.log('timedout ' + (new Date - $config._start) + 'ms');
return;
}
if ($config.check()) {
if ($config.debug) console.log('success ' + (new Date - $config._start) + 'ms');
return $config.success();
}
setTimeout(waitFor, $config.interval || 0, $config);
}
Example of use:
waitFor({
debug: true, // optional
interval: 0, // optional
timeout: 1000, // optional
check: function () {
return page.evaluate(function() {
return $('#thediv').is(':visible');
});
},
success: function () {
// we have what we want
},
error: function () {} // optional
});
It's a little easier when you use a config variable.
Solution 2:
PhantomJS runs asynchronously by default, causing problems like the one you describe above (where the script finishes before your results are ready)
However there is nothing to stop you using it in a synchronous way.
Just use phantom.page.sendEvent('mousemove')
in a while loop. This will keep looping through the event pump until the webkit engine loads your page or processes any necessary browser events.
var page = require('webpage').create();
// Step 1: View item
page.open('http://localhost/item3324.php');
do { phantom.page.sendEvent('mousemove'); } while (page.loading);
page.render('step1-viewitem.png');
// Step 2: Add to cart
page.evaluate(function() {$('#add-to-cart').click(); });
do { phantom.page.sendEvent('mousemove'); } while (page.loading);
page.render('step2-viewcart.png');
// Step 3: Confirm contents
page.evaluate(function() {$('#confirm-cart').click(); });
do { phantom.page.sendEvent('mousemove'); } while (page.loading);
page.render('step3-confirm.png');
Note that page.loading
can also be any other boolean condition, for example:
do { phantom.page.sendEvent('mousemove'); }
while (page.evaluate(function() {return $("#panel").is(":visible");}));
I discovered this approach while working on the triflejs.org project (the Internet Explorer version of phantom) trying to emulate calls to trifle.wait(ms)
inside the PhantomJS environment.
Solution 3:
My approach for this scenario is to wait until "something" is done or true. I highly suggest you to test waitfor.js.
demo.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<title>Test</title>
</head>
<body id="body">
<div id="thediv">Hello World !</div>
<script type="text/javascript">
$('#thediv').hide();
setTimeout(function () {
$('#thediv').show();
}, 3000);
</script>
</body>
</html>
demoscript.js
var page = require('webpage').create();
var system = require('system');
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 5000, //< Default Max Timout is 5s
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");
typeof (onReady) === "string" ? eval(onReady) : onReady();
clearInterval(interval);
//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
}
}
}, 500); //< repeat check every 500ms
};
if (system.args.length != 1) {
console.log('invalid call');
phantom.exit(1);
} else {
//adapt the url to your context
page.open('http://localhost:40772/demo.html', function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit();
} else {
waitFor(
function () {
return page.evaluate(function () {
return $('#thediv').is(':visible');
});
},
function () {
page.render('page.png');
phantom.exit();
}, 5000);
}
});
}
This script evaluate $('#thediv').is(':visible')
(classic Jquery code) every 500 ms to check if the div is visible.
Solution 4:
Inside page.evaluate(), use the self.loading property to test for doneness ....
var fs = require('fs');
path = '/path/to/file.html';
address = 'http://google.com';
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to access page');
} else {
var p = page.evaluate(function () {
if(!self.loading){ // ah, such beauty
return document.documentElement.outerHTML;
}
});
fs.write(path, p, 'w');
}
phantom.exit();
});