I recently started working with Python and Django. After previously working with Rails I was looking for Python equivalents to BDD and testing tools like cucumber, rspec, and capybara or webrat.
I found lettuce and splinter. These projects are a bit newer than their ruby equivalents but you can still get the job done.
Example scenario that needs to wait for an AJAX request
Most of the problems I’ve encountered when writing full stack tests happen when you have to wait for an AJAX request. So here’s an example from a Django app with some AJAX action.
# apps/users/features/signup.feature
Scenario: User enters a username that has been taken
Given a user exists with username "joeb"
When I go to the "/register/" URL
And I fill in "username" with "joeb"
And I move focus away from the username field
Then I should see "not available"
That scenario is for when a user is registering. As soon as the user enters their username and moves on to the next field an AJAX request goes off and checks whether that username is available or not. When it receives a response it displays “available” or “not available” beside the username field.
Setting up Lettuce and Splinter
But before you do anything you might want to setup a couple of things in your lettuce terrain.py file.
- Prepare test environment before each test run.
- Tear down test environment after each test run.
- Flush your database before each scenario.
- Set up your browser instance using Splinter.
# terrain.py
from lettuce import before, after, world
from splinter.browser import Browser
from django.test.utils import setup_test_environment, teardown_test_environment
from django.core.management import call_command
from django.db import connection
from django.conf import settings
@before.harvest
def initial_setup(server):
call_command('syncdb', interactive=False, verbosity=0)
call_command('flush', interactive=False, verbosity=0)
call_command('migrate', interactive=False, verbosity=0)
call_command('loaddata', 'all', verbosity=0)
setup_test_environment()
world.browser = Browser('webdriver.firefox')
@after.harvest
def cleanup(server):
connection.creation.destroy_test_db(settings.DATABASES['default']['NAME'])
teardown_test_environment()
@before.each_scenario
def reset_data(scenario):
# Clean up django.
call_command('flush', interactive=False, verbosity=0)
call_command('loaddata', 'all', verbosity=0)
@after.all
def teardown_browser(total):
world.browser.quit()
Implementing steps
Splinter makes it easy to implement your steps and control the browser by providing simple methods like the following:
- browser.visit(url)
- browser.fill(field, value)
- browser.find_by_css_selector('h1')
- browser.choose('some-radio')
Check out the splinter docs for a full list of what you can do.
So here are the steps for the “User enters a username that has been taken” scenario.
# apps/users/features/user-steps.py
@step(u'I go to the "(.*)" URL')
def i_go_to_the_url(step, url):
world.response = world.browser.visit(django_url(url))
@step(u'a user exists with username "(.*)"')
def a_user_exists_with_username(step, p_username):
user = UserProfile(username=p_username, email='example@example.com')
user.set_password('secret007')
user.save()
@step(u'I fill in "(.*)" with "(.*)"')
def i_fill_in(step, field, value):
world.browser.fill(field, value)
@step(u'I move focus away from the username field')
def i_move_focus_away_from_the_username_field(step):
world.browser.fill("password", "value")
world.browser.wait_for_xpath('//*[@id="availability" and text()="not available"]')
@step(u'I should see "(.*)"')
def i_should_see(step, text):
assert text in world.browser.html
Running lettuce features
To run your features for the users app with a separate settings file you would use something like…
python manage.py harvest --apps=users --settings=settings_lettuce
This uses a separate settings file which is useful if you need a separate environement. For example if you want to use a sqlite3/in memory database to make your tests run quicker. Or one other thing I had to do was to reduce the global_log_level because the extra output in the terminal made it harder to read when lettuce was outputting the test results.
You can see all the code for this example at https://github.com/cillian/batucada/tree/full-stack-tests
Keep up the good work Lettuce and Splinter.