Headless Behat/Mink testing with Sahi and PhantomJS

Shane Auckland
shanethehat
Published in
4 min readNov 29, 2012

--

We have been using Behat for acceptance testing at work for a while now, but rather informally. I have been interested in it from a CI point of view, especially as our longer term plan is to automate the deployment of several sites from a single codebase. A barrier to this, and to our more recent desire to use Vagrant for controlling our dev environments, was the way in which we were running JavaScript tests using Selenium and Firefox. Today, I decided to cut off the head.

Getting Started

An obvious place to start was PhantomJS, a fully featured headless webkit browser. After a bit of searching around I came across a blog post by Ryan Grenz which seemed to be doing exactly what I was looking for. Unfortunately Ryan’s post is now a little out of date, so I suggest reading that first then coming back here for the updated information.

The versions I installed were:

Installing Sahi and PhantomJS are beyond the scope of this post, and individual experiences will vary based on your OS. Unlike Ryan, I decided to get everything running locally before remoting the server, so on Ubuntu I installed Sahi and PhantomJS to ~/Sahi/ and ~/bin/ respectively.

Installing Behat with Composer

We have decided to install Behat, Mink and the required extensions using Composer. Following the sometimes cryptic Behat documentation we ended up with a composer.json that looked like this:

[javascript]
{
“require”: {
“behat/behat”: “2.4.*@stable”,
“behat/mink”: “1.4.*@stable”,
“behat/mink-extension”: “*”,
“behat/mink-goutte-driver”: “*”,
“behat/mink-sahi-driver”: “dev-master”,
},
“minimum-stability”: “dev”,
“config”: {
“bin/dir”: “bin/”;
}
}
[/javascript]

Running this results in a vendor folder containing all of the Behat, Mink and extensions required to run the tests.

Configuring Behat

The next step is writing the YAML script to configure Behat. This is where the first discrepancy with Ryan’s example comes in; there is no longer an environment option for the Behat object. Instead the settings are added to the Mink extension:

[javascript]
default:
extensions:
BehatMinkExtensionExtension:
base_url: ‘http://www.google.co.uk'
javascript_session: sahi
browser_name: phantomjs
goutte: ~
sahi: ~
[/javascript]

The settings here are fairly simple. Sahi is set as the default driver for any JavaScript tests, and the browser is set to phantomjs.

Getting ready to use Phantom

Since we’ve just set Sahi to use a browser that isn’t defined, we’d better define it. Ryan’s addition to Sahi’s browser_types.xml works as is, but I’ll include it again here, with the paths changed for my OS.

[xml]
<browserType>
<name>phantomjs</name>
<displayName>PhantomJS</displayName>
<icon>safari.png</icon>
<path>/home/shanethehat/bin/phantomjs</path>
<options> — proxy=localhost:9999 /home/shanethehat/myapp/behat/phantom-sahi.js</options>
<processName>phantomjs</processName>
<capacity>100</capacity>
<force>true</force>
</browserType>
[/xml]

There is a small gotcha to be aware of here: if you have never run Sahi before, the browser_types.xml file will not exist. If you can’t see it in your Sahi installation folder, in userdata/config/, then go into userdata/bin/ and run the version of start_dashboard that fits your OS, and it will be magically created.

The final bit of required setup is the phantom-sahi.js script that allows requests to be passed into PhantomJS. Again, Ryan’s example is out of date on this point; something that caused me more than an hour of pain earlier today! This is the more recent example from the Sahi documentation, which works fine for me:

[javascript]
if (phantom.args.length === 0) {
console.log(‘Usage: sahi.js <Sahi Playback Start URL>’);
phantom.exit();
} else {
var address = phantom.args[0];
console.log(‘Loading ‘ + address);
var page = new WebPage();
page.open(address, 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’);
}
});
}
[/javascript]

And finally

All that’s left is to write some JavaScript based Behat tests and set them running. Here is a rather self-indulgent test script:

[text]
Feature: Do a Google search
In order to find my blog posts
As a user
I want to be able to use google.com to locate search results

@javascript
Scenario: I want to search for a blog post
Given I am on “/”
And I fill in “q” with “Exploring SOAP in Zend Framework 2”
When I press “Google Search”
Then I should see “shanethehat » Exploring SOAP in Zend Framework 2”
[/text]

All being well, you should see each step of the test run in the terminal, and the end output should be something like this:

[text]
Feature: Do a Google search
In order to find my blog posts
As a user
I want to be able to use google.com to locate search results

@javascript
Scenario: I want to search for a blog post # features/user.feature:7
Given I am on “/” # FeatureContext::visit()
And I fill in “q” with “Exploring SOAP in Zend Framework 2” # FeatureContext::fillField()
When I press “Google Search” # FeatureContext::pressButton()
Then I should see “shanethehat » Exploring SOAP in Zend Framework 2” # FeatureContext::assertPageContainsText()

1 scenario (1 passed)
4 steps (4 passed)
0m3.696s
[/text]

--

--