Автоматизация тестов Django: интеграция Selenium для веб-разработки

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Разработчики, использующие 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 и добавьте следующие настройки:

Python
Скопировать код
# settings.py
SELENIUM_DRIVER = 'chrome' # Выбор драйвера: chrome, firefox, edge
SELENIUM_BROWSER_EXECUTABLE_PATH = None # Опционально: путь к исполняемому файлу браузера
SELENIUM_HEADLESS = True # Запуск браузера в фоновом режиме

Далее создадим базовый класс для тестов с Selenium в Django. В директории вашего приложения создайте файл tests/test_selenium.py:

Python
Скопировать код
# 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, которая будет создавать нужный драйвер в зависимости от настроек:

Python
Скопировать код
# 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-приложения:

Python
Скопировать код
# 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)

Для более сложных сценариев, таких как тестирование авторизации, создадим соответствующий тест-кейс:

Python
Скопировать код
# 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), которые инкапсулируют взаимодействие с конкретными страницами приложения:

Python
Скопировать код
# 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

Теперь мы можем использовать этот объект в наших тестах:

Python
Скопировать код
# 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-странице:

Python
Скопировать код
# Поиск по 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')

После того как элемент найден, вы можете взаимодействовать с ним различными способами:

Python
Скопировать код
# Клик по элементу
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 предлагает два типа ожиданий:

  1. Неявные ожидания — глобальная настройка для всех операций поиска элементов
  2. Явные ожидания — точечные ожидания для конкретных условий

Вот пример использования явных ожиданий для работы с динамическими элементами в Django-приложении:

Python
Скопировать код
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 предоставляет специализированные классы:

Python
Скопировать код
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-приложении:

Python
Скопировать код
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-валидацией и отправкой необходимо правильно работать с ожиданиями и проверять результаты асинхронных запросов:

Python
Скопировать код
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:

Python
Скопировать код
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:

Python
Скопировать код
# 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:

yaml
Скопировать код
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:

yaml
Скопировать код
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:

groovy
Скопировать код
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
Скопировать код
# 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:

yaml
Скопировать код
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 с плагинами для генерации отчетов:

Python
Скопировать код
# 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:

Python
Скопировать код
# 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-тестов с системами мониторинга и оповещения позволяет быстро реагировать на проблемы:

Python
Скопировать код
# 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/)

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Какой инструмент используется для автоматизации веб-браузеров в Django?
1 / 5

Загрузка...