Автоматизация веб-интерфейсов: 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.

Этот подход позволяет не логиниться каждый раз заново, но профиль может быть поврежден или занят другим процессом.

Тестирование с Selenium в Python - comments

En
тестирование python selenium (python)