Rover.com is a Django shop, but I personally come from a Rails background. The Rails world has great tooling and infrastructure for automated functional tests - capybara, capybara-webkit, and the new hotness poltergist. Underneath poltergeist lies PhantomJS, a headless webkit with very few dependencies, excellent for automated testing. Unfortuantely, PhantomJS version 1.5 dropped Python bindings, leaving us Djangonauts out to dry. There also isn’t a great capybara equivalent in the Python world (Ghost.py is the closest).
Thankfully, despite the roadblocks, there is a path forward! Django (starting with 1.4) comes with
LiveServerTestCase to support our exact use case. After failed attempts to get Ghost.py up and running (due to dependencies and lack of documentation), I landed upon a solution that will start us Django developers down the path of being first class citizens once more (well, maybe not exactly first class, but at least those coach seats up front with a little extra legroom). I’ve also taken some baby steps to improve our testing assertion syntax, trying to fill the capybara void, which I’ll get into at the end of this post.
Let’s get started.
Install the Prerequisites
The following steps are for Ubuntu 12.04 LTS.
Ubuntu 12.04’s default ppa’s don’t have the latest version of NodeJS, so we first need to add a new repository.
sudo apt-get install software-properties-common sudo apt-get update sudo apt-get install -y python-software-properties python g++ make sudo add-apt-repository -y ppa:chris-lea/node.js sudo apt-get update
Now, we can install
sudo apt-get install nodejs
For use with PhantomJS
sudo apt-get install fontconfig
NodeJS comes with npm, their package manager. From that, we can install PhantomJS.
sudo npm -g install phantomjs
Note here that I’m installing it globally, which is not required to get this to work, but can make things easier.
pip install -U selenium
With our system set up to support our automated testing, we now need to set up our application so we can start writing our own tests.
Add selenium to your
INSTALLED_APPS = ( 'django.contrib.auth', '...', 'selenium', )
Install the Selenium Utility Belt
We’ve written and open-sourced the very beginning of what we hope will grow into a more fully-featured assertion framework Selenium testing in Django. We created this to wrap some of the selenium syntax into a more expressive feature set to enable more rapid test writing. When testing interfaces, it is really nice to be able to express assertions as they relate to your goals, such as
assertOnPage. This was the impetus for creating the selenium utility belt. The project lives at https://github.com/roverdotcom/selenium-utility-belt and is in its infancy. We hope to add to this (with your help!), rework its structure, and release it onto PyPI for wider consumption.
selenium_utility_belt.py file into your project at a convenient place to import it to your base test case class. We toss ours in our common app, in the test folder.
Base class for your test cases
from django.test import LiveServerTestCase from common.test.selenium_utility_belt import SeleniumUtilityBelt class InterfaceTestCase(SeleniumUtilityBelt, LiveServerTestCase): def setUp(self): super(InterfaceTestCase, self).setUp()
Write your first test!
With just a little bit of wrapping of the Selenium API, our interface becomes very simple to test. Here we have a single test that asserts the presence and visibility of our Location text entry field on our homepage.
from common.test import InterfaceTestCase class HomepageTests(InterfaceTestCase): def setUp(self): super(HomepageTests, self).setUp() def test_location_field_on_homepage(self): self.open('/') self.assertOnPage('#location', visible=True)
When running these tests on our CI server, we ran into the issue of port collisions for the running PhantomJS processes.
LiveServerTestCase had foresight on this issue, and added an option when you run your tests. Simply specify the range of ports to use when you call your test runner.
./manage.py test --liveserver=localhost:8090-8100
Recent versions of django-jenkins take this option as well, so you can easily use these tests on your CI server
If you have run into any questions or hit any roadblocks along your way here, you can reach me at @croby. Contributions to the utility belt are welcome and encouraged!