Automated Browser Testing with Selenium
Automated browser testing allows you to verify that your application's frontend works correctly across different browsers and screen sizes. This project provides SeleniumTestCase, a specialized test class that integrates Selenium WebDriver with Django's LiveServerTestCase.
In this tutorial, you will build a test suite that verifies a page's content, tests responsive layouts, and captures screenshots across different UI themes.
Prerequisites
To use the Selenium testing infrastructure, you must have the following installed:
- Selenium Library:
pip install selenium - Webdrivers: Ensure
chromedriver(for Chrome) orgeckodriver(for Firefox) is in your system'sPATH. - Browsers: The corresponding browsers must be installed on your machine.
Step 1: Creating a Basic Selenium Test
To start, create a test class that inherits from django.test.selenium.SeleniumTestCase. This class automatically manages the lifecycle of the browser driver.
from django.test.selenium import SeleniumTestCase
from selenium.webdriver.common.by import By
class MyWebsiteTests(SeleniumTestCase):
available_apps = ["my_app"]
def test_homepage_content(self):
# Navigate to the live server URL
self.selenium.get(self.live_server_url + "/")
# Find an element and verify its text
header = self.selenium.find_element(By.TAG_NAME, "h1")
self.assertEqual(header.text, "Welcome to My Site")
When you run this test, you must specify the browser using the --selenium flag:
python manage.py test --selenium=chrome
The SeleniumTestCaseBase metaclass detects this flag and dynamically configures the selenium attribute on your test instance.
Step 2: Testing Responsive Design
SeleniumTestCase provides context managers to temporarily change the browser window size using the ChangeWindowSize helper. This is useful for verifying that elements are visible or hidden on different devices.
def test_mobile_menu_visibility(self):
self.selenium.get(self.live_server_url + "/")
# Test on desktop
with self.desktop_size():
menu = self.selenium.find_element(By.ID, "desktop-menu")
self.assertTrue(menu.is_displayed())
# Test on mobile
with self.mobile_size():
menu = self.selenium.find_element(By.ID, "desktop-menu")
self.assertFalse(menu.is_displayed())
hamburger = self.selenium.find_element(By.ID, "mobile-hamburger")
self.assertTrue(hamburger.is_displayed())
The available sizing context managers are:
desktop_size(): Sets window to 1280x720.small_screen_size(): Sets window to 1024x768.mobile_size(): Sets window to 360x800.
Step 3: Visual Testing with Screenshots
You can automate visual regression testing using the @screenshot_cases decorator. This decorator instructs the test runner to execute the test method multiple times, once for each specified UI state.
To enable this, you must set screenshots = True in your class and run tests with the --screenshots flag.
from django.test.selenium import SeleniumTestCase, screenshot_cases
class VisualTests(SeleniumTestCase):
screenshots = True
@screenshot_cases(["desktop_size", "mobile_size", "dark", "high_contrast"])
def test_profile_page_appearance(self):
self.selenium.get(self.live_server_url + "/profile/")
# Capture a screenshot for the current case
self.take_screenshot("profile_main")
When running with --screenshots, this single method will expand into four separate tests:
test_profile_page_appearance_desktop_sizetest_profile_page_appearance_mobile_sizetest_profile_page_appearance_darktest_profile_page_appearance_high_contrast
Screenshots are saved to a screenshots/ directory in your current working directory.
Step 4: Testing Accessibility and Themes
The SeleniumTestCase class includes helpers for testing specific UI modes like dark mode or high contrast.
- Dark Mode: The
dark()context manager sets atheme=darkitem inlocalStorageand reloads the page. - High Contrast: The
high_contrast()context manager uses the Chrome DevTools Protocol (CDP) to emulateforced-colors: active.
def test_dark_mode_colors(self):
with self.dark():
# The page is automatically navigated to live_server_url
body = self.selenium.find_element(By.TAG_NAME, "body")
self.assertEqual(body.get_dom_attribute("data-theme"), "dark")
def test_accessibility_emulation(self):
# Note: high_contrast() requires Chrome or Edge
with self.high_contrast():
self.take_screenshot("high_contrast_view")
Step 5: Handling Waits and Debugging
By default, SeleniumTestCase uses an implicit_wait of 10 seconds. If you need to check for the absence of an element without waiting for the timeout, use disable_implicit_wait().
def test_element_removal(self):
self.selenium.get(self.live_server_url + "/")
self.selenium.find_element(By.ID, "remove-btn").click()
# Disable wait to check immediately that it's gone
with self.disable_implicit_wait():
elements = self.selenium.find_elements(By.ID, "item-to-remove")
self.assertEqual(len(elements), 0)
If a test fails, you can inspect the browser's console logs (currently supported for Chrome):
def test_with_logging(self):
self.selenium.get(self.live_server_url + "/page-with-errors/")
logs = self.get_browser_logs(level="SEVERE")
for entry in logs:
print(f"Browser Error: {entry['message']}")
Summary
You have now built a robust browser test suite using SeleniumTestCase. You can:
- Run tests across multiple browsers using
--selenium. - Verify responsive layouts with
mobile_size()anddesktop_size(). - Generate visual artifacts using
@screenshot_cases. - Test specialized UI states like
dark()andhigh_contrast().
For testing the Django Admin specifically, consider inheriting from django.contrib.admin.tests.AdminSeleniumTestCase, which provides additional helpers like admin_login().