af83

Acceptance tests on a PHP project with the cucumber / webrat / selenium trio

And just to be nice, here is an English version of this tutorial…

Introduction

This tutorial will guide you through installing and configuring an acceptance test environment for PHP project. For this we use several ruby gems, including cucumber, webrat and selenium-client, and write our scripts as cucumber features. Here's an example:

Feature: Example Features

  Scenario: I am connected and i visit my page
    Given I am connected as "username password"
    When I visit "home page"
    Then I should see "Hello username"

The advantage of this syntax is that it remains very readable for the mere mortals, no need to delve into the ruby code to understand what the test is supposed to do.

Each "sentence" in this scenario refers to a step which is the user actions necessary to perform that action in the user interface.

The user actions can be triggered by the webrat and selenium-client libraries. Webrat is particularly suited to interactions not involving javascript while selenium-client will interpret them correctly.

Installation

For this example we will use a debian system.

Let's start by installing rubygems and the needed gems.

aptitude install rubygems
gem install rspec cucumber webrat selenium-client

To run the binaries packaged with gems from the command line you must add the path to these binaries to your PATH:

export PATH=$PATH:$HOME/.gem/ruby/1.8/bin

To use selenium, start a selenium-rc server (ie: remote control). Starting selenium server is done automatically by webrat, however the version used caused me many problems with recent versions of Firefox. I suggest you grab the latest stable version at the following address:

http://seleniumhq.org/download/ (1.0.3 au 28 juin 2010).

It is sufficient to replace the file ~/.gem/ruby/1.8/gems/webrat-0.7.1/vendor/selenium-server.jar with the version you just downloaded.

wget http://selenium.googlecode.com/files/selenium-remote-control-1.0.3.zip
unzip selenium-remote-control-1.0.3.zip
cp selenium-server-1.0.3/selenium-server.jar $GEM_HOME/webrat-0.7.1/vendor/selenium-server.jar

(Remplace $GEM_HOME by your gems installation directory.)

Voila, now we can start writing our scenarios.

Note for iceweasel: you must add the path to the folder containing the iceweasel application.ini file to your PATH otherwise

iceweasel will not start.

export PATH=$PATH:/usr/lib/iceweasel

Configuration

Now that the installation is complete, we will have to configure a web environment to run our tests. This environment can be your development environment, the approach is specific to each project so I will not dwell on this point.

Let's turn to the configuration of our test environment. We will create a directory features in the root of your project. This directory has the following structure:

$: features % tree
.
├── fixtures
│   └── image.jpg
├── home.feature
├── step_definitions
│   ├── user_steps.rb
│   └── webrat_steps.rb
└── support
    ├── env.rb
    ├── paths.rb
    └── selenium.rb

3 directories, 10 files

Let us see more precisely what role each of these directories is.

The root of the features will contain your cucumber "features", ie scenarios.

fixtures contains the files used in your scripts (eg an image to upload to a file field).

The step_definitions stores definitions of steps used in the scenarios. It will usually contain a file per action type, in our example we have two files:

  • webrat_steps: Common actions, this file can be found in the code of the webrat gem (templates/skeleton/step_definitions/webrat_steps.rb.erb). It contains, among other common helpers to any type of web project (visit a page, check that it contains a text or an html tag, ...)
  • user_steps : the user actions (login, post a comment ...)

The support is the configuration directory, it must contain the following files:

  • env.rb, the webrat configuration file
  • path.rb, is the file that defines the correspondence between the named routes that you call and the actual URL to send to the browser (example: "home page" => "/")
  • selenium.rb, is the file where you can refactor out the specific actions to selenium.

Webrat

By default webrat does not use selenium to control the browser but uses instead mechanize, here is a sample configuration file to be placed in the support/env.rb to specify the use of selenium: (Note in passing that the address of your test web environment will be configured via an environment variable for simplicities sake):

unless ENV['CUCUMBER_HOST']
  raise 'You must set CUCUMBER_HOST environment variable with the name of your host used to run cucumber features'
end

# RSpec
require 'rspec/expectations'

# Webrat
require 'webrat'

require 'test/unit/assertions'
World(Test::Unit::Assertions)

Webrat.configure do |config|
  #config.mode = :mechanize
  config.mode = :selenium
  config.application_framework = :external
  config.application_address = ENV['CUCUMBER_HOST']
  config.application_port = "80"
end

World do
  session = Webrat::Session.new
  session.extend(Webrat::Methods)
  session.extend(Webrat::Matchers)
  session
end

Named Routes

Next you must specify the routes that can be called in your tests and their text versions. Indeed, in cucumber scenarios , a literary correspondence will be much more readable than the raw URL. Here is an example configuration of these routes to be stored in the file support/path.rb :

module NavigationHelpers
  def path_to(page_name)
    case page_name
    when /home/
      '/'
    when /accueil/
      '/accueil'
    when /login/
      '/identification'
    else
      raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
        "Now, go and add a mapping in #{__FILE__}"
    end
  end
end

World(NavigationHelpers)

Helpers

In order to have the scenarios as clear and as understandable as possible, we will define some helpers. All files in the support will be loaded by cucumber in our example we will add a file selenium.rb for actions related to selenium.

Here is the support/selenium.rb file:

def wait_for_ajax(timeout=50000, request_count = 1)
  request_count.times do |index|
    selenium.wait_for_ajax :javascript_framework => :jquery
  end 
end
def wait_for_gritter
  selenium.wait_for_element "gritter-notice-wrapper"
end

You can do the same for other libraries you might need.

Writing scripts

The scenarios are defined by several cucumber keywords: Given, When, Then and And.

  • Given defines the initial context
  • When defines user actions
  • Then defines an assertion, the expected result
  • And is an association word to make the scenatio more readable, it has the same role as the keyword of the previous line.

Some actions will be called in several scenarios, hence the need for code refactoring. For example, the action Given I am connected as "username password" will certainly be present in 75% of your tests. To avoid repeating the X actions necessary to connect, we refactored that in the steps of the step_definitions directory.

Here is an example of steps for the following example:

Given /^I am connected as "(\w)\ (\w)"$/ do |login, password|
  visit path_to "login"
  fill_in 'login', :with =>  login
  fill_in 'password', :with => password
  click_button "login"
  wait_for_ajax # if your form is submitted via ajax
end 

Now in each of our tests we can call: Given I am connected as "MyUsername MyPassword".

Feature: Example Features

  Scenario: I am connected and i visit my page
    Given I am connected as "username password"
    When I visit "home page"
    Then I should see "Hello username"

Run !

Now that you've written your first scenario, go to the root of your project and run the following command:

CUCUMBER_HOST=YOUR_HOST cucumber

(Replace YOUR_HOST by your test enviroment address.)

You should see your browser run your script … hands free!

blog comments powered by Disqus