2

I'm trying to do page automation with PhantomJS. My goal is to be able to go to a website, click an image, and continue with other code once the page has loaded from the click. To test this I'm trying to write a script that will go to the url of the quick start guide on the PhantomJS website and then click on the PhantomJS logo bringing the page to the PhantomJS homepage. Also to render a picture of the website before and after the click to make sure the click worked. This is my current code:

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

page.open('http://phantomjs.org/quick-start.html', function(status) {
console.log(status);
page.render('websiteBeforeClick.png');
console.log(page.frameUrl); //check url before click

var element = page.evaluate(function() {
  return document.querySelector('img[alt="PhantomJS"]');
});

page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');

window.setTimeout(function () {
console.log(page.frameUrl); //check url after click
}, 3000);

console.log('element is ' + element); //check that querySelector() is returning an element
page.render('websiteAfterClick.png');
phantom.exit();
});

Problem is my before and after pictures are the same. This is my output when I run it.

 success
 element is [object Object]

Im using their sendEvent method from here "http://phantomjs.org/api/webpage/method/send-event.html" but I'm not sure if its working.

Also why doesnt the console.log(page.frameUrl) in my window.setTimeout() get executed?

I was looking at their page automation examples on the PhantomJS website. Particularly this one "https://github.com/ariya/phantomjs/blob/master/examples/imagebin.js". I noticed their examples used

document.querySelector('input[name=disclaimer_agree]').click()

But when I tried it with my code I got an error.

document.querySelector('img[alt="PhantomJS"]').click();
TypeError: 'undefined' is not a function

EDIT#1:

I changed the end section of my code to this:

page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');


window.setTimeout(function () {
  console.log(page.frameUrl);
  page.render('websiteAfterClick.png');
  phantom.exit();
}, 3000);

console.log('element is ' + element);
});

Now my after image is correct. But now my question is, If I want to continue on with my code i.e. click on another element on the site, will my new code have to be all nested inside of the timeout function?

user3803788
  • 37
  • 1
  • 2
  • 6
  • `Also why doesnt the console.log(page.frameUrl) in my window.setTimeout() get executed?` because you `phantom.exit()` before it has a chance to execute. – Matt Burland Jul 10 '14 at 19:31
  • @MattBurland Where should I place my phantom.exit() or my window.setTimeout() to achieve my desired outcome. – user3803788 Jul 10 '14 at 20:14
  • Looks like you figured that out already. As for additional timeouts: one approach is to nest them, but obviously that can become unwieldy. Another approach would be to start them all at the same time, but have a final timeout with the phantom.exit that is long enough to let all the others complete, but this is a little dicey if one of them is slow. The better option would be to use something like [jquery's deferred](http://api.jquery.com/category/deferred-object/) so you can tie them all together and wait for them all. – Matt Burland Jul 10 '14 at 21:10

2 Answers2

5

There is an example function phantom.waitFor(callback) that I explain on the following post, it goes as follows:

phantom.waitFor = function(callback) {
  do {
    // Clear the event queue while waiting.
    // This can be accomplished using page.sendEvent()
    this.page.sendEvent('mousemove');
  } while (!callback());
}

This can help streamline your code and avoid nested calls to window.setTimeout(), which are not very reliable anyway as you are waiting for a pre-set amount of time instead of waiting for the element to become visible. An example would be as follows:

// Step 1: Open and wait to finish loading
page.open('http://localhost/');
phantom.waitFor(function() {return !page.loading;});

// Step 2: Click on first panel and wait for it to show
page.evaluate(function() { $("#activate-panel1").click(); });
phantom.waitFor(function() {
   return page.evaluate(function() {return $("#panel1").is(":visible");})
});

// Step 3: Click on second panel and wait for it to show
page.evaluate(function() { $("#activate-panel2").click(); });
phantom.waitFor(function() {
   return page.evaluate(function() {return $("#panel2").is(":visible");})
});
console.log('READY!');
phantom.exit();

This will load each panel in succession (ie synchronously) while keeping your code simple and avoiding nested callbacks.

Hope it makes sense. You could also use CasperJS as an alternative, its aimed at making this stuff simpler.

Community
  • 1
  • 1
Steven de Salas
  • 20,944
  • 9
  • 74
  • 82
0

Yes, your new code will be called from inside of the setTimeout callback. You can nest the code directly or write a function which capsules the code for you and call that function inside setTimeout.

function anotherClick(){
    // something
}

page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');

window.setTimeout(function () {
  console.log(page.frameUrl);
  page.render('websiteAfterClick.png');
  anotherClick();
  phantom.exit();
}, 3000);

There is another way. You can also write it completely with multiple setTimeout, but then you cannot react to sudden conditions in previous calls.

page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');

window.setTimeout(function () {
  console.log(page.frameUrl);
  page.render('websiteAfterClick.png');
}, 3000);

window.setTimeout(function () {
  // some more actions
}, 6000); // you cannot know if this delay is sufficient

window.setTimeout(function () {
  phantom.exit();
}, 9000); // you cannot know if this delay is sufficient

I would suggest using CasperJS, if you want to do many actions/navigation steps.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222