Tips for taking screenshots with PhantomJS + CasperJS

February 5, 2014 – 7:38 pm Tags: ,

It’s quite easy to use PhantomJS to muck around with pages, especially if you use CasperJS which provides you a bit nicer API.

It’s also pretty easy to take screenshots, but there’s a few things you need to take into account if you want the results to be accurate…

Image loading

If the page contains images (a very common thing you might find!), you need to take some steps to ensure the images are fully loaded before taking a screenshot.

Depending on the circumstances, you might be able to get away by simply using an onload listener. However, when that doesn’t suffice, you have to resort to something slightly more complex.

There are two ways you can use to detect whether all images have been loaded:

  • Check for img.complete
  • Use the img.onload event

You may be able to get away with a simple waitFor condition:

casper.waitFor(function() {
    return this.evaluate(function() {    
        var images = document.getElementsByTagName('img');
        return Array.prototype.every.call(images, function(i) { return i.complete; });
    });
}, doSomething);

The above will wait until all images appear to have been succesfully loaded, but sometimes depending on the page in question, a different albeit slightly more complex approach may work better…

casper.evaluate(function() {
    var images = document.getElementsByTagName('img');
    images = Array.prototype.filter.call(images, function(i) { return !i.complete; });
    window.imagesNotLoaded = images.length;
    Array.prototype.forEach.call(images, function(i) {
        i.onload = function() { window.imagesNotLoaded--; };
    });
});
 
casper.waitFor(function() {
    return this.evaluate(function() {
        return window.imagesNotLoaded == 0;
    });
}, doSomething);

This approach reduces the amount of times your code will call getElementsByTagName, which can be useful in very image-heavy sites.

CSS animations

If the page in question has CSS animations and you wish to skip them, for example to make sure the page is in the end of the animations when you take a screenshot, you’ll just need to inject a little bit of CSS into the document:

casper.evaluate(function() {
    var style = document.createElement('style');
    style.innerHTML = '* { -webkit-animation-delay: 0.01s !important; -webkit-animation-duration: 0.01s !important; }';
    document.body.appendChild(style);
});

Unless the CSS on the page has been written poorly, the above CSS snippet will make sure any animations are finished almost instantly.

You may be tempted to simply put 0s instead of 0.01s, but I found a plain zero is unreliable and would sometimes screw up the animations entirely.

Dealing with dynamic elements

If you want to take screenshots of specific elements instead of the entire page, you need to provide Casper with a selector.

But what if the element has been generated dynamically, or you just don’t know its selector ahead of time?

The solution is simple: You can probably find a way to query for the element using querySelectorAll, so you can write something like follows:

casper.evaluate(function() {
    var els = document.querySelectorAll('.some-selector > div');
    var ids = 0;
    Array.prototype.forEach.call(els, function(el) { el.id = 'foo' + ids++; });
});

And there you go, now you have an easy way to tell what kind of selector you can use – Simply an ID in the format you assigned to th element. If you don’t know the number of elements, you could return the number from the evaluate call and use that.

In closing

These three cases were something that I ran into while implementing a tool to automatically do screengrabs of some pages. I hope this helps you avoid some of the pitfalls!

How to know ALL your code works with just the press of a button?

You've just finished adding an amazing new feature. Now all you have to do is to spend ages clicking through everything to make sure nothing broke...

What if you could just hit a button and know for sure the changes you did work perfectly and didn't break anything?

What if finding bugs was just a matter of seconds, instead of minutes, hours or even days?

 

Sign up for my newsletter, and I'll show you how you can use unit tests to solve these problems and more.

  • Which parts of the code are most important to test?

  • Real world examples of how to test even complex components with ease

  • How to automate testing so you can focus on writing code and feel confident old code still works

  • How to avoid bugs from repeating with regression testing

We will never sell your information or spam you, ever.

Share this:

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)