Автоматизация тестов Django: интеграция Selenium для веб-разработки
Для кого эта статья:
- Разработчики, использующие Django для создания веб-приложений
- Специалисты по тестированию и QA, заинтересованные в автоматизации тестов
Студенты и начинающие программисты, изучающие автоматизацию тестирования с помощью Selenium
Ручное тестирование Django-приложений забирает драгоценные часы, которые можно было бы потратить на разработку? Каждый клик, каждая проверка формы, каждый сценарий авторизации — всё это превращается в рутину при росте проекта. Я прошел этот путь и готов показать, как Selenium может превратить эту головную боль в автоматизированный, надежный процесс. В этом руководстве вы получите пошаговую инструкцию интеграции Selenium с Django и конкретные примеры кода, которые работают и приносят реальную пользу. 🚀
Осваивайте профессиональную автоматизацию тестирования на курсе Обучение Python-разработке от Skypro. Здесь вы не просто изучите интеграцию Selenium с Django, но и освоите полный стек технологий для создания качественных веб-приложений под руководством действующих разработчиков. От основ до продвинутых техник автоматизации — инвестируйте в навыки, востребованные на рынке прямо сейчас!
Установка и настройка Selenium в Django-проекте
Начнем с создания прочного фундамента для наших автоматизированных тестов. Установка и конфигурация Selenium в Django-проекте — это первый шаг на пути к стабильным, надежным тестам, которые избавят вас от необходимости монотонных проверок интерфейса вручную.
Прежде всего, установим необходимые компоненты через pip:
pip install selenium django
pip install webdriver-manager # Для автоматического управления драйверами
После установки базовых пакетов необходимо добавить конфигурацию в ваш Django-проект. Откройте файл settings.py и добавьте следующие настройки:
# settings.py
SELENIUM_DRIVER = 'chrome' # Выбор драйвера: chrome, firefox, edge
SELENIUM_BROWSER_EXECUTABLE_PATH = None # Опционально: путь к исполняемому файлу браузера
SELENIUM_HEADLESS = True # Запуск браузера в фоновом режиме
Далее создадим базовый класс для тестов с Selenium в Django. В директории вашего приложения создайте файл tests/test_selenium.py:
# tests/test_selenium.py
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from django.conf import settings
import time
class SeleniumTestCase(StaticLiveServerTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
options = webdriver.ChromeOptions()
if getattr(settings, 'SELENIUM_HEADLESS', False):
options.add_argument('--headless')
service = Service(ChromeDriverManager().install())
cls.selenium = webdriver.Chrome(service=service, options=options)
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
Различные браузеры требуют разных настроек и могут проявлять специфические особенности при тестировании. Вот сравнение основных браузеров для использования с Selenium в Django:
| Браузер | Преимущества | Недостатки | Интеграция с Django |
|---|---|---|---|
| Chrome | Широкая поддержка, стабильность, детальные логи | Высокое потребление памяти | Отличная, много примеров |
| Firefox | Легкий, открытый исходный код | Может быть медленнее Chrome | Хорошая, требует дополнительной настройки |
| Edge | Хорошая производительность в Windows | Ограниченная поддержка на других ОС | Нормальная, меньше документации |
| Safari | Полезен для тестов на macOS/iOS | Сложная настройка, ограничения | Слабая, мало примеров |
Алексей Морозов, Lead QA Engineer
Когда я пришел в проект разработки CRM-системы на Django, покрытие тестами было минимальным, а регрессионные баги появлялись с пугающей регулярностью. Каждый релиз превращался в стресс-тест для всей команды.
Начал я с простого — установил Selenium и создал несколько базовых тестов для критичных бизнес-процессов. Первый запуск выявил 12 багов в интерфейсе, которые не ловились обычными юнит-тестами. Через неделю я настроил полноценную инфраструктуру для Selenium-тестов в Django и интегрировал её с нашим CI/CD.
Самым сложным оказалось настроить стабильную работу в headless-режиме для тестов с JavaScript-интенсивными компонентами — помогло увеличение таймаутов и явные ожидания. Через месяц после внедрения время на регрессионное тестирование сократилось с 3-х дней до 40 минут автоматического прогона, а количество багов в production упало на 73%.
Для упрощения управления различными браузерами можно создать фабрику WebDriver, которая будет создавать нужный драйвер в зависимости от настроек:
# utils/webdriver_factory.py
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from selenium.webdriver.edge.service import Service as EdgeService
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
def get_webdriver(browser_name='chrome', headless=True):
"""Фабрика для создания экземпляра WebDriver."""
options = None
driver = None
if browser_name.lower() == 'chrome':
options = webdriver.ChromeOptions()
if headless:
options.add_argument('--headless')
service = ChromeService(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=options)
elif browser_name.lower() == 'firefox':
options = webdriver.FirefoxOptions()
if headless:
options.add_argument('--headless')
service = FirefoxService(GeckoDriverManager().install())
driver = webdriver.Firefox(service=service, options=options)
elif browser_name.lower() == 'edge':
options = webdriver.EdgeOptions()
if headless:
options.add_argument('--headless')
service = EdgeService(EdgeChromiumDriverManager().install())
driver = webdriver.Edge(service=service, options=options)
else:
raise ValueError(f"Неподдерживаемый браузер: {browser_name}")
# Настройка общих параметров
driver.implicitly_wait(10)
driver.set_page_load_timeout(30)
return driver

Создание базовых тест-кейсов с Selenium в Django
Создание эффективных тест-кейсов — ключ к успешной автоматизации тестирования в Django. Правильно структурированные тесты не только выявляют проблемы, но и служат живой документацией к вашему приложению.
Начнем с создания простого тест-кейса, который проверит доступность главной страницы вашего Django-приложения:
# tests/test_homepage.py
from .test_selenium import SeleniumTestCase
class HomepageTestCase(SeleniumTestCase):
def test_homepage_title(self):
"""Проверяет заголовок главной страницы."""
self.selenium.get(self.live_server_url)
self.assertIn('Django', self.selenium.title)
def test_homepage_content(self):
"""Проверяет наличие ключевого контента на странице."""
self.selenium.get(self.live_server_url)
body_text = self.selenium.find_element_by_tag_name('body').text
self.assertIn('Welcome', body_text)
Для более сложных сценариев, таких как тестирование авторизации, создадим соответствующий тест-кейс:
# tests/test_login.py
from .test_selenium import SeleniumTestCase
from selenium.webdriver.common.by import By
from django.contrib.auth.models import User
class LoginTestCase(SeleniumTestCase):
def setUp(self):
super().setUp()
# Создаем тестового пользователя
self.username = 'testuser'
self.password = 'password123'
User.objects.create_user(
username=self.username,
email='test@example.com',
password=self.password
)
def test_login_success(self):
"""Проверяет успешный вход в систему."""
self.selenium.get(f"{self.live_server_url}/accounts/login/")
# Заполняем форму входа
username_input = self.selenium.find_element(By.NAME, 'username')
password_input = self.selenium.find_element(By.NAME, 'password')
username_input.send_keys(self.username)
password_input.send_keys(self.password)
# Отправляем форму
self.selenium.find_element(By.XPATH, '//button[@type="submit"]').click()
# Проверяем, что мы перенаправлены на правильную страницу
self.assertEqual(self.selenium.current_url, f"{self.live_server_url}/")
# Проверяем наличие элемента, доступного только после входа
welcome_message = self.selenium.find_element(By.ID, 'welcome-message')
self.assertIn(self.username, welcome_message.text)
def test_login_failure(self):
"""Проверяет неудачный вход в систему."""
self.selenium.get(f"{self.live_server_url}/accounts/login/")
# Заполняем форму входа неверными данными
username_input = self.selenium.find_element(By.NAME, 'username')
password_input = self.selenium.find_element(By.NAME, 'password')
username_input.send_keys(self.username)
password_input.send_keys('wrong_password')
# Отправляем форму
self.selenium.find_element(By.XPATH, '//button[@type="submit"]').click()
# Проверяем, что мы остались на странице входа
self.assertIn('/accounts/login/', self.selenium.current_url)
# Проверяем, что отображается сообщение об ошибке
error_message = self.selenium.find_element(By.CLASS_NAME, 'error-message')
self.assertIn('Invalid credentials', error_message.text)
Давайте рассмотрим различные стратегии организации тест-кейсов, чтобы выбрать подход, который лучше всего соответствует вашему проекту:
| Подход | Описание | Преимущества | Недостатки |
|---|---|---|---|
| По функциональности | Тесты организованы вокруг функциональных областей приложения | Легко найти тесты для конкретной функции | Может привести к дублированию кода при тестировании E2E сценариев |
| По пользовательским сценариям | Тесты соответствуют реальным сценариям использования | Лучше проверяет реальный пользовательский опыт | Тесты могут быть сложными и длинными |
| По слоям приложения | Отдельные тесты для UI, бизнес-логики, API и т.д. | Четкое разделение ответственности | Сложно отследить покрытие E2E сценариев |
| По уровню изоляции (пирамида тестирования) | Разделение на модульные, интеграционные и UI-тесты | Сбалансированное покрытие, разная скорость выполнения | Требует хорошего понимания архитектуры тестирования |
Для более сложных приложений рекомендую создать базовые страничные объекты (Page Objects), которые инкапсулируют взаимодействие с конкретными страницами приложения:
# tests/page_objects/login_page.py
from selenium.webdriver.common.by import By
class LoginPage:
def __init__(self, selenium, live_server_url):
self.selenium = selenium
self.live_server_url = live_server_url
self.url = f"{live_server_url}/accounts/login/"
def navigate(self):
self.selenium.get(self.url)
return self
def login(self, username, password):
username_input = self.selenium.find_element(By.NAME, 'username')
password_input = self.selenium.find_element(By.NAME, 'password')
username_input.send_keys(username)
password_input.send_keys(password)
self.selenium.find_element(By.XPATH, '//button[@type="submit"]').click()
return self
def get_error_message(self):
try:
return self.selenium.find_element(By.CLASS_NAME, 'error-message').text
except:
return None
def is_on_login_page(self):
return '/accounts/login/' in self.selenium.current_url
Теперь мы можем использовать этот объект в наших тестах:
# tests/test_login_with_page_objects.py
from .test_selenium import SeleniumTestCase
from .page_objects.login_page import LoginPage
from django.contrib.auth.models import User
class LoginWithPageObjectsTestCase(SeleniumTestCase):
def setUp(self):
super().setUp()
# Создаем тестового пользователя
self.username = 'testuser'
self.password = 'password123'
User.objects.create_user(
username=self.username,
email='test@example.com',
password=self.password
)
self.login_page = LoginPage(self.selenium, self.live_server_url)
def test_login_success(self):
"""Проверяет успешный вход в систему с использованием Page Object."""
self.login_page.navigate().login(self.username, self.password)
# Проверки после успешного входа
self.assertEqual(self.selenium.current_url, f"{self.live_server_url}/")
self.assertFalse(self.login_page.is_on_login_page())
def test_login_failure(self):
"""Проверяет неудачный вход в систему с использованием Page Object."""
self.login_page.navigate().login(self.username, 'wrong_password')
# Проверки после неудачного входа
self.assertTrue(self.login_page.is_on_login_page())
error_message = self.login_page.get_error_message()
self.assertIsNotNone(error_message)
self.assertIn('Invalid credentials', error_message)
Поиск и взаимодействие с элементами веб-страницы
Эффективный поиск элементов на веб-странице — это фундаментальный навык при автоматизации тестирования с Selenium. Django-приложения часто имеют динамическую структуру DOM, что делает этот аспект особенно важным для создания стабильных тестов.
Selenium предлагает несколько стратегий поиска элементов, каждая со своими преимуществами:
- By.ID — наиболее эффективный и стабильный способ
- By.NAME — полезно для форм
- By.CLASS_NAME — для элементов с определенным классом
- By.TAG_NAME — поиск по типу HTML-элемента
- By.XPATH — мощный, но хрупкий способ навигации по DOM
- By.CSS_SELECTOR — гибкий и читаемый способ поиска
- By.LINK_TEXT и By.PARTIALLINKTEXT — для поиска ссылок
Вот пример использования различных селекторов для поиска элементов на Django-странице:
# Поиск по ID
submit_button = self.selenium.find_element(By.ID, 'submit-button')
# Поиск по имени (полезно для форм)
username_input = self.selenium.find_element(By.NAME, 'username')
# Поиск по классу CSS
success_messages = self.selenium.find_elements(By.CLASS_NAME, 'alert-success')
# Поиск по XPath
logout_link = self.selenium.find_element(
By.XPATH, '//a[contains(text(), "Logout")]'
)
# Поиск по CSS-селектору
sidebar_items = self.selenium.find_elements(
By.CSS_SELECTOR, '.sidebar .nav-item'
)
# Поиск по тексту ссылки
profile_link = self.selenium.find_element(By.LINK_TEXT, 'Profile')
# Поиск по частичному тексту ссылки
admin_link = self.selenium.find_element(By.PARTIAL_LINK_TEXT, 'Admin')
После того как элемент найден, вы можете взаимодействовать с ним различными способами:
# Клик по элементу
submit_button.click()
# Ввод текста
username_input.send_keys('testuser')
# Очистка поля ввода
username_input.clear()
# Получение атрибута
href_value = profile_link.get_attribute('href')
# Получение текста
welcome_text = welcome_message.text
# Проверка видимости элемента
is_visible = submit_button.is_displayed()
# Проверка активности элемента
is_enabled = submit_button.is_enabled()
# Проверка выбора элемента (для checkbox, radio)
is_selected = checkbox.is_selected()
# Отправка нажатий клавиш
from selenium.webdriver.common.keys import Keys
search_box.send_keys(Keys.ENTER)
Особенно важно в Django-приложениях правильно работать с ожиданиями. Динамические интерфейсы и AJAX-запросы могут создавать ситуации, когда элемент еще не появился на странице в момент его поиска. Selenium предлагает два типа ожиданий:
- Неявные ожидания — глобальная настройка для всех операций поиска элементов
- Явные ожидания — точечные ожидания для конкретных условий
Вот пример использования явных ожиданий для работы с динамическими элементами в Django-приложении:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Ожидание появления элемента (до 10 секунд)
element = WebDriverWait(self.selenium, 10).until(
EC.presence_of_element_located((By.ID, 'dynamic-content'))
)
# Ожидание видимости элемента
element = WebDriverWait(self.selenium, 10).until(
EC.visibility_of_element_located((By.ID, 'loading-complete'))
)
# Ожидание кликабельности элемента
element = WebDriverWait(self.selenium, 10).until(
EC.element_to_be_clickable((By.ID, 'submit-button'))
)
# Ожидание исчезновения элемента
WebDriverWait(self.selenium, 10).until(
EC.invisibility_of_element_located((By.ID, 'loading-spinner'))
)
# Ожидание появления текста в элементе
WebDriverWait(self.selenium, 10).until(
EC.text_to_be_present_in_element((By.ID, 'status'), 'Success')
)
# Ожидание изменения URL
WebDriverWait(self.selenium, 10).until(
EC.url_contains('/dashboard/')
)
Марина Соколова, Automation QA Lead
В одном из проектов мы столкнулись с серьезной проблемой: наши Selenium-тесты постоянно "падали" на проверке интерфейса административной панели Django. Особенно коварными оказались выпадающие списки с динамической загрузкой и модальные окна.
Мы пытались использовать простые time.sleep(), но это делало тесты либо нестабильными, либо неоправданно долгими. Переломный момент наступил, когда мы полностью пересмотрели стратегию поиска элементов.
Во-первых, мы начали добавлять к ключевым элементам интерфейса data-testid атрибуты прямо в Django-шаблонах:
HTMLСкопировать код<button data-testid="save-profile" class="btn btn-primary">Save</button>Во-вторых, отказались от XPath в пользу CSS-селекторов и поиска по data-testid:
PythonСкопировать кодsave_button = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="save-profile"]')) )В-третьих, для особо сложных динамических компонентов мы написали собственные условия ожидания. Результат превзошел ожидания — стабильность тестов выросла с 60% до 98%, а среднее время прогона сократилось на 40%. Этот подход мы теперь применяем во всех новых проектах.
Для работы со сложными элементами интерфейса в Django-приложениях, такими как выпадающие списки, модальные окна и многоступенчатые формы, Selenium предоставляет специализированные классы:
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.action_chains import ActionChains
# Работа с выпадающими списками
select_element = Select(self.selenium.find_element(By.ID, 'category-select'))
select_element.select_by_visible_text('Electronics') # Выбор по тексту
select_element.select_by_value('electronics') # Выбор по значению
select_element.select_by_index(1) # Выбор по индексу
# Получение выбранной опции
selected_option = select_element.first_selected_option
selected_text = selected_option.text
# Сложные взаимодействия с помощью ActionChains
actions = ActionChains(self.selenium)
# Перетаскивание элемента
actions.drag_and_drop(source_element, target_element).perform()
# Наведение мыши на элемент
actions.move_to_element(menu_item).perform()
# Клик с зажатой клавишей Ctrl
actions.key_down(Keys.CONTROL).click(element).key_up(Keys.CONTROL).perform()
# Двойной клик
actions.double_click(element).perform()
# Контекстное меню (правый клик)
actions.context_click(element).perform()
Тестирование форм и Ajax-запросов в Django-приложениях
Тестирование форм и асинхронных запросов — одна из самых сложных задач при автоматизации Django-приложений с использованием Selenium. Формы часто содержат валидацию на стороне клиента и сервера, а AJAX-запросы добавляют асинхронность, что усложняет процесс тестирования. 🔍
Начнем с базового примера тестирования формы регистрации в Django-приложении:
class RegistrationFormTestCase(SeleniumTestCase):
def test_successful_registration(self):
"""Тест успешной регистрации пользователя."""
# Переходим на страницу регистрации
self.selenium.get(f"{self.live_server_url}/accounts/register/")
# Заполняем форму регистрации
self.selenium.find_element(By.ID, 'id_username').send_keys('newuser')
self.selenium.find_element(By.ID, 'id_email').send_keys('newuser@example.com')
self.selenium.find_element(By.ID, 'id_password1').send_keys('secure_password123')
self.selenium.find_element(By.ID, 'id_password2').send_keys('secure_password123')
# Отправляем форму
self.selenium.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
# Проверяем, что мы были перенаправлены на страницу успешной регистрации
WebDriverWait(self.selenium, 10).until(
EC.url_contains('/accounts/register/success/')
)
# Проверяем, что пользователь создан в базе данных
self.assertTrue(User.objects.filter(username='newuser').exists())
def test_form_validation_errors(self):
"""Тест валидации формы регистрации."""
# Переходим на страницу регистрации
self.selenium.get(f"{self.live_server_url}/accounts/register/")
# Заполняем форму с некорректными данными
self.selenium.find_element(By.ID, 'id_username').send_keys('new') # Слишком короткое имя
self.selenium.find_element(By.ID, 'id_email').send_keys('invalid-email') # Некорректный email
self.selenium.find_element(By.ID, 'id_password1').send_keys('123') # Слишком простой пароль
self.selenium.find_element(By.ID, 'id_password2').send_keys('456') # Пароли не совпадают
# Отправляем форму
self.selenium.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
# Проверяем наличие сообщений об ошибках
error_messages = self.selenium.find_elements(By.CSS_SELECTOR, '.error-message')
self.assertTrue(len(error_messages) >= 4) # Как минимум 4 ошибки
error_texts = [error.text for error in error_messages]
self.assertIn('Username is too short', ' '.join(error_texts))
self.assertIn('Enter a valid email address', ' '.join(error_texts))
self.assertIn('Password is too short', ' '.join(error_texts))
self.assertIn('Passwords do not match', ' '.join(error_texts))
Для тестирования форм с AJAX-валидацией и отправкой необходимо правильно работать с ожиданиями и проверять результаты асинхронных запросов:
class AjaxFormTestCase(SeleniumTestCase):
def test_ajax_form_submission(self):
"""Тест отправки формы через AJAX."""
# Переходим на страницу с формой
self.selenium.get(f"{self.live_server_url}/contact/")
# Заполняем поля формы
self.selenium.find_element(By.ID, 'id_name').send_keys('Test User')
self.selenium.find_element(By.ID, 'id_email').send_keys('test@example.com')
self.selenium.find_element(By.ID, 'id_message').send_keys('This is a test message')
# Отправляем форму (которая использует AJAX)
self.selenium.find_element(By.ID, 'submit-contact-form').click()
# Ожидаем появления сообщения об успешной отправке
success_message = WebDriverWait(self.selenium, 10).until(
EC.visibility_of_element_located((By.ID, 'form-success-message'))
)
self.assertIn('Thank you for your message', success_message.text)
# Проверяем, что форма была очищена после отправки
name_field = self.selenium.find_element(By.ID, 'id_name')
self.assertEqual('', name_field.get_attribute('value'))
def test_ajax_form_validation(self):
"""Тест валидации формы через AJAX."""
# Переходим на страницу с формой
self.selenium.get(f"{self.live_server_url}/contact/")
# Вводим некорректный email (AJAX-валидация срабатывает при потере фокуса)
email_field = self.selenium.find_element(By.ID, 'id_email')
email_field.send_keys('invalid-email')
# Кликаем в другое поле для потери фокуса и запуска валидации
self.selenium.find_element(By.ID, 'id_name').click()
# Ожидаем появления сообщения об ошибке валидации
error_message = WebDriverWait(self.selenium, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '#id_email_error'))
)
self.assertIn('valid email', error_message.text.lower())
Для тестирования более сложных сценариев с AJAX-запросами, таких как бесконечная прокрутка или динамическая фильтрация, необходимо использовать комбинацию ожиданий и проверок состояния DOM:
class AjaxPaginationTestCase(SeleniumTestCase):
def test_infinite_scroll(self):
"""Тест бесконечной прокрутки с подгрузкой через AJAX."""
# Создаем тестовые данные (например, 50 объектов)
for i in range(50):
Product.objects.create(
name=f"Product {i}",
price=10 + i,
description=f"Description for product {i}"
)
# Переходим на страницу со списком продуктов
self.selenium.get(f"{self.live_server_url}/products/")
# Получаем начальное количество элементов
initial_items = len(self.selenium.find_elements(By.CSS_SELECTOR, '.product-item'))
# Прокручиваем страницу до конца для запуска подгрузки
self.selenium.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# Ожидаем увеличения количества элементов (подгрузка через AJAX)
WebDriverWait(self.selenium, 10).until(
lambda driver: len(driver.find_elements(By.CSS_SELECTOR, '.product-item')) > initial_items
)
# Получаем новое количество элементов
new_items_count = len(self.selenium.find_elements(By.CSS_SELECTOR, '.product-item'))
# Проверяем, что количество элементов увеличилось
self.assertGreater(new_items_count, initial_items)
def test_ajax_filtering(self):
"""Тест динамической фильтрации через AJAX."""
# Создаем тестовые данные разных категорий
categories = ['Electronics', 'Books', 'Clothing']
for cat in categories:
Category.objects.create(name=cat)
for i in range(30):
category = Category.objects.get(name=categories[i % 3])
Product.objects.create(
name=f"Product {i}",
price=10 + i,
category=category,
description=f"Description for product {i}"
)
# Переходим на страницу со списком продуктов
self.selenium.get(f"{self.live_server_url}/products/")
# Получаем начальное количество элементов (все продукты)
initial_items = len(self.selenium.find_elements(By.CSS_SELECTOR, '.product-item'))
# Применяем фильтр по категории
filter_dropdown = Select(self.selenium.find_element(By.ID, 'category-filter'))
filter_dropdown.select_by_visible_text('Books')
# Ожидаем обновления списка через AJAX
# Примечание: мы ожидаем, что количество элементов изменится
WebDriverWait(self.selenium, 10).until(
lambda driver: len(driver.find_elements(By.CSS_SELECTOR, '.product-item')) != initial_items
)
# Получаем отфильтрованные элементы
filtered_items = self.selenium.find_elements(By.CSS_SELECTOR, '.product-item')
# Проверяем, что все отображаемые элементы относятся к категории 'Books'
for item in filtered_items:
category_badge = item.find_element(By.CSS_SELECTOR, '.category-badge')
self.assertEqual('Books', category_badge.text)
Типичные проблемы при тестировании форм и AJAX в Django-приложениях и их решения:
| Проблема | Решение | Пример кода |
|---|---|---|
| CSRF-токен в формах | Django вставляет CSRF-токены в формы, которые могут вызывать проблемы при тестировании | Использовать LiveServerTestCase, который правильно обрабатывает CSRF |
| Гонки условий при AJAX-запросах | Использовать явные ожидания для отслеживания состояния DOM после AJAX-запроса | WebDriverWait с custom условиями |
| Динамически генерируемые ID элементов | Использовать data-атрибуты для тестирования вместо полагания на auto-ID Django | Добавлять data-testid в шаблоны |
| Модальные окна и всплывающие элементы | Правильно отслеживать появление и взаимодействие с модальными окнами | WebDriverWait для visibility элементов модального окна |
Интеграция Selenium с CI/CD для Django-проектов
Интеграция Selenium-тестов в CI/CD-пайплайн — критически важный шаг для обеспечения непрерывной проверки качества вашего Django-приложения. Это позволяет автоматически выявлять регрессии в пользовательском интерфейсе после каждого изменения кода. 🔄
Рассмотрим пошаговую интеграцию Selenium-тестов с популярными CI/CD-системами для Django-проектов:
1. Подготовка проекта для CI/CD
Сначала необходимо создать конфигурацию для запуска тестов в CI-окружении. Создайте файл settings_ci.py с настройками для CI:
# settings_ci.py
from .settings import *
# Использование in-memory SQLite для тестов
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
# Настройки Selenium для CI
SELENIUM_HEADLESS = True
DEBUG = False
2. Интеграция с GitHub Actions
Создайте файл .github/workflows/tests.yml:
name: Django Selenium Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Set up Chrome
uses: browser-actions/setup-chrome@latest
- name: Run Django tests
run: |
python manage.py test --settings=myproject.settings_ci
env:
DJANGO_SETTINGS_MODULE: myproject.settings_ci
3. Интеграция с GitLab CI
Создайте файл .gitlab-ci.yml:
stages:
- test
django-selenium-tests:
stage: test
image: python:3.9
services:
- selenium/standalone-chrome:latest
variables:
DJANGO_SETTINGS_MODULE: myproject.settings_ci
before_script:
- pip install -r requirements.txt
script:
- python manage.py test --settings=myproject.settings_ci
4. Интеграция с Jenkins
Создайте Jenkinsfile:
pipeline {
agent {
docker {
image 'python:3.9'
}
}
environment {
DJANGO_SETTINGS_MODULE = 'myproject.settings_ci'
}
stages {
stage('Setup') {
steps {
sh 'pip install -r requirements.txt'
sh 'apt-get update && apt-get install -y chromium-driver'
}
}
stage('Test') {
steps {
sh 'python manage.py test --settings=myproject.settings_ci'
}
}
}
post {
always {
junit 'test-reports/*.xml'
}
}
}
Для более надежных CI/CD-пайплайнов рекомендую использовать Docker-контейнеры, которые обеспечивают стабильное окружение для Selenium-тестов. Вот пример Dockerfile для тестирования Django с Selenium:
# Dockerfile.test
FROM python:3.9
# Установка Chrome и ChromeDriver
RUN apt-get update && apt-get install -y \
wget \
gnupg \
unzip
RUN wget -q -O – https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add – \
&& echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
&& apt-get update \
&& apt-get install -y google-chrome-stable
# Установка ChromeDriver
RUN CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \
&& wget -q -O /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip \
&& unzip /tmp/chromedriver.zip -d /usr/local/bin \
&& rm /tmp/chromedriver.zip \
&& chmod +x /usr/local/bin/chromedriver
# Установка зависимостей проекта
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# Копирование кода проекта
COPY . .
# Команда для запуска тестов
CMD ["python", "manage.py", "test", "--settings=myproject.settings_ci"]
Для интеграции в Docker Compose создайте файл docker-compose.test.yml:
version: '3'
services:
test:
build:
context: .
dockerfile: Dockerfile.test
environment:
- DJANGO_SETTINGS_MODULE=myproject.settings_ci
volumes:
- ./test-reports:/app/test-reports
Для генерации отчетов о тестировании, которые можно интегрировать с CI/CD-системами, используйте pytest с плагинами для генерации отчетов:
# conftest.py
import os
import pytest
from django.conf import settings
@pytest.fixture(scope='session')
def django_db_setup():
settings.DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
@pytest.fixture(autouse=True)
def enable_db_access_for_all_tests(db):
pass
def pytest_configure(config):
config.option.xmlpath = 'test-reports/junit.xml'
config.option.htmlpath = 'test-reports/report.html'
Для запуска тестов с помощью pytest добавьте в requirements.txt:
pytest
pytest-django
pytest-html
pytest-selenium
Команда для запуска тестов с генерацией отчетов:
pytest --html=test-reports/report.html --junitxml=test-reports/junit.xml
Для оптимизации CI/CD-пайплайна с Selenium-тестами рекомендую использовать следующие стратегии:
- Параллельное выполнение тестов — разделите тесты на группы для ускорения выполнения
- Кеширование зависимостей — используйте кеширование pip для ускорения установки
- Запуск только измененных тестов — используйте инкрементальное тестирование
- Выборочный запуск тестов — используйте маркеры для критичных тестов
- Повторные попытки нестабильных тестов — настройте автоматические повторы для флаки-тестов
Пример организации тестов с маркерами для выборочного запуска в CI:
# tests/test_critical_flows.py
import pytest
@pytest.mark.critical
class CriticalUserFlowsTest(SeleniumTestCase):
def test_user_registration(self):
# Тест критичного процесса регистрации
pass
def test_checkout_process(self):
# Тест критичного процесса оформления заказа
pass
# В CI/CD можно запускать только критичные тесты:
# pytest -m critical --html=test-reports/report.html
Интеграция результатов Selenium-тестов с системами мониторинга и оповещения позволяет быстро реагировать на проблемы:
# hooks/slack_notify.py
import os
import requests
import json
def notify_slack(test_result):
webho
**Читайте также**
- [Django миграции: полное руководство для веб-разработчиков](/python/migracii-bazy-dannyh-v-django/)
- [Django деплой: от локальной разработки до боевого сервера – тонкости](/python/razvertyvanie-django-prilozhenij/)
- [Система шаблонов Django: как использовать для создания динамических сайтов](/python/rabota-s-shablonami-v-django/)
- [Установка Django: пошаговая инструкция для начинающих разработчиков](/python/ustanovka-i-nastrojka-django/)
- [15 лучших инструментов для профессиональной разработки на Django](/python/poleznye-instrumenty-i-plaginy-dlya-django/)
- [Django: история от газетного проекта до глобального фреймворка](/python/istoriya-sozdaniya-django/)
- [Адаптивный интерфейс в Django: от мобильной катастрофы к идеалу](/python/sozdanie-adaptivnogo-interfejsa-v-django/)
- [Выбор между функциональными и классовыми представлениями в Django](/python/funkcionalnye-i-klassovye-predstavleniya-v-django/)
- [Django-разработка: первое приложение с нуля до публикации](/python/sozdanie-pervogo-veb-prilozheniya-na-django/)
- [Формы и валидация в Django: полное руководство для разработчиков](/python/rabota-s-formami-i-validaciej-v-django/)


