Django: Automated testing with selenium

Hello again,

A couple weeks ago I wanted to test the templates of a site I’m building, mainly javascript functions and some user interactions, and decided to use selenium.

I wanted to use raw selenium because that’s my first time using it in python, so I wanted to learn how it works without the help of third-party modules.

To my surprise, I didn’t find a straight forward tutorial, so I’m writing one with the steps I did to make it work. The tests I created are available at the end of this post.

It’s worth noting that I used selenium 3.1, django 2.1 and python 3.6. Running the server on Ubuntu 18.

Let’s begin!

1. Installation

The first step is to install selenium. I used pip for that:

pip3 install selenium

Selenium requires a driver to launch the browser, and each browser has its own. Firefox, the one I chose, uses the geckodriver, which is available here.

The driver must be in a folder listed in the PATH environment variable. In Ubuntu, just move it to the /bin folder and it’s ready to use.

2. StaticLiveServerTestCase

Selenium demands the test class to be either a LiveServerTestCase or a StaticLiveServerTestCase, that’s because it needs the server running to test the site.

Both classes are similar, the difference is that the latter will load the static content (custom css and javascript files for instace) while the former won’t.

I prefer StaticLiveServerTestCase because one of the reasons for using selenium is to test the javascript functions, so, static content is necessary.

3. Class Structure

The main methods to consider are:

  1. setUpClass(): executed once before the first test.
  2. tearDownClass(): executed once after the last test.
  3. setUp(): executed before each test.
  4. tearDown(): executed after each test.

The setUpTestData() isn’t on the list because it isn’t available in LiveServerTestCase.

A good place to open the browser is in the setUpClass() because it takes long to open it, this way it’ll use the same window in all the tests. What leads to using tearDownClass() to close the browser.

In a LiveServerTestCase the database is flushed after each test, then it’s necessary to populate it before each test too. Either setUp() or the own test are good places for it, although setUp() is the expected place and should be favored.

tearDown() isn’t really necessary for the base structure but it’s good to know that it exists :3

The base test class should look like:

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
class TestName(StaticLiveServerTestCase):
def setUpClass(cls):
cls.browser = webdriver.Firefox()
def tearDownClass(cls):
def setUp(self):
super(TestName, self).setUp()
# Populate the database here

4. live_server_url

To access a webpage in selenium we must use the URL of the testing server. The URL changes everytime time you run the tests, a different port is assigned to the server, but it’s stored in the variable live_server_url. An webpage can be accessed like this:

def test_create_button(self):
self.browser.get(self.live_server_url + 'question/create/')
# Test the page

view raw
hosted with ❤ by GitHub

5. Explicit and Implicit Waits

As far as I know selenium waits the page to be ready before executing commands. However, in some cases we will need to wait some action to complete so we can resume testing the page.

Usually this happens when a javascript function is used, and we must wait its completion, or when the test case loads another page and we should wait for it, otherwise the next comands will run in the initial page and fail.

In those cases we can use both implicit or explicit waits. The implicit one simply stops the execution for a fixed amount of time, you can use it as in:

driver = webdriver.Firefox()
driver.implicitly_wait(10) # in seconds

Usually, implicit waits are a bad choice because you’ll risk either having unnecessarily long wait times or inconsistent tests.

Explicit waits, on the other hand, are more reliable. They’ll wait for a given expected condition to happen and resume as soon as it does. The following example shows how to wait for an element to be clickable:

from django.urls import reverse
from import WebDriverWait
from import expected_conditions as EC
from import By
def test_title_redirects_to_details(self):
self.browser.get(self.live_server_url + reverse('question-list'))
title = WebDriverWait(self.browser, 5).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '#question2 .card-link-title'))

view raw
hosted with ❤ by GitHub

There are multiple builtin expected conditions which you can find here. Notice that some conditions expect a locator, like element_to_be_clickable(locator) in the snippet above, in this case you should use the By class. You can find all the available locators here, the usage of them all is similar to By.CSS_SELECTOR.

6. Login user in code

In some tests it might be useful to login the user in code. The other option is using selenium to simulate the actual login, which will take longer. In case you are testing a page that requires the login instead of the login page, it’s better to save this time.

The trick I found to login the user is to use cookies to pass a logged in session to the browser. You can set the cookie in the setUp() like:

view raw
hosted with ❤ by GitHub

This trick will make the slow tests a little less slow πŸ™‚

7. Auto ids

At last, I’d like to warn you about creating objects in the database when the model uses auto generated ids. It happens that the database is flushed between tests but the auto id counter won’t necessarily reset.

Then, if you add two objects to the database, their ids will be 1 and 2 in the first test and 3 and 4 in the second. This behavior might break some tests which use the id to identify the object. In this case, always set the id yourself, like:

8. Example

With everything stated above you should be ready to test your project with flexibility. I’ll also leave the tests I created here as reference, hope they help πŸ™‚

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s