Skip to main content

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:

  1. Selenium Library: pip install selenium
  2. Webdrivers: Ensure chromedriver (for Chrome) or geckodriver (for Firefox) is in your system's PATH.
  3. 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:

  1. test_profile_page_appearance_desktop_size
  2. test_profile_page_appearance_mobile_size
  3. test_profile_page_appearance_dark
  4. test_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 a theme=dark item in localStorage and reloads the page.
  • High Contrast: The high_contrast() context manager uses the Chrome DevTools Protocol (CDP) to emulate forced-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:

  1. Run tests across multiple browsers using --selenium.
  2. Verify responsive layouts with mobile_size() and desktop_size().
  3. Generate visual artifacts using @screenshot_cases.
  4. Test specialized UI states like dark() and high_contrast().

For testing the Django Admin specifically, consider inheriting from django.contrib.admin.tests.AdminSeleniumTestCase, which provides additional helpers like admin_login().