Francisco Souza: High level acceptance testing in your PHP applications using Python, Lettuce and Selenium
Posted on | July 29, 2010 | No Comments
Usually, in PHP applications, I don't run acceptance tests. Lately, I have been thinking about how can I change this. I am a little experienced with others acceptance tests frameworks, for Python (Lettuce) and Ruby (Cucumber), but there is nothing like Lettuce and/or Cucumber in PHP world. So, what can I do? I can just use Lettuce or Cucumber to test any application, including PHP or Java application. In this blog post, I will show how to use behaviour-driven development (BDD) behind Lettuce to write acceptance tests for PHP applications.Before start to coding, let's build up our environment, with Python, Lettuce and Selenium. If you use Linux, Mac OS, FreeBSD or any other Unix-like operating system, Python is already installed in your system. So, let's install Lettuce and Selenium.
Installing Lettuce
To install Lettuce from source code, you can clone the repository from github and install it using setuptools:
$ git clone git://github.com/gabrielfalcao/lettuce.git
$ cd lettuce
$ [sudo] python setup.py installIf you use pip or easy_install, you can also install Lettuce with the following commands:
$ [sudo] easy_install lettuce$ [sudo] pip install lettuceInstalling Selenium
The code used in this blog post was written for Selenium 2.0, wich is still alfa. So, to install Selenium, you need to checkout the SVN repository and run the setup.py install command:
$ svn checkout http://selenium.googlecode.com/svn/trunk/ selenium
$ cd selenium
$ [sudo] python setup.py installWith both Selenium and Lettuce installed, let's code.
Let's code! :)
The example will be very simple: a feature called "Login" with two scenarios: "Successful login" and "Unsuccessful login". See the feature "code" above, and understand it:
Feature: Login
In order to have unlimited powers
As a system anonymous user
I want to login in the system
Scenario Outline: Successful and unsuccessful login
Given that there is a user registered in the system with the username "admin" and the password "123"
When I navigate to the login page
And fill the username field with <login>
And fill the password field with <password>
And click the "Log in" button
Then I see the message <message>
Examples:
| login | password | message |
| admin | 123 | Welcome to admin |
| admin | 1234 | Error. Verify your username and password |
Put this feature file (login.feature) inside a directory called feature in the root of your application (or any other place).
Prepare the terrain and define the steps
According the Lettuce documentation, the second round is to define the steps described in the feature file. Before we define our steps, let's write a terrain.py Python module, which prepares the terrain to run our acceptance tests. The only things we need to do is setup a Selenium webdriver instance to test the content and the behaviour of our login page. Here is the terrain.py code:
from lettuce import before, after, world
from selenium import get_driver, FIREFOX
@before.all
def setup_browser():
world.browser = get_driver(FIREFOX)
world.root_url = 'http://localhost/php_app_test/'
@after.all
def teardown_browser(total):
world.browser.quit()
The terrain.py module should be in the features directory. Now we can define the steps from the scenario in a Python module called login_steps, and put this module inside a directory called step_definitions, in the features directory. Here is the directory tree:

And in this Python module, we define the steps:
# -*- coding: utf-8 -*-
from lettuce import *
@step(u'Given that there is a user registered in the system with the username "admin" and the password "123"')
def do_nothing(step):
'''There is nothing that Lettuce can do to guarantee this sentence'''
pass
@step(u'When I navigate to the login page')
def when_i_navigate_to_the_login_page(step):
login_url = world.root_url + 'login.html'
world.browser.get(login_url)
@step(u'And I fill the username field with (.*)')
def and_i_fill_the_username_field_with_login(step, login):
username_field = world.browser.find_element_by_xpath('//input[@name="username"]')
username_field.send_keys(login)
@step(u'And I fill the password field with (.*)')
def and_i_fill_the_password_field_with_password(step, password):
password_field = world.browser.find_element_by_xpath('//input[@name="password"]')
password_field.send_keys(password)
@step(u'And I click the "(.*)" button')
def and_i_click_the_group1_button(step, button_value):
button = world.browser.find_element_by_xpath('//input[@value="%s"]' %(button_value))
button.click()
@step(u'Then I see the message (.*)')
def then_i_see_the_message_message(step, message):
assert message in world.browser.get_page_source()
If you don't know Python, take a look at some free books (like Dive into Python and How to think like a computer scientist: Learning with Python) and learn it :)
Understanding the step definitions
Let's see more about the step definitions above. In the first step ("Given that there is a user registered in the system with the username "admin" and the password "123""), there is nothing to do: we cannot check and guarantee that there is a user registered in the system (unless we have access to the database, for example).
On the second step ("When I navigate to the login page"), we use the browser provided by Selenium WebDriver to go to the login page, in the URL http://localhost/php_app_test/login.html. You can see how to use the Selenium WebDriver in Selenium WebDriver docs.
The third step ("And fill the username field with <login>") is where we get the username text field by it's name and fill it with the login obtained from this step's sentence. We use the xpath syntax to find the field by it's name, and on the fourth step ("And fill the password field with <password>"), we get the password field by it's name and fill it with the password obtained from the fourth step sentence. The last browser interation is in the sixth step ("And click the "Log in" button"), when we get the "Log in" button and click it.
The last step ("Then I see the message <message>") asserts that the properly message appears on the screen when the user try to login with the right and wrong data.
The PHP code
Now, let's write the PHP code needed to make this specification work. I will not connect to a database and execute a complete login, we will just build a HTML form in the login.html page and make a simple PHP code to show the properly message in each case. The HTML code is here:
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>Login page</title>
</head>
<body id="">
<form action="login.php" method="post" accept-charset="utf-8">
<label for="id_username">Username: </label><input type="text" name="username" value="" id="id_username"/><br />
<label for="id_password">Password: </label><input type="password" name="password" value="" id="id_password"/>
<input type="submit" name="submit" value="Log in" id=""/>
</form>
</body>
</html>
And the
<?php
if ($_POST['username'] == 'admin' && $_POST['password'] == '123') {
echo 'Welcome to admin';
} else {
echo 'Error. Verify your username and password';
}
That was a really simple example that shows how to write high level tests to PHP applications, with Selenium WebDriver and Lettuce :) You can checkout the application code at Github: http://github.com/franciscosouza/php_app_test.
Category: Planet
Comments
Leave a Reply
Warning: include(/home/remarkwit/enterpriselamp.org/wp-content/themes/Enterprise_LAMP/r_sidebar.php) [function.include]: failed to open stream: No such file or directory in /home/remarkwit/enterpriselamp.org/wp-content/themes/Enterprise_LAMP/index.php on line 36
Warning: include() [function.include]: Failed opening '/home/remarkwit/enterpriselamp.org/wp-content/themes/Enterprise_LAMP/r_sidebar.php' for inclusion (include_path='.:/usr/local/lib/php:/usr/local/php5/lib/pear') in /home/remarkwit/enterpriselamp.org/wp-content/themes/Enterprise_LAMP/index.php on line 36