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 verify ALL your code works in just 10 seconds?

Imagine being able to hit a button, wait a few seconds, and know for sure that the changes you just did work perfectly and didn't break anything?

What if instead of having to pull your hair out to find bugs, you would know exactly where to look? 

 

Sign up for my newsletter, and I'll tell you how to stop spending so much time and frustration manually testing for problems in your JavaScript code:

  • Learn to write unit tests QUICKLY, so not having time to do it is not a problem

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

  • How to automate everything so you can focus on writing code and feel safe doing even big changes

  • How to set a process into place that makes sure you don't make the same mistake twice

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)