Selenium WebDriver: установка и устройство

Собираем рабочее окружение и заодно понимаем, что происходит между строчкой driver.get(...) и реально открывшимся браузером — без этой картинки половина ошибок Selenium выглядит мистикой.

Selenium WebDriver — это библиотека и стандартизированный протокол, позволяющие управлять настоящим браузером извне: не эмуляция, а тот же Chrome или Firefox, который стоит у пользователя, только команды ему шлёт ваш код.

Слово «настоящий» здесь принципиально. Selenium не рисует свою страницу и не парсит HTML, как requests с BeautifulSoup. Он открывает браузер, и браузер честно выполняет JavaScript, применяет CSS, шлёт запросы, рендерит DOM. Поэтому Selenium видит ровно то, что видит пользователь, — и поэтому он медленный. Обе эти черты растут из одного корня.

Как WebDriver управляет браузером

Между вашим скриптом и браузером стоит посредник — драйвер: отдельная программа (chromedriver, geckodriver, msedgedriver), которую пишут авторы самого браузера. Цепочка выглядит так:

ваш скрипт на Python
      │  вызов метода: driver.get("https://example.com")
      ▼
библиотека selenium
      │  HTTP-запрос с JSON-телом на localhost
      ▼
драйвер (chromedriver) — маленький HTTP-сервер
      │  внутренний протокол браузера (у Chrome — CDP)
      ▼
браузер Chrome — открывает страницу

Самое неожиданное для новичков: Selenium общается с драйвером по обычному HTTP. Драйвер — это локальный веб-сервер, который слушает порт (например, 9515) и принимает REST-подобные команды. Набор этих команд стандартизирован W3C и называется WebDriver Protocol. Именно благодаря стандарту один и тот же код работает и с Chrome, и с Firefox: меняется драйвер, но не команды.

Вот как выглядит то, что библиотека шлёт под капотом, когда вы пишете driver.get("https://example.com"):

POST http://localhost:9515/session/8f3c.../url
Content-Type: application/json

{"url": "https://example.com"}

Ответ:
{"value": null}          # null = команда выполнена без ошибок

Основные команды протокола — их полезно узнавать в трейсах и логах:

ЗапросМетод PythonЧто делает
POST /sessionwebdriver.Chrome()запускает браузер, создаёт сессию, возвращает её id
POST /session/{id}/urldriver.get(url)переходит по адресу
GET /session/{id}/titledriver.titleвозвращает заголовок вкладки
POST /session/{id}/elementdriver.find_element(...)ищет элемент, возвращает его ссылку
POST /session/{id}/element/{eid}/clickelement.click()кликает по элементу
DELETE /session/{id}driver.quit()закрывает сессию и браузер

Из этой картинки сразу следуют три практических вывода. Первый: каждая строчка вашего теста — это сетевой round-trip, поэтому UI-тесты медленные, и поэтому не надо дёргать find_element в цикле по сто раз. Второй: сессия — это состояние на стороне драйвера; если процесс Python умрёт, не закрыв сессию, браузер останется висеть в памяти. Третий: версия драйвера обязана понимать версию браузера — драйвер разговаривает с Chrome по внутреннему протоколу, который меняется от версии к версии.

Установка

Нужны три вещи: Python 3.8+, сам браузер (Chrome или Firefox — обычный, тот же, что для сёрфинга) и библиотека selenium.

# 1. Отдельное виртуальное окружение под проект — обязательно, а не «когда-нибудь потом»
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

# 2. Библиотека. Пакет называется просто selenium
pip install selenium

# 3. Проверяем, что встало
pip show selenium | head -2

Результат:

Name: selenium
Version: 4.27.1

Обратите внимание на две ловушки в имени пакета. Пакет для Python называется selenium — не selenium-webdriver (это npm-пакет для JavaScript) и не seleniumbase (это отдельный сторонний фреймворк поверх Selenium). И версия должна быть 4.x: между Selenium 3 и 4 сменился синтаксис поиска элементов, и половина статей в интернете написана про третью версию, отчего у новичков всё «не работает».

Selenium Manager: почему драйвер больше не надо качать руками

Раньше установка выглядела так: узнать версию Chrome, пойти на сайт, скачать chromedriver точно такой же версии, положить его в PATH, повторять после каждого автообновления Chrome. Именно на этом шаге отваливалась половина начинающих.

Начиная с Selenium 4.6 в библиотеку встроен Selenium Manager — он делает всё это за вас. При первом создании драйвера Selenium Manager:

  1. определяет, какой браузер и какой версии установлен в системе;
  2. подбирает совместимую версию драйвера;
  3. скачивает её (а при необходимости — и сам браузер) и кладёт в локальный кэш;
  4. подставляет путь к драйверу в вашу сессию.

Кэш лежит в ~/.cache/selenium (Linux/macOS) или %USERPROFILE%\.cache\selenium (Windows). Скачивание происходит один раз: дальше драйвер берётся из кэша, пока Chrome не обновится.

Практический вывод: вам больше не нужны ни ручное скачивание, ни пакет webdriver-manager (сторонняя библиотека, которая решала эту проблему до 4.6 — в новых проектах она лишняя). Строчка создания драйвера теперь выглядит просто:

from selenium import webdriver

driver = webdriver.Chrome()   # Selenium Manager сам найдёт и подложит chromedriver
driver.quit()

Проверка окружения

Прежде чем писать первый тест, полезно убедиться, что вся цепочка «Python → selenium → драйвер → браузер» жива. Вот скрипт-диагностика: он ничего не тестирует, он проверяет вас.

import platform
import selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

print("Python :", platform.python_version())
print("Selenium:", selenium.__version__)

options = Options()
options.add_argument("--headless=new")   # без окна: быстрее и работает на сервере

driver = webdriver.Chrome(options=options)
try:
    caps = driver.capabilities
    print("Браузер:", caps["browserName"], caps["browserVersion"])
    print("Драйвер:", caps["chrome"]["chromedriverVersion"].split(" ")[0])
    driver.get("https://example.com")
    print("Заголовок:", driver.title)
finally:
    driver.quit()

print("Окружение готово.")

Результат (числа версий будут вашими):

Python : 3.12.3
Selenium: 4.27.1
Браузер: chrome 131.0.6778.86
Драйвер: 131.0.6778.85
Заголовок: Example Domain
Окружение готово.

Разберём небанальные места. driver.capabilities — это словарь, который драйвер вернул в ответ на POST /session: там лежат реальные версии браузера и драйвера, и это первое, что стоит распечатать, когда «у меня не работает, а у коллеги работает». Флаг --headless=new запускает Chrome без графического окна — это новая реализация headless-режима (старая, --headless, вела себя иначе и местами ломала тесты). А try/finally вокруг работы с драйвером — привычка, которую надо выработать с первого дня: о ней подробно в следующем уроке.

Как это работает

Что физически происходит в момент webdriver.Chrome():

  1. Selenium Manager сверяется с кэшем, при необходимости качает chromedriver;
  2. библиотека запускает процесс chromedriver и передаёт ему свободный порт;
  3. chromedriver поднимается как локальный HTTP-сервер и запускает процесс Chrome — со свежим временным профилем, без ваших закладок, расширений и куки (поэтому автотест всегда стартует «с чистого листа», и это правильно);
  4. библиотека шлёт POST /session, драйвер отвечает sessionId;
  5. дальше каждая команда вашего теста превращается в HTTP-запрос с этим sessionId.

После quit() происходит обратное: DELETE /session/{id}, драйвер закрывает Chrome, затем завершается сам. Если этого не случилось — в системе остаются два висящих процесса. На локальной машине это «просто памяти жалко», в CI-агенте после сотни таких прогонов кончается память и падают все тесты подряд.

Частые ошибки

  • SessionNotCreatedException: This version of ChromeDriver only supports Chrome version 128. Классика: Chrome обновился, а драйвер остался старый. На Selenium 4.6+ обычно означает, что в PATH лежит старый chromedriver, скачанный руками, и он перебивает Selenium Manager. Лечение: удалить его из PATH и дать менеджеру сделать свою работу.
  • WebDriverException: unable to obtain driver / cannot find Chrome binary. Chrome вообще не установлен (частый случай на голом Linux-сервере) либо стоит в нестандартной папке. Ставим браузер или указываем путь: options.binary_location = "/usr/bin/google-chrome".
  • Корпоративный прокси и офлайн-машина. Selenium Manager лезет в интернет за драйвером. Если сеть закрыта — он падает на первом же запуске. Решения: положить драйвер заранее и передать путь через Service(executable_path=...), либо прогреть кэш на машине с интернетом.
  • Установили не тот пакет. pip install selenium-webdriver или seleniumbase вместо selenium, дальше — ImportError и час недоумения.
  • Копируете код из статьи 2019 года. Если видите driver.find_element_by_id("x") или webdriver.Chrome("./chromedriver") — это Selenium 3, в четвёртой версии этих вызовов больше нет. Ищите синтаксис с By и Service.
  • Ставите selenium глобально. Через полгода на машине четыре проекта с разными версиями и конфликт зависимостей. venv — не формальность.

Итоги

  • Selenium управляет настоящим браузером через посредника-драйвер; общение идёт по HTTP+JSON по стандарту W3C WebDriver Protocol.
  • Каждая команда теста — сетевой round-trip к локальному серверу драйвера. Отсюда и медленность UI-тестов, и требование закрывать сессию.
  • Установка сводится к python -m venv + pip install selenium (версия 4.x). Браузер — обычный, установленный в системе.
  • Selenium Manager (с 4.6) сам качает и подкладывает драйвер. Руками chromedriver скачивать не нужно, webdriver-manager — не нужен.
  • Первое, что печатают при проблемах, — selenium.__version__ и driver.capabilities: там видно реальные версии браузера и драйвера.
Проверьте себя
1. Как библиотека selenium передаёт команды драйверу браузера?
AНапрямую вызывает функции C++ внутри процесса браузера
BHTTP-запросами с JSON по стандарту W3C WebDriver к локальному серверу драйвера
CЧерез файл-очередь на диске, который читает браузер
DВнедряет JavaScript в страницу и общается через localStorage
2. Что делает Selenium Manager, встроенный в Selenium начиная с версии 4.6?
AУскоряет тесты, кэшируя загруженные страницы
BУправляет параллельным запуском тестов на нескольких машинах
CСам определяет версию браузера, скачивает совместимый драйвер и подставляет путь к нему
DАвтоматически генерирует локаторы для элементов страницы