Capturing screenshots with Mink, Sahi and PhantomJS

Following on from my previous post Headless Behat/Mink testing with Sahi and PhantomJS, I wanted to complete the final part of Ryan’s post concerning capturing a screenshot of the page which has failed a test. I had hoped this would be fairly simple, but the march of technology has made Ryan’s post almost obsolete. Also, I am not  fan of his technique, with involves injecting an element into the page from the Phantom setup script. This causes problems if your tests want to move between pages.

Setting up Phantom

The additions required to the Phantom script I used in the last post are fairly minimal, just the last four lines:

if (phantom.args.length === 0) {
console.log(‘Usage: sahi.js ‘);
} else {
var address = phantom.args[0];
console.log(‘Loading ‘ + address);
var page = new WebPage();, function(status) {
if (status === ‘success’) {
var title = page.evaluate(function() {
return document.title;
console.log(‘Page title is ‘ + title);
} else {
console.log(‘FAIL to load the address’);
// add callback listener to catch window.callPhantom() in the page
page.onCallback = function(data) {

In my opinion this is much cleaner than injecting an element into the page, although it does come with the caveat that the onCallback event is still considered experimental in PhantomJS 1.7. The onCallback handler is triggered when the loaded page makes a call to window.callPhantom(), which is going to be done when Mink fails a step. The call to page.render() will generate an image of the page at the point of the fail. It is no longer necessary to set an arbitrary viewport size first, the image will be the correct size for the page.

Hooking up Mink

As Ryan did, I created a hook in Mink to fire after a step completes. Hooks in Behat are now implemented as functions, with an annotation to set the target point.

public function afterStep(StepEvent $event)
* @AfterStep
$context = $event->getContext();
if ($context->getMinkParameter(‘browser_name’) == ‘phantomjs’ && $event->getResult() == StepEvent::FAILED) {

This is a bit different to Ryan’s code, although the intention is the same. The event object no longer contains an environment; instead I grab a reference to the context, which is the instance of the FeatureContext class. If PhantomJS is the browser and the step failed then executeScript() calls Sahi’s _call() function to trigger the call the Phantom from the page.

Now all it takes is to write a failing test and watch your screenshot appear.


  • Pingback: shanethehat » Integrating Behat and Mink with JenkinsCI

  • Jesse Sutherland
    11th April 2014 - 4:59 pm | Permalink

    Hi Shane, thanks for the great posts. I was investigating along similar lines (after having abandoned Zombie in favour of Phantomjs) but with the advantage of working about a year later.

    Only a month after this post, Phantom 1.8 ( was released, which included Ghostdriver. This uses the standard JSON Wire protocal to run WebDriver (ie Mink/Selenium in my case) so you can send standard (like getScreenshot) commands via Mink.

    Super handy. So now, my feature has a step wherein I’m checking for a UI element and I can take a screen of it without needing the callback like so:

    file_put_contents('/tmp/test-auto-complete'. mt_rand() .'.png', $this->getSession()->getDriver()->getScreenshot());

    Not too shabby eh?

  • Leave a Reply

    Your email address will not be published. Required fields are marked *