Python: Selenium
Теория: Параллельный запуск и конфигурация
Зачем нужен параллельный запуск
Когда тестов становится сотни, их проверка может занимать часы. Каждый тест открывает браузер, переходит на страницу, кликает кнопки — всё это занимает время. Если выполнять их последовательно, один за другим, прогон превращается в длинную очередь.
Параллельный запуск решает эту проблему. Pytest может делить тесты между несколькими процессами (или даже компьютерами), и каждый процесс запускает свою копию браузера. В итоге 100 тестов, которые раньше шли 50 минут, можно прогнать за 10–15 минут — в зависимости от количества ядер и ресурсов.
Как работает pytest-xdist
pytest-xdist — это плагин, который добавляет Pytest возможность запускать тесты в несколько потоков. Устанавливается он просто:
Запускается тоже просто — достаточно добавить параметр -n, где указывают число параллельных процессов:
Это значит: "запусти тесты в 4 процессах одновременно". Можно не указывать число вручную — флаг auto подберёт количество по числу ядер процессора:
Что происходит при параллельном запуске
Pytest запускает несколько копий себя и раздаёт тесты по процессам. Каждый процесс получает свою часть набора и работает независимо: открывает свой браузер, пишет свои логи, сохраняет свои скриншоты.
Чтобы тесты не мешали друг другу, важно, чтобы они не делили один и тот же файл, профиль браузера или логин. Например:
- У каждого процесса своя папка
screenshots/worker1,screenshots/worker2и т.д. - Браузер создаётся заново в каждом тесте или хотя бы в каждом процессе.
- Данные (например, пользователи) должны быть уникальными, чтобы не было коллизий.
Простой пример
Допустим, есть 10 тестов логина. Обычно они выполняются один за другим. Но если добавить pytest -n 5, Pytest разделит их на 5 групп и запустит 5 браузеров параллельно. Каждый процесс выполнит по два теста, и общее время сократится в 4–5 раз.
Как избежать конфликтов
Если все процессы используют один и тот же профиль браузера, начнутся ошибки: одни тесты будут закрывать вкладки других, куки смешаются, появятся непредсказуемые падения.
Чтобы этого не было, в фикстуре создаётся уникальный профиль для каждого процесса. Pytest предоставляет специальную переменную worker_id (например, gw0, gw1), по которой можно различать процессы.
Теперь каждый процесс работает со своей чистой копией браузера.
Пример запуска
Флаг --dist=loadscope говорит: “все тесты одного модуля или класса выполняются в одном процессе”, чтобы не ломались фикстуры с областью class.
Когда параллельный запуск не нужен
Параллельный режим даёт прирост скорости, но не подходит, если:
- тесты используют один и тот же аккаунт или общие данные,
- внешний сервис не выдерживает несколько одновременных логинов,
- стенд падает при множественных запросах.
В таких случаях оставляют обычный последовательный прогон или делят тесты по категориям: быстрые — параллельно, медленные — отдельно.
Передача параметров через командную строку (--browser=chrome)
Когда автотесты должны запускаться в разных браузерах, неудобно менять код вручную перед каждым прогоном. Гораздо проще управлять этим через параметры командной строки. Pytest умеет принимать собственные аргументы — например, --browser=chrome или --env=stage, — и использовать их внутри фикстур.
Как добавить пользовательский параметр
Pytest позволяет объявить новые опции через специальную функцию pytest_addoption. Обычно её добавляют в conftest.py.
Как использовать
Теперь тесты можно запускать в любом браузере без изменения кода:
Если не указать параметр, по умолчанию запустится Chrome — потому что в pytest_addoption задано default="chrome".
Что делает фикстура
Фикстура driver получает имя браузера из параметра --browser. Pytest сохраняет этот параметр в request.config, и его можно считать в любом месте кода. На основе значения создаётся нужный экземпляр WebDriver:
webdriver.Chrome(options=opts)для Chrome;webdriver.Firefox(options=opts)для Firefox.
Таким образом один и тот же тест работает с любым браузером.
Пример теста
При запуске Pytest сам подставит драйвер, соответствующий параметру --browser.
Пример запуска с отчётом
Можно также комбинировать опции:
Pytest выполнит все тесты в Firefox, выведет подробный лог и сохранит отчёт для Allure.
Дополнительные параметры
Аналогичным образом можно добавить другие настройки:
--env=stage— выбор стенда;--headless— включение headless-режима;--lang=ru-RU— язык интерфейса;--window-size=1366,768— размер окна.
Пример с несколькими опциями:
Теперь можно комбинировать параметры:
Управление конфигурацией через .env и pytest.ini
Тестовый проект живёт на разных стендах и в разных окружениях. Одни настройки отвечают за поведение Pytest, другие — за секреты и параметры браузера. Удобно развести эти миры: всё, что связано с Pytest и структурой запуска, хранится в pytest.ini; всё, что касается адресов, логинов, ключей и флагов браузера, уходит в .env.
Что кладут в pytest.ini
Файл pytest.ini задаёт правила работы тестового раннера. Здесь фиксируются пути к тестам, формат трейсбека, уровень логов, наборы меток и дефолтные флаги запуска. Это не секретные данные и не зависят от окружения пользователя. Пример минимальной конфигурации показывает типичные поля.
Если проект гоняется параллельно, сюда часто добавляют режим распределения и количество воркеров по умолчанию. На локальной машине удобно оставить одиночный запуск, а в CI прокинуть собственные флаги через переменные, чтобы не менять файл.
Что кладут в .env
Файл .env хранит параметры окружения, которые меняются от стенда к стенду. Это базовые URL, логины технических пользователей, флаги браузера, каталоги артефактов. Его не коммитят; в репозитории лежит только .env.example с пустыми значениями. Пример рабочего файла показывает типичный набор.
Загрузка .env в фикстурах
Чтение параметров удобно делать в conftest.py. Библиотека python-dotenv поднимает переменные окружения до старта тестов. Поверх .env остаётся право последнего слова за командной строкой, поэтому параметры из pytest --browser=… перекрывают значения из файла.
Здесь порядок источников очевиден. Значения по умолчанию зашиты в коде. .env перекрывает дефолты. Параметры из командной строки перекрывают .env. Такой каскад даёт гибкость: разработчик запускает локально «как в .env», а пайплайн передаёт свои флаги, не трогая файлы.
Доступ к секретам без утечек
Учётки и токены не попадают в код. Чтение происходит через os.getenv, а значения прокидываются в шаги или Page Object. Для демонстрации достаточно отдать логин и пароль авторизации из отдельной фикстуры.
Страница логина ничего не знает о .env; она получает готовые значения и вводит их через Selenium.
Несколько .env под разные стенды
Команда выбирает активный файл на старте. Самый простой приём — переменная ENV_FILE. Перед запуском тестов указывается путь, а загрузка .env читает нужный файл.
На локальной машине лежит .env. Для стейджа в CI прокидывается ENV_FILE=.env.stage. Для контейнера — .env.ci. Код фикстур не меняется.
Комбинация с pytest.ini без конфликтов
pytest.ini остаётся про Pytest: пути, метки, формат логов, политика трейсбека. .env остаётся про окружение браузера и стенды. При необходимости некоторые параметры можно продублировать в addopts, но секреты и URL туда не идут. В результате конфигурация прозрачна: тесты читают адрес стенда из .env, раннер берёт формат вывода из pytest.ini, а любой человек может переопределить поведение одной командой в терминале.
Такой подход снимает две боли сразу. Настройки Pytest не смешиваются с секретами и адресами. А параметры окружения можно переключать без правок кода: достаточно изменить .env
Что значит параллельность в тестах
Когда Pytest запускает тесты с флагом -n, он просто создаёт несколько процессов (воркеров). Каждый процесс работает независимо: открывает свой браузер, выполняет тесты и пишет логи. То есть параллельность = количество браузеров, запущенных одновременно.
Если указать pytest -n 10, то будет 10 браузеров. Каждый ест память, процессор и немного видеоресурсов. Если ресурсов не хватает — система начнёт тормозить, браузеры зависнут, тесты станут нестабильными.
Почему нельзя запустить тысячу браузеров
Потому что физическая машина не справится. Один браузер в headless-режиме может занимать 300–600 МБ памяти. 1000 браузеров — это сотни гигабайт RAM и огромная нагрузка на CPU. Даже мощный сервер не выдержит такой объём.
Обычно границы такие:
- ноутбук / разработческая машина: 2–4 параллельных браузера;
- офисный сервер (8–16 ядер, 32 ГБ RAM): 8–16 воркеров;
- CI-сервер или облако: 20–50 браузеров, если ресурсы распределены между узлами.
Всё, что выше — делается через распределённую инфраструктуру вроде Selenium Grid, Selenoid или BrowserStack, где браузеры запускаются не на одном компьютере, а на разных машинах или контейнерах.
Что такое «стенд»
Стенд — это тестовое окружение, копия реального сервера или сайта, на котором гоняются тесты. Например, у компании может быть:
- prod — реальный сайт для пользователей;
- stage — тестовый сервер для QA;
- dev — внутренний сервер для разработчиков.
На стенде хранятся тестовые данные, отдельная база, свои логины и конфигурации. Тесты не должны работать на продакшене, чтобы не трогать настоящих пользователей.
Как обычно распределяют нагрузку
Если тестов много, их делят по стендам или узлам:
- каждый узел — это отдельная машина с браузерами;
- общий координатор (
pytest-xdist, Jenkins, Grid, Selenoid) раздаёт тесты на эти узлы; - результаты собираются обратно в один отчёт.
Например:
Если ресурсов мало — параллельность наоборот снижает стабильность. Тогда лучше запустить 2–3 процесса, но получить предсказуемый результат.
Параллельность — это компромисс:
- чем больше ядер и памяти, тем больше тестов можно гонять одновременно;
- но каждый браузер должен быть изолирован (отдельный профиль, куки, папки артефактов);
- и тесты должны идти на тестовом стенде, а не на живом проде.
Главное правило: параллельно можно столько, сколько позволяет железо и инфраструктура, не ломая стабильность тестов.


