PhantomJS: setTimeout Still Not Working? - phantomjs

There are a lot of posts on the net documenting a serious bug in PhantomJS, in which the delay in setTimeout is ignored. Most of them are a few years old. I would have thought this bug had been fixed by now, but I appear to be encountering it:
Here's my PhantomJS test code, used with PhantomJS v2.1.1:
page.open(uri, function (status) {
var content = page.content;
console.log('page loaded');
if (status !== "success")
{
phantomResult = JSON.stringify({
"status": status,
"content": "page not opened"
});
console.log(phantomResult);
phantom.exit(1);
}
else
{
content = page.content;
page.onConsoleMessage = function (msg) { console.log(msg); };
console.log('about to try timeout test');
window.setTimeout(console.log('set timeout test'), 100);
console.log('about to try timeout test 2');
window.setTimeout(console.log('set timeout test 2'), 2000);
console.log('about to try timeout test 3');
window.setTimeout(console.log('set timeout test 3'), 4000);
window.setTimeout(console.log(phantom.exit(1)), 6000);
}
});
The console.log calls executed via setTimeout are executed immediately one right after the other, with no delay between them.
Has this bug not been fixed yet? I'm using OS X and calling phantomJS via the terminal.
Hopefully there's a fix for this! Thanks in advance to all for any info.

I fixed it by rolling my own setTimeout code:
var waitStart = new Date();
var done = false;
var timeNow;
var interval = 1000;
while (!done) {
timeNow = new Date();
done = timeNow - waitStart > interval;
//console.log('timeNow - waitStart = ' + (timeNow - waitStart));
}

Try this code:
window.setTimeout(function () { console.log('set timeout test') }, 100);

Related

Repeat Nightwatch test automatically

Does anyone know a way I can automatically rerun a Nightwatch test a set number of times?
I have the following code:
module.exports = {
'Log into system - create order': function (client) {
client
.url('XXXX')
.waitForElementVisible('body', 1000)
.assert.title('Reach - Log in')
.assert.visible('#UserName')
.setValue('#UserName', 'XXXX')
.assert.visible('#Password')
.setValue('#Password', 'XXXX')
.assert.visible('input[value="Login"]')
.click('input[value="Login"]')
.waitForElementVisible('img.test', 1000)
.assert.visible('li[title="XXXX"] a[tabindex="5"]')
.click('li[title="Sales"]')
.assert.cssClassPresent('li[title="XXXX"]', 'active')
.click('a[href="/Quotes/Add"]')
.waitForElementVisible('#s2id_CustomerId_Remote', 1000)
.click('#s2id_CustomerId_Remote')
.assert.visible('#s2id_autogen2_search')
.setValue('#s2id_autogen2_search', 'bik')
.waitForElementVisible('.select2-highlighted', 1000)
.click('.select2-highlighted')
.waitForElementVisible('#customerNotes', 1000)
.click('#s2id_ProductId_Remote')
.assert.visible('#s2id_autogen3_search')
.setValue('#s2id_autogen3_search', '123XP')
.pause(5000)
.assert.visible('.select2-highlighted')
.click('.select2-highlighted')
.pause(5000)
.assert.visible('.ui-sortable > tr')
.setValue('#Quote_PONumber', 'abc123')
.click('input[value="Create Order"]')
.waitForElementVisible('.ac-order-number', 1000)
.assert.visible('a[data-value="abc123"]')
.pause(5000)
.end()
}
}
rather than .end() the test I'd like to .rerun() the test say 30 times. I can't see an option to do this anywhere in the docs.
Many thanks in advance.
You can wrap your commands in a client.perform() and a for loop
client.perform(function(){
for (i = 0; i < 29; i++) {
client
.url('XXXX')
.
.
.
.end();
}
})
What you need is a little of async iteration logic and client.perform() function :
module.exports = {
'Log into system - create order': function (client) {
var currentIteration = 0,
iterationCount = 30;
function runTest() {
client
.url('XXXX')
// ... YOUR CODE HERE, WITHOUT .end()
.perform(function() {
if (++currentIteration < iterationCount) {
return runTest();
}
client.end(); // After passing 30 iterations end the session
});
}
runTest();
}
};
If you want to repeat he test with different inputs ? then you can do something like this
module.exports = {
"Login Fail Cases": function(browser) {
let dataSet = [
{ username: "madhus", pass: "madhus" },
{ username: "admin", pass: "admin" }
];
//will run for 2 times as length of dataset is 2
dataSet.forEach(function(data) {
browser
.url("https://localhost:3000/")
// you tests here
}, this);
// note: end the test outside the loop once all tests are executed
browser.end();
}
};

Click and open a new page in PhantomJS [duplicate]

Phantomjs has these two really handy callbacks onLoadStarted and onLoadFinished which allow you to essentially pause execution while the page is loading. But I've been searching and I can't find an equivalent for if you click() a submit button or hyperlink. A similar page load happens but onLoadStarted doesn't get called for this event I guess because there isn't an explicit page.open() that happens. I'm trying to figure out a clean way to suspend execution while this load takes place.
One solution is obviously nested setTimeout's but I'd like to avoid this scenario because it's hacky and relies on trial and error instead of something reliable and more robust like testing against something or waiting for an event.
Is there a specific callback for this kind of page load that I missed? Or maybe there's some kind of generic code pattern that can deal with this sort of thing?
EDIT:
I still haven't figured out how to get it to pause. Here's the code that doesn't call the onLoadStarted() function when I call the click() command:
var loadInProgress = false;
page.onLoadStarted = function() {
loadInProgress = true;
console.log("load started");
};
page.onLoadFinished = function() {
loadInProgress = false;
console.log("load finished");
};
page.open(loginPage.url, function (status) {
if (status !== 'success') {
console.log('Unable to access network');
fs.write(filePath + errorState, 1, 'w');
phantom.exit();
} else {
page.evaluate(function (loginPage, credentials) {
console.log('inside loginPage evaluate function...\n')
document.querySelector('input[id=' + loginPage.userId + ']').value = credentials.username;
document.querySelector('input[id=' + loginPage.passId + ']').value = credentials.password;
document.querySelector('input[id=' + loginPage.submitId + ']').click();
//var aTags = document.getElementsByTagName('a')
//aTags[1].click();
}, loginPage, credentials);
page.render(renderPath + 'postLogin.png');
console.log('rendered post-login');
I double checked that the id is correct. The page.render() will show that the information is submitted, but only if I put it in a setTimeout(), otherwise it renders it immediately and I only see the credentials inputted, before the page redirect. Maybe I'm missing something else?
I think the onLoadStarted and onLoadFinished functions are everything you need. Take for example the following script:
var page = require('webpage').create();
page.onResourceReceived = function(response) {
if (response.stage !== "end") return;
console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + response.url);
};
page.onResourceRequested = function(requestData, networkRequest) {
console.log('Request (#' + requestData.id + '): ' + requestData.url);
};
page.onUrlChanged = function(targetUrl) {
console.log('New URL: ' + targetUrl);
};
page.onLoadFinished = function(status) {
console.log('Load Finished: ' + status);
};
page.onLoadStarted = function() {
console.log('Load Started');
};
page.onNavigationRequested = function(url, type, willNavigate, main) {
console.log('Trying to navigate to: ' + url);
};
page.open("http://example.com", function(status){
page.evaluate(function(){
// click
var e = document.createEvent('MouseEvents');
e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
document.querySelector("a").dispatchEvent(e);
});
setTimeout(function(){
phantom.exit();
}, 10000);
});
It prints
Trying to navigate to: http://example.com/
Request (#1): http://example.com/
Load Started
New URL: http://example.com/
Response (#1, stage "end"): http://example.com/
Load Finished: success
Trying to navigate to: http://www.iana.org/domains/example
Request (#2): http://www.iana.org/domains/example
Load Started
Trying to navigate to: http://www.iana.org/domains/reserved
Request (#3): http://www.iana.org/domains/reserved
Response (#2, stage "end"): http://www.iana.org/domains/example
New URL: http://www.iana.org/domains/reserved
Request (#4): http://www.iana.org/_css/2013.1/screen.css
Request (#5): http://www.iana.org/_js/2013.1/jquery.js
Request (#6): http://www.iana.org/_js/2013.1/iana.js
Response (#3, stage "end"): http://www.iana.org/domains/reserved
Response (#6, stage "end"): http://www.iana.org/_js/2013.1/iana.js
Response (#4, stage "end"): http://www.iana.org/_css/2013.1/screen.css
Response (#5, stage "end"): http://www.iana.org/_js/2013.1/jquery.js
Request (#7): http://www.iana.org/_img/2013.1/iana-logo-header.svg
Request (#8): http://www.iana.org/_img/2013.1/icann-logo.svg
Response (#8, stage "end"): http://www.iana.org/_img/2013.1/icann-logo.svg
Response (#7, stage "end"): http://www.iana.org/_img/2013.1/iana-logo-header.svg
Request (#9): http://www.iana.org/_css/2013.1/print.css
Response (#9, stage "end"): http://www.iana.org/_css/2013.1/print.css
Load Finished: success
It shows that clicking a link emits the LoadStarted event once and NavigationRequested event twice, because there is a redirect. The trick is to add the event handlers before doing the action:
var page = require('webpage').create();
page.open("http://example.com", function(status){
page.onLoadFinished = function(status) {
console.log('Load Finished: ' + status);
page.render("test37_next_page.png");
phantom.exit();
};
page.onLoadStarted = function() {
console.log('Load Started');
};
page.evaluate(function(){
var e = document.createEvent('MouseEvents');
e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
document.querySelector("a").dispatchEvent(e);
});
});
If you need to do those things, maybe it is time to try something else like CasperJS. It runs on top of PhantomJS, but has a much better API for navigating web pages.
Use the high-level wrapper, nightmarejs.
You can easily click there and wait afterwards.
Here is the code (Examples section):
var Nightmare = require('nightmare');
new Nightmare()
.goto('http://yahoo.com')
.type('input[title="Search"]', 'github nightmare')
.click('.searchsubmit')
.run(function (err, nightmare) {
if (err) return console.log(err);
console.log('Done!');
});
More examples and API usage can be found at github
Here is my code based on some other answers. In my case, I didn't need to specifically evaluate any other javascript. I just needed to wait for the page to finish loading.
var system = require('system');
if (system.args.length === 1) {
console.log('Try to pass some arguments when invoking this script!');
}
else {
var page = require('webpage').create();
var address = system.args[1];
page.open(address, function(status){
page.onLoadFinished = function(status) {
console.log(page.content);
phantom.exit();
};
});
}
Save the above in a file called "scrape.js" and call it this way:
phantomjs --ssl-protocol=any --ignore-ssl-errors=true scrape.js https://www.example.com
The SSL-related params are added to avoid other issues that I was having with certain HTTPS sites (related to certificate loading issues).
Hope this helps someone!

How to optimize PhantomJS for search engines to index a single page application?

I have been searching for an headless web browser that can run on server for web crawlers to index a single page application. Firslyt I tried HTMLUnit and Selenium (HtmlUnitDriver) but it seems both of them have issues with xhr requests.
And I discovered PhantomJS which performs better and seems mature. PhantomJS has an internal webserver so I decided to use it with my reverse proxy. However I ran a benchmark and PhantomJS hits a cpu core 100% and the average page loading the is around 4 seconds. The reason is I have to wait the browser to load all resources to be able to get correct results. Here is my PhantomJS script:
var page = require('webpage');
var system = require('system');
var server = require('webserver').create();
// credit: http://backbonetutorials.com/seo-for-single-page-apps/
var service = server.listen(port, { 'keepAlive': true }, function(z, response) {
var request = page.create();
var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();
request.onResourceReceived = function (response) {
if (requestIds.indexOf(response.id) !== -1) {
lastReceived = new Date().getTime();
responseCount++;
requestIds[requestIds.indexOf(response.id)] = null;
}
};
request.onResourceRequested = function (request) {
if (requestIds.indexOf(request.id) === -1) {
requestIds.push(request.id);
requestCount++;
}
};
request.settings = {
loadImages: false,
javascriptEnabled: true,
loadPlugins: false
};
request.open(z.url, function (status, a) {
if (status !== 'success') {
console.log('FAIL to load the address '+a);
}
});
var checkComplete = function () {
var now = new Date().getTime();
if ((now - lastReceived > 300 && requestCount === responseCount) || now - startTime > 5000) {
clearInterval(checkCompleteInterval);
response.statusCode = 200;
response.headers = {
'Cache': 'no-cache',
'Content-Type': 'text/html; charset=UTF-8',
'Connection': 'Keep-Alive',
'Keep-Alive': 'timeout=5, max=100',
'Content-Length': request.content.length
};
response.write(request.content);
response.close();
request.release();
console.log(request.url+" -> "+(now - startTime));
}
}
var checkCompleteInterval = setInterval(checkComplete, 3);
});
Is there any improvement that can be done to speed up the script, should I just run PhantomJS using its shell command for better performance or is there any alternative to these browsers?
You can use some command line switches to improve the capture performance:
First, you can ignore all images with --load-images=no. There's no need to load images when doing the HTML snapshots.
You can also enable the cache with --disk-cache=yes (use --max-disk-cache-size to set its size in bytes)
Finally, the WebPage#onResourceRequested callback may also be useful to abort some requests (trackers, media files...) with the NetworkRequest#abort method.

phantomJS webpage timeout

I have set up a script to create webshots of our app.
It runs perfectly and all is fine Until I encounter an image with a broken url :
"<img src='http://testserver.our.intranet/fetch/image/373e8fd2339696e2feeb680b765d626e' />"
I have managed to break the script after 6 seconds using the below, Before it was just looping forever.
But, is it possible to ignore the network request (AKA take the image out of DOM) and then proceed to create the thumb without the image, (or with an injected image missing image !)
var page = require('webpage').create(),
system = require('system'),
address, output, size;
if (system.args.length < 3 || system.args.length > 5) {
phantom.exit(1);
} else {
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 640, height: 640 };
page.zoomFactor = 0.75;
page.clipRect = { top: 10, left: 0, width: 640, height: 490 };
try{
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit();
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
} finally{
setTimeout(function() {
console.log("Max execution time " + Math.round(6000) + " seconds exceeded");
phantom.exit(1);
}, 6000);
}
}
PhantomJS 1.9 has introduced a new setting, resourceTimeout, that controls how long a request can take before it gets cancelled. Along with that, there's a onResourceTimeout event that is triggered if/when a request times out.
Here's a code snippet illustrating all of the above:
var page = require('webpage').create();
page.settings.resourceTimeout = 5000; // 5 seconds
page.onResourceTimeout = function(e) {
console.log(e.errorCode); // it'll probably be 408
console.log(e.errorString); // it'll probably be 'Network timeout on resource'
console.log(e.url); // the url whose request timed out
phantom.exit(1);
};
page.open('http://...', function (status) {
...
}
Unfortunately those options are poorly documented right now. I had to go through GitHub discussions and the PhantomJS source code in order to find them out.

setinterval function not working titanium mobile

I have the following code:
var c = null;
c = Titanium.Network.createHTTPClient();
var url = 'example.com';
c.open('GET', url);
Titanium.API.log('info', url);
c.onload = function()
{
Titanium.API.log('info', 'onload');
setInterval(getPeople(), 5000);
};
c.send();
The code should access the getPeople() function every 5 seconds, but it does not. In fact, the application exists prematurely.
what Im I doing wrong?
Try either:
setInterval(getPeople, 5000);
or:
setInterval("getPeople()", 5000);