Wednesday, 19 January 2011

Introduction to Selenium for Python programmers

Selenium is an application that automates web browsers, helping you test your web application from a user perspective, in an automated manner. These properties make Selenium tests a perfect fit for validating your js-level functionality and implementing acceptance tests.

Of course, it has some drawbacks: you need to run your application from another process, which gives you some pain with checking the backend state of things. The tests might be quite slow, and - if you don't write them well - extremely fragile.

However, starting with basic Selenium tests is very simple, which I'm going to prove below. We will create a trivial website, with a single element only: a link to Google. Next, we will implement a Selenium test that makes sure this indeed happens.

Prepare the environment

We will work in a virtualenv called "seltest". If you don't know what virtualenv is, you likely want to read this first. Enter the directory of your choice and run the following commands:

mkdir seltest
cd seltest
virtualenv --no-site-packages .

We will work from within the seltest directory, so that we don't need to activate the virtualenv, and instead call our binaries by "bin/python" or "bin/pip".

Let's download the Selenium executable and its python bindings:

bin/pip install selenium

You can already play with Selenium. First, start another terminal window and run:

java -jar selenium-server-standalone-2.0a4.jar

Then from our main terminal:

$ bin/python
>>> from selenium.remote import connect
>>> from selenium import FIREFOX
>>> browser = connect(FIREFOX) # this will run the browser
>>> browser.get("") # you should see the browser navigating to yahoo
>>> browser.close() # this will close the session

Prepare and run the website

The website will consist of a single link, we can skip all the obligatory html boilerplate at this stage. Save the text


into a file index.html, and from another terminal (yes, you will need three terminal windows) run:

Python -m SimpleHTTPServer

This will start serving your page on the port 8000, you can visit the page from your web browser on http://localhost:8000/

Implement the test

Open the file in your editor of choice and dump the following

import unittest
from selenium.remote import connect
from selenium import FIREFOX

class SelTest(unittest.TestCase):
def setUp(self):
self.browser = connect(FIREFOX)
def tearDown(self):
def test_simple(self):
link = self.browser.find_element_by_partial_link_text("Google")
self.assertEqual(self.browser.get_title(), "Google")

if __name__ == "__main__":

The setUp and tearDown methods manage the browser session, and the actual test lives in the test_simple method. We are using four methods from the browser object: get, find_element_by_partial_link_text, click and get_title. In case you wonder where these come from, look for the WebDriver class definition. You can find it in lib/python2.6/site-packages/selenium/remote/ in your environment.

Run the test

Now, you are ready to run your test.


You should see something along the lines of:

$ bin/python 
Ran 1 test in 5.521s


Which indicates, that all your tests passed correctly.

Saturday, 15 January 2011

Optimizing fabfiles

I really like my deploys to be as fast as possible. Unfortunately, the RTT between my and my server makes this quite hard. Today, I came up with a simple optimisation, that lets you make your fabric commands faster (saving on RTT). Say you have a series of consecutive "run" calls. Each call needs to get sent, evaluated and the results need to come back. Why wait for them, when we don't want to continue after failure anyway? The simple fix is to change this:

def my_task():
run("command 2")
run("command 3")
... into this:
def my_task():
commands = []
_run = commands.append
_run("command 2")
_run("command 3")
run(" && ".join(commands))
This way, all your commands get called, and the execution still stops on first failure.

Sunday, 9 January 2011

Customising Django's uniqueness validation message

In case you've been wondering: you need to override the unique_error_message method on your model. The unique_check argument is a tuple containing field names that are supposed to be unique together (for regular uniqueness this is a one-element tuple). See the example below for validating the slug field:

class MyModel(models.Model):
slug = models.SlugField(max_length=200, unique=True)
  def unique_error_message(self, model_class, unique_check):
if unique_check == ("slug",):
return u"This slug is already taken"
return super(Office, self).unique_error_message(model_class, unique_check)

Monday, 3 January 2011

Issues with Django and MySQL on Mac OS X



bin/pip install MySQL-python --no-install
#mysql_config = /usr/local/bin/mysql_config
mysql_config = /usr/local/mysql/bin/mysql_config
bin/pip install build/MySQL-python

The library path


$otool -L lib/python2.6/site-packages/ 


all worked fine. Hope that helps someone in similar trouble.