Testing registration scenario against django with lettuce and selenium webdriver

on Jan 14, 2013 by Danu


The best way to “understand” is by following a good example, so we prepared a sample project which uses django-registration library in order to have a simple, generic user-registration application for our tests. To play with it, just create a new virtualenv and checkout the sample project on Github. The only dependencies are django, selenium, nose and django-registration, so a simple pip install -r requirements.txt inside your virtualenv will be enough.



Because the default webdriver API is rather low-level and lacks things necessary for creating suites of automated web tests another option is to use splinter, which provides a much nicer Python interface to Selenium. We'll stick to selenium-webdriver because we don't need another layer of abstraction for this simple demo app.

We’ll use Firefox by default since it has webdriver support built-in. If you want to use Chrome you’ll need to download the chrome driver separately. 



First let's extend django default settings with lettuce specific configurations. This way we avoid running tests against dev db and declare another db to run tests against. This is actually bad, the lack of test database integration with django and the fact that lettuce server is used over LiveServerTestCase may cause reliability issues in the future (see django test database). Add lettuce.django to INSTALLED_APPS. You can also specify the app names in LETTUCE_APPS to run tests against, so that you won’t need to type the app name as command-line parameters. 

from settings import *

    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'lettuce.db',                   # Or path to database file if using sqlite3.
        'USER': '',                             # Not used with sqlite3.
        'PASSWORD': '',                         # Not used with sqlite3.
        'HOST': '',                             # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                             # Set to empty string for default. Not used with sqlite3.




Test structure

Lettuce will look for a features folder inside every installed app. For each app you want to create lettuce tests you have to create a sub folder called features. Our features are stored under registerapp folder. The "feature" file contains scenarios, while ".py" file contains python code executed for steps used in scenarios.


Feature: Register
    In order to get access to app 
    A user should be able to register

Scenario: User registers
    Given I go to the "/register" URL
    When I fill in "username" with "danul"
    And I fill in "email" with "danclaudiupop@gmail.com"
    And I fill in "password1" with "test123"
    And I fill in "password2" with "test123"
    And I press "submit"
    Then I should see "Check your email"
    And I should receive an email at "danclaudiupop@gmail.com" with the subject "Activate your djangoproject.com account - you have 7 days!"
    And I activate the account
    Then I should see "Congratulations!"

Scenario: User logs in successfully
    Given I go to the "/login" URL
    When I fill in "username" with "danul"
    And I fill in "password" with "test123"
    And I press "submit"
    Then I should see "Welcome, danul. Thanks for logging in."



We are importing django_url utility function so we can easily access web pages within steps using relative urls. We also importing the email backend that sends mails to a multiprocessing queue. Because Django server runs in a different process than lettuce you are not able to use the EMAIL_BACKEND specified in settings file, not to mention that harvest.py is using django.test.utils.setup_test_environment that overrides email backend to locmem

import re

from lettuce import step, world
from lettuce.django import django_url
from lettuce.django import mail
from nose.tools import assert_equals

@step(u'I go to the "(.*)" URL')
def i_go_to_the_url(step, url):
    world.response = world.browser.get(django_url(url))

@step(u'I fill in "(.*)" with "(.*)"')
def i_fill_in(step, field, value):

@step(u'I press "(.*)"')
def i_press(step, button_id):
    button = world.browser.find_element_by_id(button_id)

@step(u'I should see "(.*)"')
def i_should_see(step, expected_response):
    h1 = world.browser.find_element_by_tag_name('h1')
    assert_equals(h1.text, expected_response)

@step(u'And I should receive an email at "([^"]*)" with the subject "([^"]*)"')
def i_should_receive_email_with_subject(step, address, subject):
    message = mail.queue.get(True, timeout=5)
    world.email_body = message.body
    assert_equals(message.subject, subject)
    assert_equals(message.recipients(), [address])

@step(u'And I activate the account')
def and_i_activate_the_account(step):
    activation_url = re.findall(
    world.response = world.browser.get(activation_url)



Its about setup and teardown. We setup the browser instance using webdriver. We prepare the test environment by running the django syncdb command which creates the database tables as well loading any fixtures named initial_data. Teardown test environment by destroying the test database. Different hooks and commands can be used, for e.g: before each scenario load specific fixtures using loaddata.

from django.core.management import call_command
from django.db import connection
from django.conf import settings

from lettuce import before, after, world
from selenium import webdriver

def initial_setup():
    call_command('syncdb', interactive=False, verbosity=1)
    world.browser = webdriver.Firefox()

def teardown_browser(total):


Running lettuce tests

All that’s left is running the tests:

python manage.py harvest tests/lettuce_selenium_tests --settings=conf.settings_lettuce

Source code for this project can be found on github:


See also:

Web development fun with Lettuce and Django

Tags: webdriver django selenium lettuce bdd | Category: testing , python Back To Top

comments powered by Disqus