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.
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.
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.
3. Class Structure
The main methods to consider are:
- setUpClass(): executed once before the first test.
- tearDownClass(): executed once after the last test.
- setUp(): executed before each test.
- 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|
|cls.browser = webdriver.Firefox()|
|# Populate the database here|
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:
|self.browser.get(self.live_server_url + 'question/create/')|
|# Test the page|
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.
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:
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 selenium.webdriver.support.ui import WebDriverWait|
|from selenium.webdriver.support import expected_conditions as EC|
|from selenium.webdriver.common.by import By|
|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'))|
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.
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:
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 🙂