Автоматизация веб-интерфейсов: Selenium и Python
Основы тестирования веб-интерфейсов с Selenium
Как настроить и выполнить первый тест с Selenium WebDriver на Python?
Наиболее эффективный способ запуска автоматизированных тестов – использование библиотеки Selenium с драйвером браузера. Ниже приведен стандартный сценарий, позволяющий открыть страницу, найти элемент и выполнить с ним действие.
from selenium import webdriver
from selenium.webdriver.common.by import By
# Настройка драйвера Chrome
driver = webdriver.Chrome()
# Открыть страницу
driver.get("https://example.com")
# Найти элемент по CSS-селектору
search_box = driver.find_element(By.CSS_SELECTOR, "input[name='q']")
search_box.send_keys("Selenium Python")
# Отправить форму
search_box.submit()
# Закрыть браузер
driver.quit()тестирование python selenium (тестирование с selenium в python)
В этом коде используется webdriver.Chrome(), который автоматически загружает драйвер ChromeDriver, если он установлен в PATH. Если драйвер не найден, возникает ошибка WebDriverException. Решение – указать путь к драйверу явно: webdriver.Chrome(executable_path='/path/to/chromedriver').
Типичные проблемы и их решение
- NoSuchElementException: элемент не найден. Проверьте селектор, добавьте ожидание загрузки элемента.
- StaleElementReferenceException: элемент устарел после обновления страницы. Повторите поиск элемента заново.
- ElementClickInterceptedException: другой элемент перекрывает кнопку. Используйте JavaScript для клика или прокрутку.
Как обрабатывать ожидания появления динамических элементов?
В веб-приложениях элементы часто появляются после загрузки данных или анимации. Без явных ожиданий тест может упасть. Рекомендуется использовать WebDriverWait вместе с ожидаемыми условиями.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com")
# Ожидание до 10 секунд, пока элемент не станет кликабельным
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn")))
element.click()Такой подход минимизирует ложные сбои из-за времени загрузки. Вместо фиксированных пауз (time.sleep) применяются умные ожидания.
Возможные ошибки
- TimeoutException: элемент не появился в течение заданного времени. Увеличьте таймаут или проверьте правильность селектора.
- Неправильное условие: выбор неверного типа ожидания (например, presence_of_element_located вместо visibility_of_element_located).
Как взаимодействовать с выпадающими списками (select)?
Для работы с HTML-элементами <select> существует специальный класс Select. Он предоставляет методы выбора по видимому тексту, значению атрибута value или индексу.
from selenium.webdriver.support.ui import Select
driver = webdriver.Chrome()
driver.get("https://example.com/form")
select_element = driver.find_element(By.ID, "country")
select = Select(select_element)
# Выбор по видимому тексту
select.select_by_visible_text("Россия")
# Выбор по значению атрибута value
select.select_by_value("ru")
# Выбор по индексу (начиная с 0)
select.select_by_index(2)Если элемент не является обычным <select>, а реализован через div и li, потребуется кликать по пунктам списка вручную.
Проблемы
- UnexpectedTagNameException: элемент не является тегом select. Используйте другой подход.
- Элемент может быть неактивен или скрыт; примените ожидание кликабельности.
Как работать с фреймами и вкладками?
Фреймы (iframe) требуют переключения контекста драйвера. Аналогично для новых вкладок и окон необходимо переключать вкладку.
# Переключение в iframe по индексу или элементу
driver.switch_to.frame(0) # первый iframe
driver.switch_to.frame("frame-name")
driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "iframe"))
# После работы в iframe вернуться к основному документу
driver.switch_to.default_content()
# Переключение на новую вкладку, открытую через JavaScript
original_window = driver.current_window_handle
# ... действие, открывающее вкладку ...
for window_handle in driver.window_handles:
if window_handle != original_window:
driver.switch_to.window(window_handle)
breakБез переключения попытка найти элемент внутри iframe или в новой вкладке вызовет NoSuchElementException.
Типичные ошибки
- Попытка искать элемент до переключения в iframe.
- Потеря ссылки на исходное окно после переключения; сохраните handle.
Как выполнить JavaScript в контексте страницы?
Иногда нужно изменить атрибуты элемента, прокрутить страницу или выполнить код, который не поддерживается Selenium напрямую. Используется метод execute_script.
# Прокрутка до элемента
driver.execute_script("arguments[0].scrollIntoView(true);", element)
# Скрытие элемента (например, для обхода рекламы)
driver.execute_script("arguments[0].style.display='none';", element)
# Получение текста из теневого DOM
text = driver.execute_script("return arguments[0].shadowRoot.querySelector('.some-class').textContent;", host_element)Выполнение JavaScript может нарушить изоляцию теста, но иногда это единственный способ взаимодействия.
Проблемы
- Передача неправильного объекта в arguments[0].
- Синтаксическая ошибка в скрипте – исключение JavascriptException.
Как сделать скриншот страницы или элемента?
Снимки экрана полезны для документирования ошибок. Selenium позволяет делать как полный скриншот страницы, так и только определённого элемента.
# Полный скриншот страницы
driver.save_screenshot("full_page.png")
# Скриншот конкретного элемента
element.screenshot("element.png")Если нужно захватить всю страницу с прокруткой, можно использовать метод driver.get_full_page_screenshot_as_file() (доступен не во всех браузерах) или объединить несколько скриншотов с помощью сторонних библиотек.
Типичные ошибки
- Путь к файлу не существует или нет прав на запись.
- Скриншот элемента может быть пустым, если элемент скрыт.
Как организовать тесты по шаблону Page Object Model?
Для поддержки крупных проектов применяется паттерн Page Object. Каждая страница представляется классом, в котором описаны элементы и методы взаимодействия.
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.username_field = (By.ID, "username")
self.password_field = (By.ID, "password")
self.login_button = (By.CSS_SELECTOR, "button[type='submit']")
def enter_username(self, username):
self.driver.find_element(*self.username_field).send_keys(username)
def enter_password(self, password):
self.driver.find_element(*self.password_field).send_keys(password)
def click_login(self):
self.driver.find_element(*self.login_button).click()
# Использование в тесте
driver = webdriver.Chrome()
driver.get("https://example.com/login")
login_page = LoginPage(driver)
login_page.enter_username("admin")
login_page.enter_password("secret")
login_page.click_login()Этот подход упрощает поддержку тестов: при изменении локатора достаточно обновить один класс.
Проблемы
- Излишнее усложнение для простых тестов.
- Необходимость синхронизации page-объектов с изменениями интерфейса.
Как выполнять тесты в headless режиме?
Headless режим позволяет запускать браузер без графического интерфейса, что ускоряет выполнение тестов на серверах.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.headless = True
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
print(driver.title) # Вывод заголовка страницы
driver.quit()В Firefox аналогично используется Options() и атрибут headless. Headless режим может прозводить иные размеры окна, что влияет на адаптивный дизайн.
Проблемы
- Некоторые функции (например, загрузка файлов) могут работать нестабильно.
- Проблемы с отображением шрифтов или иконок.
Расширенные примеры и редкие сценарии
Как использовать кастомные ожидания с условием?
Иногда встроенных условий недостаточно. Можно написать свой класс, реализующий expected_condition.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
class element_has_text(object):
def __init__(self, locator, text):
self.locator = locator
self.text = text
def __call__(self, driver):
element = driver.find_element(*self.locator)
if self.text in element.text:
return element
else:
return False
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_text((By.ID, "status"), "Завершено"))
print(element.text)Завершено
Такое ожидание будет ждать, пока в тексте элемента не появится нужная подстрока.
Как управлять куками браузера?
Куки можно добавлять, удалять и получать для настройки сессии.
# Получить все куки
cookies = driver.get_cookies()
# Добавить куку
driver.add_cookie({"name": "session_id", "value": "abc123", "domain": ".example.com"})
# Удалить куку по имени
driver.delete_cookie("session_id")
# Очистить все куки
driver.delete_all_cookies()
# Проверить, что кука установлена
driver.refresh() # обновление страницы для применения куки
print(driver.get_cookie("session_id")){'name': 'session_id', 'value': 'abc123', ...}Как работать с алертами и модальными окнами?
Для работы с JavaScript алертами, подтверждениями и промптами используется switch_to.alert.
# Нажатие кнопки, вызывающей alert
driver.find_element(By.ID, "alert-btn").click()
# Переключение на alert
alert = driver.switch_to.alert
# Получить текст алерта
print(alert.text)
# Принять (OK)
alert.accept()
# Для confirm: alert.dismiss() – отмена
# Для prompt: alert.send_keys("текст") – затем accept()Если игнорировать алерт, дальнейшие действия вызовут UnexpectedAlertPresentException.
Как использовать ActionChains для сложных взаимодействий?
ActionChains позволяет выполнять последовательности действий: перетаскивание, hover, двойной клик, контекстное меню.
from selenium.webdriver import ActionChains
action = ActionChains(driver)
# Перетаскивание элемента source на target
action.drag_and_drop(source_element, target_element).perform()
# Наведение мыши на элемент
action.move_to_element(element).perform()
# Двойной клик
action.double_click(element).perform()
# Комбинация клавиш (Ctrl+A)
action.key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform()
# Клик и удержание
action.click_and_hold(element).move_by_offset(50, 0).release().perform()Цепочки действий могут быть не выполнены, если элемент не видим или перекрыт.
Как работать с Shadow DOM?
Элементы, инкапсулированные в Shadow DOM, недоступны через обычные селекторы. Необходимо получить корневой элемент Shadow Root через JavaScript.
# Псевдо-селектор /deep/ и >>> устарели, используем execute_script
host_element = driver.find_element(By.CSS_SELECTOR, "my-component")
shadow_root = driver.execute_script("return arguments[0].shadowRoot", host_element)
# Теперь shadow_root можно использовать как обычный элемент (но только с Selenium 4)
# Для поиска внутри Shadow DOM:
inner_element = shadow_root.find_element(By.CSS_SELECTOR, ".inner")
print(inner_element.text)В Selenium 4 поиск внутри shadow root доступен через метод find_element, но в ранних версиях нужен JavaScript.
Как запустить тесты параллельно с Selenium Grid?
Selenium Grid позволяет распределить тесты между несколькими узлами (nodes) и браузерами. На машине запускается хаб, а тесты подключаются к нему через Remote WebDriver.
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
driver = webdriver.Remote(
command_executor='http://localhost:4444/wd/hub',
desired_capabilities=DesiredCapabilities.CHROME
)
driver.get("https://example.com")
print(driver.title)
driver.quit()Параллельное выполнение обычно настраивается через тестовый фреймворк (pytest, unittest) с использованием плагинов (pytest-xdist). Каждый тест получает свой driver, созданный через Remote.
Example Domain
Как использовать браузерный профиль для сохранения сессии?
При тестировании может потребоваться сохранить куки и локальное хранилище между запусками. Для Chrome используется user-data-dir.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--user-data-dir=/path/to/profile")
options.add_argument("--profile-directory=Default")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
# Все данные сессии сохранятся в profileВ Firefox используется FirefoxProfile.
Этот подход позволяет не логиниться каждый раз заново, но профиль может быть поврежден или занят другим процессом.