🌐 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».