Photo of David Winter

david winter

Testing Javascript websites with Behat

This is my third blog post on how to use Behat for your web application testing. If you haven’t already, be sure to read Getting started with Behat and Using Behat with Mink as this post continues on where they left off.

In my previous post I left you with being able to create your own custom, reusable definitions in Behat. We’ve touched on the real basics of testing with Behat on a very simple example website. However, in 2012, you’ll find it very hard to find a website that doesn’t have some kind of Javascript included providing a dynamic and better user experience for visitors. It’s crucial that we’re able to test and guarantee that the animations and ajax requests that the users interact with work as we expect.

In this blog post, we’ll be using the twitter bootstrap example site. It has a variety of different JS related widgets and interactions which are perfect for explaining some of the basic Javascript testing concepts when it comes to Behat. Here is the behat.yml configuration file being used:

default:
  context:
    parameters:
      base_url: http://twitter.github.com/bootstrap/

Now take a look at the following example test:

Feature: Modal dialog

	Scenario: Open modal dialog
		Given I am on "/javascript.html"
		And I should see "Launch Modal"
		When I press "Launch Modal"
		Then I should see "One fine body…"

If you visit the modals section on the example site, there is a red button under the demo heading. Clicking that opens a modal box with the text ‘One fine body…’. That’s what our above test is for. Run that test and you’ll probably see:

Feature: Modal dialog

  Scenario: Open modal dialog            # features/modal.feature:3
    Given I am on "/javascript.html"     # FeatureContext::visit()
    And I should see "Launch Modal"      # FeatureContext::assertPageContainsText()
    When I press "Launch Modal"          # FeatureContext::pressButton()
      The selected node does not have a form ancestor.
    Then I should see "One fine body…" # FeatureContext::assertPageContainsText()

1 scenario (1 failed)
4 steps (2 passed, 1 skipped, 1 failed)

Great. Test failure. But we know it should work because we just did the exact same steps our test has in our browser. Why did it fail?

Behat by default uses a headless driver called Goutte for all tests. It’s a very fast driver, but it doesn’t support Javascript. As the official documentation for Mink so perfectly states:

Those browser emulators send a real HTTP requests against an application and parse the response content. They are very simple to run and configure, because this type of emulators can be written in any available programming language and can be run through console on servers without GUI. Headless emulators have both, advantages and disadvantages. Advantages are simplicity, speed and ability to run it without the need in real browser. But this type of browsers have one big disadvantage - they have no JS/AJAX support. So, you can’t test your rich GUI web applications with headless browsers.

We need to use a different type of driver for our Javascript-dependant tests. These type of drivers are called ‘browser controllers’. They launch the browser you designate and then it does exactly what your tests say. This means your tests have the full Javascript support of your browser behind them.

The easiest of these drivers to get setup and running is Selenium2Driver. We’re going to use this with Google Chrome to get our tests passing. Thanks to @ColonelRosa for pointing out Selenium2 support in Mink 1.3.

First of all you’ll need the chromedriver on your machine, and have it available in your path. You’re able to download the driver from the Google code project page. Or if you’re on a Mac and have homebrew installed, simply run brew install chromedriver.

The last thing you’ll need is the WebDriver. Go to http://seleniumhq.org/download/ and then you want to choose the ‘Selenium Server’. As of writing, it’s version 2.16.1. This will download a Java .jar file. Once you have this, simply run the following command in the terminal. It’s good idea to have this run in it’s own window or tab, because it must be running while the tests are active:

java -jar /path/to/selenium-server-standalone-2.16.1.jar

You now have all the required tools.

Now we just need to update our behat config, and our tests slightly. Update your behat.yml to contain:

default:
  context:
    parameters:
      base_url: http://twitter.github.com/bootstrap/
      browser: chrome
      javascript_session: webdriver

This is telling behat that for our javascript tests, we want it to use the WebDriver driver (Selenium2). Also, that it is to use Google Chrome as the browser the driver controls.

Now, we need to simply mark our test as being Javascript dependant, which is as easy as adding a @javascript tag above the Scenario:

Feature: Modal dialog

	@javascript
	Scenario: Open modal dialog
		Given I am on "/javascript.html"
		And I should see "Launch Modal"
		When I press "Launch Modal"
		Then I should see "One fine body…"

Now run behat.

Damn. The tests are still failing. This is because when the button is pressed, Javascript animates the opening of the modal window, and hasn’t completed by the time the next step runs searching for the text. The animation hasn’t completed, and therefore the step fails.

Well, we can get the test passing, and also add an additional definition that will make testing modals easier in the future. Lets update the scenario to the following:

	@javascript
	Scenario: Open modal dialog
		Given I am on "/javascript.html"
			And I should see "Launch Modal"
		When I press "Launch Modal"
		Then I should see the modal "Modal Heading"
			And I should see "One fine body…"

We’ve added a new definition I should see the modal "Modal Heading". This will allow us to write other tests that depend on modal dialogs opening with various headings. Run behat to get the skeleton definition method and add it to your FeatureContext.php file:

/**
 * @Then /^I should see the modal "([^"]*)"$/
 */
public function iShouldSeeTheModal($title)
{
	$this->getSession()->wait(20000, '(0 === jQuery.active && 0 === jQuery(\':animated\').length)');
	$this->assertElementContainsText('#modal-from-dom .modal-header h3', $title);
	assertTrue($this->getSession()->getPage()->find('css', '#modal-from-dom')->isVisible());
}

I’ll run through this method now line-by-line:

  1. We use the wait method to allow us to wait for the animation to complete. The first parameter sets 20 seconds to wait or until the following Javascript evaluates to true. We test to ensure that all Jquery ajax calls have completed, and that no elements are being animated.
  2. We want to check that the heading portion of the modal dialog contains the heading text that we pass through in the step. In our test, this is “Modal Heading”. So we use a CSS selector to do this.
  3. Finally, we just want to be sure that the modal dialog is actually visible on the page.

That’s it. One final change we can make is to move our Jquery wait check code into it’s own method so that we can use this in any other custom definitions we may create in future:

protected function jqueryWait($duration = 1000)
{
		$this->getSession()->wait($duration, '(0 === jQuery.active && 0 === jQuery(\':animated\').length)');
}

/**
 * @Then /^I should see the modal "([^"]*)"$/
 */
public function iShouldSeeTheModal($title)
{
	$this->jqueryWait(20000);
	$this->assertElementContainsText('#modal-from-dom .modal-header h3', $title);
	assertTrue($this->getSession()->getPage()->find('css', '#modal-from-dom')->isVisible());
}

Now run behat, and you should get a perfect test score:

1 scenario (1 passed)
5 steps (5 passed)

Example code used in this post can be found on Github.