🌐 Selenium

Полный справочник: WebDriver, установка, локаторы, ожидания, действия, окна, фреймы, JavaScript, Page Object, pytest и JUnit, Grid, headless, CI, отладка и частые проблемы.

Шпаргалки · Тестирование · #testing #selenium #webdriver #ui #automation

Что это

Selenium WebDriver управляет реальным браузером по стандарту W3C WebDriver. Языки: Python, Java, C#, JavaScript, Ruby, Kotlin. Браузеры: Chrome, Firefox, Edge, Safari. Selenium Manager (с версии 4.6) сам скачивает драйвер. Компоненты: WebDriver (код), Grid (параллельный запуск), IDE (запись действий, расширение браузера). Selenium 4 поддерживает относительные локаторы, CDP / BiDi, окна и вкладки, улучшенный Grid.

Установка

pip install selenium pytest                          # Python
npm i selenium-webdriver                              # JavaScript
# Java (Maven): org.seleniumhq.selenium:selenium-java:4.x
# C#: dotnet add package Selenium.WebDriver

Первый тест (Python)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")            # без окна (для CI)
options.add_argument("--window-size=1440,900")
driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com/login")
    driver.find_element(By.ID, "email").send_keys("anna@x.ru")
    driver.find_element(By.CSS_SELECTOR, "input[type=password]").send_keys("Qwerty123!", Keys.ENTER)
    WebDriverWait(driver, 10).until(EC.url_contains("/profile"))
    assert "Анна" in driver.find_element(By.CSS_SELECTOR, ".user-name").text
finally:
    driver.quit()                                  # закрывает окно и процесс драйвера

Java

WebDriver driver = new ChromeDriver(new ChromeOptions().addArguments("--headless=new"));
try {
    driver.get("https://example.com");
    WebElement btn = new WebDriverWait(driver, Duration.ofSeconds(10))
        .until(ExpectedConditions.elementToBeClickable(By.cssSelector("button.submit")));
    btn.click();
    Assert.assertEquals(driver.getTitle(), "Готово");
} finally { driver.quit(); }

Навигация и страница

driver.get(url); driver.back(); driver.forward(); driver.refresh()
driver.current_url; driver.title; driver.page_source
driver.set_window_size(1280, 800); driver.maximize_window(); driver.get_window_size()
driver.save_screenshot("page.png"); element.screenshot("el.png")
driver.get_cookies(); driver.add_cookie({"name": "sid", "value": "abc"}); driver.delete_all_cookies()

Локаторы

Локатор Пример Заметка
By.ID By.ID, "email" самый быстрый и надёжный
By.NAME By.NAME, "q"
By.CSS_SELECTOR .card > a[href^='/p'], [data-testid='submit'] основной выбор
By.XPATH //div[@data-id='5']//button[contains(., 'Купить')] когда нужен поиск по тексту или вверх по дереву
By.LINK_TEXT, PARTIAL_LINK_TEXT "Выход" только ссылки
By.CLASS_NAME, By.TAG_NAME простые случаи

Приоритет: data-testid / id → CSS → XPath. Избегайте длинных абсолютных путей (/html/body/div[3]/...) и локаторов по тексту, который меняется. Относительные локаторы (Selenium 4): locate_with(By.TAG_NAME, "input").below(el).to_right_of(label).

els = driver.find_elements(By.CSS_SELECTOR, "li.item")      # список (пустой, если нет), без исключения
el.find_element(By.CSS_SELECTOR, ".price")                   # поиск внутри элемента
el.text; el.get_attribute("href"); el.get_property("value"); el.is_displayed(); el.is_enabled(); el.is_selected(); el.value_of_css_property("color")
el.click(); el.send_keys("текст"); el.clear(); el.submit()

Ожидания (главная причина нестабильности тестов)

Вид Суть
Явное WebDriverWait ждёт условия до таймаута: рекомендуется
Неявное implicitly_wait(5) общий таймаут поиска элемента; не смешивайте с явным
Fluent своя частота опроса, игнорируемые исключения
time.sleep не используйте вместо ожиданий
wait = WebDriverWait(driver, 10, poll_frequency=0.5, ignored_exceptions=[StaleElementReferenceException])
wait.until(EC.visibility_of_element_located((By.ID, "result")))
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.pay"))).click()
wait.until(EC.text_to_be_present_in_element((By.ID, "status"), "Готово"))
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".spinner")))
wait.until(EC.number_of_windows_to_be(2))
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")

Условия: presence_of_element_located (есть в DOM), visibility_of... (видим), element_to_be_clickable, alert_is_present, frame_to_be_available_and_switch_to_it, staleness_of, title_contains, url_contains.

Формы и выбор

from selenium.webdriver.support.ui import Select
s = Select(driver.find_element(By.ID, "city"))
s.select_by_visible_text("Москва"); s.select_by_value("msk"); s.select_by_index(0); s.first_selected_option.text
checkbox.click() if not checkbox.is_selected() else None
driver.find_element(By.CSS_SELECTOR, "input[type=file]").send_keys("/abs/path/photo.jpg")      # загрузка файла

Действия мыши и клавиатуры

from selenium.webdriver import ActionChains
ac = ActionChains(driver)
ac.move_to_element(menu).pause(0.3).click(item).perform()           # наведение и клик
ac.double_click(el).perform(); ac.context_click(el).perform()       # двойной и правый клик
ac.drag_and_drop(src, dst).perform(); ac.click_and_hold(el).move_by_offset(100, 0).release().perform()
ac.key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL).perform()
driver.execute_script("arguments[0].scrollIntoView({block:'center'})", el)

Окна, вкладки, фреймы, алерты

original = driver.current_window_handle
driver.switch_to.new_window("tab")                                    # новая вкладка (Selenium 4)
for h in driver.window_handles:
    if h != original: driver.switch_to.window(h)
driver.close(); driver.switch_to.window(original)

driver.switch_to.frame("frame-id")  # или индекс, или элемент
driver.switch_to.default_content(); driver.switch_to.parent_frame()

alert = driver.switch_to.alert; alert.text; alert.accept(); alert.dismiss(); alert.send_keys("текст")

Shadow DOM: host.shadow_root.find_element(By.CSS_SELECTOR, "button").

JavaScript и логи

driver.execute_script("return document.title")
driver.execute_script("arguments[0].click()", el)                      # когда обычный клик перекрыт
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
driver.execute_async_script("var cb = arguments[arguments.length - 1]; setTimeout(() => cb('ok'), 500)")
driver.get_log("browser")     # консоль браузера (Chrome)

Page Object

class LoginPage:
    URL = "/login"
    EMAIL = (By.ID, "email"); PASSWORD = (By.ID, "password"); SUBMIT = (By.CSS_SELECTOR, "button[type=submit]")
    ERROR = (By.CSS_SELECTOR, ".error")
    def __init__(self, driver): self.driver = driver; self.wait = WebDriverWait(driver, 10)
    def open(self): self.driver.get(BASE + self.URL); return self
    def login(self, email, pw):
        self.driver.find_element(*self.EMAIL).send_keys(email)
        self.driver.find_element(*self.PASSWORD).send_keys(pw)
        self.driver.find_element(*self.SUBMIT).click()
        return ProfilePage(self.driver)
    def error_text(self): return self.wait.until(EC.visibility_of_element_located(self.ERROR)).text

Тест читается как сценарий, селекторы живут в одном месте. Принцип: страница возвращает следующую страницу, ассерты остаются в тестах.

pytest-фикстуры

import pytest
@pytest.fixture
def driver():
    d = webdriver.Chrome(options=opts)
    d.implicitly_wait(0)
    yield d
    d.quit()

def test_login(driver):
    page = LoginPage(driver).open()
    profile = page.login("anna@x.ru", "Qwerty123!")
    assert profile.username() == "Анна"

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):             # скриншот при падении
    out = yield; rep = out.get_result()
    if rep.when == "call" and rep.failed and "driver" in item.funcargs:
        item.funcargs["driver"].save_screenshot(f"fail_{item.name}.png")

Параллельность: pytest-xdist (-n 4), отчёты Allure (pytest --alluredir=out), перезапуск нестабильных pytest-rerunfailures (как временная мера).

Selenium Grid и облака

docker run -d -p 4444:4444 --shm-size=2g selenium/standalone-chrome            # один узел
# Grid: hub + nodes; клиент подключается удалённо:
driver = webdriver.Remote(command_executor="http://localhost:4444/wd/hub", options=webdriver.ChromeOptions())

Альтернативы: Selenoid и Moon (Docker-кластер), BrowserStack, Sauce Labs, LambdaTest (облако). Учитывайте --shm-size и --no-sandbox в контейнерах.

Настройки браузера

o = webdriver.ChromeOptions()
o.add_argument("--headless=new"); o.add_argument("--disable-gpu"); o.add_argument("--no-sandbox"); o.add_argument("--disable-dev-shm-usage")
o.add_argument("--lang=ru-RU"); o.add_argument("--user-data-dir=/tmp/profile"); o.add_argument("--incognito")
o.add_experimental_option("prefs", {"download.default_directory": "/tmp/dl", "profile.default_content_setting_values.notifications": 2})
o.set_capability("goog:loggingPrefs", {"browser": "ALL"})
o.page_load_strategy = "eager"                                # normal | eager | none
f = webdriver.FirefoxOptions(); f.add_argument("-headless")

Эмуляция мобильных устройств: o.add_experimental_option("mobileEmulation", {"deviceName": "iPhone 12"}). Прокси и сертификаты через опции и Proxy.

Частые исключения

Исключение Причина и решение
NoSuchElementException нет элемента: неверный локатор, не дождались, он во фрейме или в shadow DOM
TimeoutException условие не наступило за время ожидания
StaleElementReferenceException элемент пересоздан в DOM: найдите заново, ждите стабильного состояния
ElementClickInterceptedException клик перекрыт (модальное окно, баннер cookie, прокрутка): ждите clickable, закройте оверлей
ElementNotInteractableException элемент скрыт или отключён
NoSuchFrameException, NoAlertPresentException не тот контекст
WebDriverException: session not created версия браузера и драйвера не совпадают (Selenium Manager обычно решает)

Практика

  • Один тест проверяет один сценарий и независим от других; тестовые данные создавайте через API или БД, а не через интерфейс.
  • Проверяйте результат, а не отсутствие ошибок; сообщения об ошибке информативны.
  • Дымовые и критичные пути на UI, остальное на API и юнит-уровне (пирамида тестирования).
  • Стабильные атрибуты data-testid согласуйте с разработкой.
  • Запуск в CI в headless-режиме, видео и скриншоты при падении, артефакты в отчёте.
  • Не автоматизируйте то, что нестабильно по своей сути (CAPTCHA, внешние сервисы): используйте моки.

Selenium или Playwright

Selenium: зрелая экосистема, много языков и корпоративных облаков, нативный Safari и старые браузеры, огромное сообщество. Playwright: быстрее, автоожидания и трассировка «из коробки», встроенный раннер и моки сети. Новые проекты на JS и TS чаще выбирают Playwright; существующие большие наборы на Java и C# живут на Selenium. См. шпаргалку «Playwright».