Автоматизация · Инфраструктура
Почему UI-автотесты падают из-за окружения
Тест проходит локально и падает в CI. Разбираем, где искать причину, что даёт изоляция браузеров и почему новые контейнеры не спасут тесты с общими данными.
Тест проходит локально, но падает в CI
Начни с окружения. На ноутбуке может стоять одна версия Chrome, в CI другая. Локально тест открывает чистый профиль, а на общей машине получает куки от предыдущего запуска. В обоих случаях падение выглядит как ошибка сценария, хотя код теста не менялся.
Сравни версии браузера и драйвера, разрешение экрана, часовой пояс, локаль и переменные окружения. Потом посмотри, как создаётся сессия. Один браузер на весь набор тестов удобен до первого случая, когда один сценарий меняет настройки пользователя, а следующий наследует это состояние.
Разница бывает менее заметной. На одной машине есть нужный шрифт, на другой его нет. В одной среде браузер уже обновился, в другой всё ещё работает старая версия. Селектор попадает в другой элемент, скриншот отличается, а команда тратит время на поиск «плавающего» бага.
Есть и другая причина: данные. Два потока могут одновременно редактировать одного пользователя или очищать одну корзину. Новый контейнер для браузера их не разделит: оба теста всё равно обращаются к одной базе приложения.
Что даёт отдельная браузерная сессия
Изоляция убирает часть случайностей. Тест получает свой браузер или контекст, свои куки и локальное хранилище. После прогона сессия закрывается. Следующий тест начинает с известного состояния, а не с того, что оставил соседний.
Контейнеры решают ещё одну задачу: фиксируют набор браузера, драйвера и системных зависимостей. Это полезно, когда тесты пишут на одних машинах, а запускают на других. В Selenium удалённый браузер можно подключить через RemoteWebDriver; адрес Grid задаётся в конфигурации тестов. Сам сценарий при этом не должен знать, на какой машине открылся браузер.
Версию браузера тоже надо выбирать явно. Иначе образ с тегом «последний» обновится между прогонами, и вчерашний результат уже нельзя будет воспроизвести. Когда тест упал, сохрани фактическую версию браузера рядом с логом и скриншотом.
Но одинаковый образ не означает одинаковый результат. Контейнер не ускорит медленный API, не починит нестабильный селектор и не отменит гонку за общей записью в базе. Поэтому после перехода на новую инфраструктуру надо смотреть на причины оставшихся падений, а не объявлять любой красный тест «флаком».
Параллельность упирается в приложение и данные
Увеличить число браузеров легко. Увеличить пропускную способность тестового стенда сложнее. Когда одновременно стартуют десятки сценариев, приложение может начать отвечать медленнее. Тогда фиксированные ожидания чаще истекают, и общий прогон становится менее надёжным.
Раздели тестовых пользователей и создаваемые сущности между потоками. Для каждого сценария используй свои данные; после прогона удаляй их там, где это возможно и безопасно. Если сервис не выдерживает нагрузку, ограничь число параллельных запусков. Оптимальное значение ищут по времени полного прогона и частоте ложных падений, а не по числу доступных ядер.
Очередь сессий не добавляет мощности. Она полезна, когда свободных браузеров временно нет, но длинная очередь увеличивает время обратной связи. Отдельно считай время ожидания браузера и время самого теста: эти цифры требуют разных решений.
Для падения нужны артефакты, а не новый запуск вслепую
У удалённого браузера сложнее увидеть, что произошло. Сохраняй скриншот на шаге ошибки, консоль браузера, сетевые ошибки и логи теста. Видео помогает, когда элемент исчезает слишком быстро или падение связано с анимацией. Запись каждого успешного теста только расходует место; реши заранее, какие артефакты хранить и как долго.
Проверь, где находился браузер и куда он обращался. Адрес localhost внутри контейнера указывает на сам контейнер, а не на приложение на машине разработчика. Из-за такой ошибки тест часто выглядит как «приложение недоступно», хотя сервис запущен.
Если падение исчезает после повторного запуска, его причина всё ещё неизвестна. Сопоставь время ошибки с логами приложения и состоянием стенда. Только после этого решай, исправлять сценарий, данные или инфраструктуру.
Что использовать сейчас
Историческая статья про Selenoid хорошо показывает, зачем выносить браузеры из машин автоматизаторов. Но сам репозиторий Selenoid архивирован с декабря 2024 года и помечен как неподдерживаемый. Строить на нём новую инфраструктуру без отдельной причины не советую.
Для тестов на Selenium начни с Selenium Grid и официальных Docker-образов. Они дают удалённые WebDriver-сессии и запуск в нескольких браузерах. Сначала проверь небольшую конфигурацию, а уже потом усложняй её под реальную нагрузку.
Для проекта на Playwright сначала используй встроенные возможности раннера. Он запускает тесты в отдельных worker-процессах и даёт каждому тесту изолированный BrowserContext. Контейнер может понадобиться для одинаковой среды CI, но отдельный контейнер на каждый тест не является обязательным условием изоляции. Параллельность и общий backend всё равно придётся настроить осознанно.
Как менять запуск без гадания
- Собери последние падения и раздели их по причинам: браузер, данные, ожидания, приложение.
- Зафиксируй версии браузера и окружение. Проверь, что локальный и CI-прогоны используют одинаковые настройки.
- Сделай независимым один небольшой набор тестов. Выдели каждому запуску собственную сессию и данные.
- Добавляй параллельные потоки постепенно. После каждого шага сравни время прогона и долю нестабильных падений.
Начальная цель простая: один и тот же тест должен одинаково проходить локально и в CI. Когда это работает, ускорять весь набор уже имеет смысл.
Идею для этой темы дал разбор Selenoid на Хабре. Инструменты и ограничения выше проверены по текущей документации Selenium, Playwright и репозиторию Selenoid.