You're probably familiar with PhantomJS's use in headless testing of web applications. It's used for launching tests for Capybara, Jasmine and other test frameworks.
But wait, there's more!
Since PhantomJS is using WebKit, a real layout and rendering engine, it can capture a web page as a screenshot. Because PhantomJS can render anything on the web page, it can be used to convert contents not only in HTML and CSS, but also SVG and Canvas*
This post will provide one approach to building out a screen cap feature in a Rails application, using PhantomJS
Let's start by building out our screen capturing script, then we'll take a look at integrating it with Rails.
Screen Cap Script
Usage
Initially, our script should be able to be used from the command line. Let's say we want to capture a screen shot of this sick cat website that you found while trying to buy coffee from clowder.nyc.
We should be able to use our script like this:
phantomjs screen-cap-script.js http://www.clowder.com clowder-pic.png
The first argument is the path to our screen cap script, the second argument is the site we want to take a screen cap of, and the third argument is the name for the screen cap file we will create and save.
Let's get started.
Building the Script
Our script needs to require Phantom's Web Page module, and use it to create a new instance of Web Page:
var page = require('webpage').create();
We also need to require the System module, so that our script can accept arguments from the command line:
var page = require('webpage').create();
var system = require('system');
Next up, we'll define an onError
function for our page
instance, so that it returns an helpful error message if our screen cap fails:
page.onError = function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
console.error(msgStack.join('\n'));
};
Okay, now we're ready to write the core of that screen cap script!
page.open(system.args[1], function(status) {
console.log('Status: ' + status);
page.render(system.args[2]);
phantom.exit();
});
Here, we use the page.open()
function and pass it the arguments captured from the command line and stored in system.args
. Recall that the second command line argument, i.e. the argument stored at index 1 of system.args
, is the URL of the web page we want to capture.
The line to pay attention to is:
page.render(system.args[2])
This is the line that actually captures the screen, saving it to a file specified in this argument. Recall that system.args[2]
stores the third argument we gave to our script in the command line––the name of the file we want to store the screen shot as.
Lastly, we exit the script with phantom.exit()
.
This script will save the screen cap PNG to the same directory from which you are running the script.
Now that our screen cap script is up and running, let's take a look at one approach to integrating it with a Rails application.
Screen Cap Script + Rails
We'll be running our screen cap script from a controller action that responds to the user's click of a "take screen cap" button.
# some controller
def take_screencap
# use screen cap script here
end
Keep in mind that the screen cap file will be saved to the same directory as the directory from which we run the script. We want our screen caps to be stored in the public/images
folder. So, we'll need to change directories before we run the script.
# some controller
def take_screencap
Dir.chdir(Rails.root.join('public', 'images'))
end
Now we can run our script as a system
command, giving it the appropriate arguments:
def take_screencap
Dir.chdir(Rails.root.join('public', 'images'))
system "phantomjs #{PATH_TO_PHANTOM_SCRIPT}
#{params["screencap_url"]
#{params["screencap_url"]}.png"
end
But wait! Where is our Phantom script? Let's keep it in app/assets/javascripts/screencap.js
PATH_TO_PHANTOM_SCRIPT = Rails.root.join('app', 'assets', 'javascripts', 'screencap.js')
def take_screencap
Dir.chdir(Rails.root.join('public', 'images'))
system "phantomjs #{PATH_TO_PHANTOM_SCRIPT}
#{params["screencap_url"]
#{params["screencap_url"]}.png"
end
And that's it!