
Привет! Я Павел Лобач из команды инфраструктуры тестирования Т-Банка. Расскажу, как у нас организована инфраструктура для запуска E2E браузерных тестов, как она развивалась и как в итоге вылилась в открытый проект Selebrow.
Будет много технических подробностей и ни слова про ИИ!
E2E-тесты и как их запускают
E2E-тесты (end-to-end, или сквозные, тесты) занимают вершину пирамиды тестирования. E2E — наиболее комплексные и ресурсоемкие тесты, которые проверяют всю систему от начала до конца, включая взаимодействие всех компонентов и пользовательский инте��фейс.
Суть E2E-тестов в том, чтобы взаимодействовать с тестируемым приложением так, как это делал бы пользователь, и оценивать результат. А пользователь взаимодействует с веб-сервисами, используя браузер, поэтому идея запустить из кода тестов браузер и как-то его заставить автоматически взаимодействовать со страницей кажется здравой.
Как делали деды. Исторически для автоматизации браузерных тестов использовался фреймворк Selenium, а для мира Node.js есть Webdriver.IO.
Selenium общается с браузером, используя протокол Webdriver, который почти стал стандартом W3C, но так и остался вечным черновиком. Вот только браузеры не понимают Webdriver, и поэтому нужна прокладка, называемая драйвером: для Chrome — chromedriver, для Firefox — geckodriver, для Safari — safaridriver.
Тестовый фреймворк запускает процесс-драйвер и взаимодействует с ним по протоколу Webdriver. Драйвер запускает браузер и управляет им, используя свой внутренний протокол.

Минусы использования такого подхода:
Необходим промежуточный процесс, который выполняет трансляцию и так медленного stateful Webdriver-протокола.
Проблемы масштабирования: браузеры запускаются рядом с тестами и мешают друг другу, особенно если хочется запускать тесты в несколько потоков.
Нужно как-то доставлять драйвер и браузер нужной версии перед запуском тестов. Это неудобно для запуска тестов на CI, так как добавляет задержки на скачивание бинарников и лишнюю зависимость от внешних интернет-ресурсов.
Есть проблема повторяемости тестов локально и на CI: разные OS, шрифты, настройки сглаживания. Это важно для скриншотного тестирования.
Docker to the rescue. Совместив перечень недостатков со списком хайповых в 2017 году технологий, появилась идея запускать браузеры в Docker-контейнерах из готовых имиджей. Это поможет решить проблемы с повторяемостью тестов и доставкой браузера и драйвера новой версии: просто используем одни и те же имиджи для запуска тестов локально и в CI!
Нужно написать какой-нибудь оркестратор. Так в свое время появился проект selenoid, который принимает подключение по протоколу Webdriver и запускает Docker-контейнеры, внутри которых уже все есть: и драйвер, и браузер нужных версий. Вот только мы все еще ограничены одним хостом, на котором запущен selenoid и Docker-демон.
Каждому браузеру нужно выделить примерно 1 ядро CPU и 2 ГБ RAM, что ограничивает нас запуском одновременно 64 браузеров на 64-ядерной машине (а такую еще добыть нужно). Увы, этого может быть недостаточно. Что, если у нас есть пара десятков дешевых 8-ядерных виртуалок — как распределять контейнеры с браузерами между ними? Для решения этой задачи авторы проекта selenoid создали балансер ggr, который распределяет запросы между selenoid-хостами — получается браузерная ферма.

Начало браузерной фермы
Когда в 2020 году я пришел в Т-Банк, у нас была браузерная ферма, основанная на доработанных версиях selenoid и ggr. Мы добавили возможность задавать квоты на ферме для команд (чтобы кто-то один не занял все ресурсы), а это потребовало прикрутить аутентификацию.
После решения некоторых технических проблем и добавления метрик проект заработал стабильно и не требовал внимания.
У сетапа ggr+selenoid было несколько недостатков:
Необходим еще один промежуточный процесс, который выполняет трансляцию stateful Webdriver-протокола. А в случае ggr+selenoid получается очень длинная цепочка взаимодействия.
Есть часы низкой нагрузки, когда мощности фермы простаивают.
Нет возможности запускать тесты на Safari, так как Safari для linux никогда не существовал, как и сборок Webkit-браузеров, пригодных для использования с selenoid.
Можно было бы этим пренебречь, если бы мир вокруг был неизменным, но реальность заставила нас все переделать.
Революция первая: новая инфраструктура сборки. До 2021 года наш CI был довольно типичным: GitLab CI + шареные Docker-ранеры. Сверившись со списком хайповых технологий 2021 года, мы решили построить новый CI с Kubernetes и клаудами.

Старая инфраструктура CI не позволяла нам масштабироваться и эффективно утилизировать выделенное железо: есть часы пониженной нагрузки, когда простаивали тысячи ядер CPU.
А при чем тут браузерная ферма? Да вроде была ни при чем, но через некоторое время пользователи стали робко спрашивать запросили фичу, чтобы ходить браузерами из фермы на сервисы, запущенные внутри CI.
В новой инфраструктуре сборки мы дали пользователям инструмент для запуска временных изолированных интеграционных окружений прямо внутри CI. Проект называется Kuber-API. Так как CI-кластеры не содержат ingress-контроллеров, попасть на поды, запущенные внутри CI, из старой браузерной фермы не представлялось возможным.
Рассмотрев разные возможности — туннели и прочие решения, усложняющие схему взаимодействия, — мы решили... избавиться от фермы совсем. Новая идея состояла в том, чтобы каждой CI-джобе дать свою мини-ферму. Эта мини-ферма будет работать в том же кластере Kubernetes, запускать браузеры внутри k8s-подов вместо Docker-контейнеров и умирать после завершения сборки.
Для реализации нового плана за пару недель и почти без перекуров мы написали проект, который назвали Selebrow. Получился GitLab-сервис, который можно подключить в своем CI.
test-job:
services:
- selebrow/selebrow:latestТесты подключаются к localhost:4444, как будто у нас запущен обычный selenoid.
Новым подходом мы решили ряд вопросов:
Браузеры теперь запускаются в одном Kubernetes-кластере с CI-сборками и интеграционными окружениями, поэтому возможны любые варианты взаимодействия: браузер → CI job, браузер → сервис из временного окружения.
Проблема масштабирования полностью решилась: каждый может запустить себе столько браузеров, сколько хочет — в пределах выделенной квоты. CI-кластер автоматически растет под нагрузкой и, наоборот, сжимается при падении нагрузки. Вот для чего нужны эти ваши клауды!
Аутентификация пользователей больше не нужна, так как внутри CI мы и так знаем, кто это.

Из недостатков нового решения стоит отметить более медленный старт пода с браузером, но проблема нивелировалась добавлением возможности переиспользования уж�� запущенных подов браузеров в рамках тестовой сессии.
Революция вторая: Playwright. Вроде на этом можно было бы выдохнуть и заняться другими делами. Но тут на сцену врывается новый тестовый фреймворк Playwright, и он получает просто взрывной адопшен.
Отставив в сторону удобство написания тестов (а там все очень хорошо), Playwright предлагает более технологичный метод взаимодействия с браузером: вместо промежуточного драйвера фреймворк использует родной протокол общения. Например, для Chrome это CDP. Схема взаимодействия тестов с браузером максимально упрощается, и это объективно сильно ускоряет тесты.

А еще Playwright из коробки предоставляет сборку мини-браузера на основе Webkit, позволяя запускать тесты «почти на Safari» на платформах, отличных от macOS, и возможность удаленного запуска браузеров тоже в наличии.
Звезды сложились так, что Playwright признали перспективным и предпочтительным фреймворком для написания E2E-тестов внутри нашей компании и появилась необходимость реализовать его поддержку в браузерной ферме. Внезапно оказалось, что изначальная реализация Selebrow получилась настолько легко расширяемой, что задача была сделана практически «еще вчера». Для подключения браузерной фермы в конфигурации Playwright-проекта нужно всего лишь указать параметр connectOptions.
export default defineConfig({
projects: [
{
name: 'chrome',
use: {
...devices['Desktop Chrome'],
connectOptions: {
wsEndpoint: 'ws://localhost:4444/pw/chrome/1.49.0'
}
}
}
...Прощай selenoid
На этом наша «история успеха» не заканчивается: до сих пор мы не принимали во внимание технологии, которые пользователи применяют для запуска тестов локально. Общей фермы-то больше нет, а запустить тесты «как в CI» может быть необходимо. Например, для скриншотных тестов.
До всей истории с Playwright в качестве локального решения мы предлагали использовать старый добрый selenoid с нашими имиджами. Но selenoid не умеет в Playwright, а просто запускать Playwirght тесты локально — почти гарантированно получить расхождения в скриншотах из-за зоопарка пользовательских устройств (Windows, macOS). Кроме того, проект selenoid был заархивирован и больше не развивается с конца 2024 года.

Мы решили написать замену selenoid и в уже существующий сервис добавили поддержку Docker-бэкенда. Конфигурацию браузеров в CI опубликовали на внутреннем общем ресурсе, и теперь она автоматически применяется при запуске бинарника браузерной фермы локально.
Замена почившего selenoid может быть потенциально интересна более широкой аудитории, поэтому мы открыли большую часть кода нашего сервиса. Получившийся проект назвали Selebrow.
Сейчас наш внутренний проект на 99% собирается из кода Selebrow и мы планируем публиковать все доработки в open source.
Итоги
Параллельно с развитием проекта росло количество клиентов-пользователей, и общее количество ежедневно запускаемых браузеров достигло тех самых 100k+, обещанных в заголовке.

Если вы хотите запустить нечто подобное у себя или уже используете Selenoid/Selenum Grid, предлагаем попробовать Selebrow. Пишите issue, если есть какие-то вопросы по запуску или настройке, — всем постараемся ответить. Если чего-то не хватает, тоже пишите: у нас уже сложился некоторый бэклог по Selebrow — возможно, там это уже есть.
Полезные ссылки
Стандарт протокола Webdriver (draft)
