If you’ve never heard of Selenium, put simply, it’s a tool that allows you to create tests that are run in the browser and interact with your UI in the same way as if you were manually testing your website or app. It’s the de-facto standard to test complex Web UI interactions that usually involve a heavy use of JavaScript, and that’s probably the main use-case for it. Other than that, we also use it sometimes as a helper tool for cross-browser design (CSS) testing by running Selenium tests through different browsers and taking screenshots or recording videos.
Selenium’s been around for a long time now, and is available in various programming languages, but up until Django 1.4 came along you couldn’t have your Selenium tests (easily) integrated with your Django test suite. Since then, a new class named LiveServerTestCase, that your Selenium test classes can inherit from, was introduced. This class will automatically run a test server in the background and your Selenium tests will be run against that server. It not only simplifies the process, but it also means your tests can be setup the same way your Django tests are, and so both can be run together without any aditional setup using the standard manage.py test
command.
Installation
The best way to “understand” Selenium is by actually seeing it in action, so we prepared an example project with a small test for the Django Admin login page. To play with it, just create a new virtualenv and checkout our sample project on Github. The only dependencies are Django and Selenium, so a simple pip install -r selenium_intro/requirements.pip
inside your virtualenv will be enough.
Settings
To make things simple, we’ll use Firefox by default since it has webdriver support built-in. If you want to run tests on Chrome you’ll need to download the chrome driver separately from chromedriver downloads and add a SELENIUM_WEBDRIVER
variable in settings.py
to point to the Chrome driver instead (see selenium_tests.webdriver
in the sample project).
Our settings.py
configuration serves two purposes:
- Not everyone is forced to run the Selenium tests, so it’s opt-in through
INSTALLED_APPS
. - You can switch to another webdriver without a big hassle. For production tests you can write a separate command and change the driver on the fly so you can test different browsers.
App / Test Structure
Selenium tests can be setup inside each app’s test directory just like a regular test would be, but since they’re very tied to the specific project, our preference is to create an app where all the tests are stored. We called this “selenium_tests” and other than the tests themselves, there are two other files:
test.py
Here we defined our base test class and helper methods. We have an “open” helper because Selenium needs an absolute URL (including server and port), but you can also define methods for common operations like “sign in”, “sign out”, etc.. that you may need throughout all the tests.
webdriver.py
Because the default webdriver query methods are very verbose (hello Java!) and missing some important features, we felt the need to create a custom webdriver class so we can add some functionality of our own.
Another option is to use splinter, which provides a much nicer Python interface to Selenium.
In this example, we have only one method, which is very handy:
find_css
is basically a shortcut for both “find_elements_by_css_selector” and “find_element_by_css_selector”, and as the method names imply, it enables you to find DOM elements using regular CSS selectors.wait_for_css
is a helpful method to block the test from progressing until a element (css selector) is found on the page. This method is commonly used in non-blocking instructions (such as AJAX requests) or some JavaScript interactions that do DOM manipulation. Whenever you get an “element not found” error due to JavaScript being slower than your test instructions, this is the method to use.
webdriver.py
to add “generic” shortcuts and test.py
for “app specific” shortcuts (like sign in).
Writing your first test
Our example test will be a very simple Django admin sign in test, here’s how our tests/auth.py
looks like, and the inline documentation should be enough to get an understanding of how it works: