Skip to main content

Testing the Admin Interface

To write automated tests for the Django admin interface, use the AdminSeleniumTestCase class. This class provides a pre-configured Selenium environment and helper methods specifically designed for interacting with admin-specific UI elements and workflows.

Basic Admin Selenium Test

To create a test, subclass AdminSeleniumTestCase and include your app in available_apps. Use admin_login to authenticate before navigating to admin pages.

from django.contrib.admin.tests import AdminSeleniumTestCase
from django.contrib.auth.models import User
from django.urls import reverse
from selenium.webdriver.common.by import By

class MyAdminSeleniumTests(AdminSeleniumTestCase):
# Include your app and the standard admin apps
available_apps = ["my_app"] + AdminSeleniumTestCase.available_apps

def setUp(self):
self.user = User.objects.create_superuser(
username="admin",
password="password",
email="admin@example.com"
)

def test_changelist_row_count(self):
# Authenticate using the helper
self.admin_login(username="admin", password="password")

# Navigate to the changelist
self.selenium.get(self.live_server_url + reverse("admin:auth_user_changelist"))

# Use standard Selenium methods to interact with the page
rows = self.selenium.find_elements(By.CSS_SELECTOR, "#result_list tbody tr")
self.assertEqual(len(rows), 1)

Synchronizing Page Loads and Elements

The admin interface often involves dynamic content and page transitions. AdminSeleniumTestCase provides several methods to ensure your tests wait for the UI to be ready.

Waiting for Page Loads

Use the wait_page_loaded() context manager when clicking a link or button that triggers a full page refresh.

# From django/contrib/admin/tests.py
with self.wait_page_loaded():
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()

Waiting for Specific Elements

Use wait_for or wait_until_visible to block until an element appears, which is useful for AJAX-heavy parts of the admin like autocomplete fields.

# Wait for a specific CSS selector to appear
self.wait_for("#changelist-form")

# Wait for specific text within an element
self.wait_for_text(".success", "Successfully added.")

# Wait for an element to become visible
self.wait_until_visible("#some-dynamic-element")

Handling Admin Popups

The admin frequently uses popups for adding or selecting related objects (e.g., clicking the + icon next to a ForeignKey). Use wait_for_and_switch_to_popup() to handle the window transition.

# Click the '+' icon to add a related object
self.selenium.find_element(By.ID, "add_id_related_model").click()

# Switch focus to the popup window
self.wait_for_and_switch_to_popup()

# Interact with the popup
self.selenium.find_element(By.ID, "id_name").send_keys("New Related Object")
self.selenium.find_element(By.NAME, "_save").click()

# After the popup closes, focus automatically returns to the main window
# but you may need to wait for the page to be ready
self.wait_page_ready()

Interacting with Form Widgets

AdminSeleniumTestCase includes helpers for common form interactions, particularly for <select> elements.

# Select an option by value
self.select_option("#id_status", "published")

# Assert that specific options are available
self.assertSelectOptions("#id_category", ["1", "2", "3"])

# Assert which options are currently selected
self.assertSelectedOptions("#id_tags", ["tag1", "tag2"])

# Check if a field is disabled
if self.is_disabled("#id_readonly_field"):
print("Field is read-only")

Troubleshooting and Gotchas

Content Security Policy (CSP) Violations

AdminSeleniumTestCase automatically checks for CSP violations in the browser's security logs during tearDown. If any violations are found, the test will fail.

def tearDown(self):
# Ensure that no CSP violations were logged in the browser.
self.assertEqual(self.get_browser_logs(source="security"), [])
super().tearDown()

Implicit Wait and Absence Checks

The default implicit wait is 10 seconds. If you need to assert that an element is not present, use disable_implicit_wait() to avoid waiting for the full timeout.

with self.disable_implicit_wait():
elements = self.selenium.find_elements(By.CSS_SELECTOR, ".should-not-exist")
self.assertEqual(len(elements), 0)

Browser Configuration

You can control the browser used for tests via command line arguments:

  • --selenium: Specify the browser (e.g., chrome, firefox).
  • --headless: Run the browser in headless mode for CI environments.

Example:

python manage.py test my_app.tests.SeleniumTests --selenium=chrome --headless