Continuous Integration for a Javascript-heavy Django Site

by Sebastien Mirolo on Sat, 25 May 2013

In the popular series on how technical decisions are made, we will see Today why fortylines picked django-jenkins, phantomjs, casperjs and django-casper to build its continuous integration infrastructure on.

Continuous integration for Django projects

Python is a great language. It makes it quick to get to working code in production. Unfortunately like many dynamic programming languages, issues arise when you start to add features and rework the code base. With a static type checking language like C++, the compiler would caught stupid mistakes like variable name that wasn't updated all the places it is used, or like a function parameter that used to be an integer and is now a list of integers.

Welcome to the world of extensive testing. If you are looking to deploy code changes at the pace required to run a webapp, continuous integration becomes rapidly an important aspect of the development cycle.

django-jenkins is a perfect start if, like us, you are running django and jenkins. The only unfortunate hic-hup so far is the absence of support for behave. That is unfortunate because we chose behave over lettuce for various reasons.

Since behave accepts a --junit command line flag, it is though possible to integrate and jenkins directly, as a subsequent command.

$ python manage.py jenkins
$ behave --junit --junit-directory ../reports

SIDE NOTE: There is a django-behave project. Unfortunately using it will remove the ability to run standard django testsuites - or more accurately from reading comments in the code - mixing django-behave and tests based on unittest hasn't been implemented yet. There has been no update to the repository in eight months as of writing this post.

Javascript content

After starting out with Amazon Payment System, integrating with Paypal, we finally settled on stripe.

Stripe is great from a developer's perspective. APIs and documentations are excellent. The one feature that is advertised as a benefit to developers is also the feature that threw off our django/behave/jenkins initiative: Javascript.

Until now we used mechanize to simulate a browser and check the generated HTML. With the introduction of Javascript in our stack, it wasn't possible to rely on mechanize alone. We needed to integrate a javascript engine in our test infrastructure.

The first intuition was to use python bindings to selenium, a browser automation framework.

$ pip install selenium --upgrade
# Download the webdriver for your browser
$ unzip ~/Download/chromedriver_mac_26.0.1383.0.zip
$ mv chromedriver /usr/local/bin
$ export PATH=$PATH:"/Applications/Google Chrome.app/Contents/MacOS"

Selenium is quite heavy weight. It requires to launch the browser executable and that will trigger GUI windows popping-up on your screen. You might be able to install all the required packages to run a X11 server with no display attached on your continuous server virtual machine but that seems like overkill and like a potential rat hole of package dependencies.

Welcome to phantomjs, the headless webkit engine.

Browsing around for phantomjs and BDD, it wasn't long before I stumbled on jasmine and djangojs. Jasmine is wonderful to unit tests javascript code and djangojs helps integrate a javascript-heavy webui into a django site. Both projects deliver as promised. That is where the subtlety lies. We needed something to drive end-to-end system tests, something that would help write tests at the level of "open url", "fill form", "click button", "check text in html page", etc.

We thus reverted our first attempt of using phantomjs with jasmine and djangojs and started to look again for a more suited solution. That is how a few searches later we ended-up on casperjs and django-casper. By itself casperjs generates junit xml output. You can thus use casperjs straight from the command line in your jenkins shell.

$ cat hellotest.js
casper.test.comment('this is an hello test');
casper.test.assertTrue(true, "YEP");
casper.test.done();
$ casperjs test --xunit=./test-result.xml hellotest.js

Once integrated into django through a django-casperjs wrapper, your tests look and behave like regular django tests. Hence they integrate perfectly with manage.py test and django-jenkins. Excellent!

by Sebastien Mirolo on Sat, 25 May 2013