В первой части статьи мы рассказали, как легко и быстро построить инфраструктуру для запуска UI-тестов на Android с помощью Appium и Selenoid. Продолжаем историю и рассказываем, как внедрили в схему запуск UI-тестов на iOS. Максимальное количество параллельных потоков в пределах хоста ограничено его ресурсами. Поэтому нам понадобился инструмент для объединения нескольких хостов в один кластер. Для этого мы используем Go Grid Router (GGR) от ребят из Aerokube. Согласно описанию из документации, GGR — балансировщик нагрузки, используемый для создания масштабируемых и высокодоступных кластеров Selenium. В схеме проект с тестами обращается к GGR. Он опрашивает указанные в его конфигурации Selenoid и распределяет нагрузку между ними в зависимости от платформы, наличия свободных потоков и установленного удельного веса каждого Selenoid. Развернуть GGR и GGR UI несложно: Устанавливаем Docker. Создаём каталог для файлов конфигурации GGR mkdir -p /etc/grid-router/quota. Создаём файл users.htpasswd $ htpasswd -bc /etc/grid-router/users.htpasswd test test-password. Создаём файл с квотами, где в качестве хоста указываем адрес развёрнутого Selenoid: Запускаем контейнер с GGR: В проекте тестов меняем порт Appium на порт запущенного GGR: Запускаем контейнер с GGR UI: Запускаем контейнер с Selenoid UI, где вselenoid-uri передаём порт GGR UI: Теперь наш Selenoid UI должен отображать состояние всех Selenoid-кластеров, подключенных к GGR.  Для запуска UI-тестов на iOS мы используем собственную ферму Mac mini. Таким же образом ферму можно собрать из списанных рабочих макбуков или взять их в аренду. На каждый хост необходимо установить: Appium (для работы с iOS≥16 необходимо использовать Appium 2.x).  Xcode.  Мы не могли повторить структуру, используемую при запуске Android-тестов, так как не нашли способ запускать iOS-симуляторы в Docker-контейнерах. Рассматривали вариант запуска Docker-OSX, но возникли сомнения по легальности его использования для целей, не связанных с OS X Security Research. Мы решили пойти другим путём. Добавили Appium (порт 4723) в качестве хоста Selenoid для iOS-тестов в ранее созданный конфиг GGR: Схема iOS-части в таком случае выглядит следующим образом: Используемая в итерации структура работоспособна. Проблема в том, что в данном случае мы можем запускать тесты только в одном потоке на каждом Mac mini, что расточительно. Также кластер не будет отображаться в Selenoid UI. Selenoid позволяет работать не только с контейнерами. В связи с вышеуказанными проблемами мы решили использовать Selenoid и с iOS-тестами, но в виде исполняемого файла: Скачиваем Selenoid (amd64/arm64).  Создаём файл конфигурации browsers.json.  В конфигурации указываем Appium и параметры запуска: Предоставим разрешение на исполнение файла Selenoid. В нашем случае мы использовали chmod 755. Запускаем Selenoid через терминал. Мы использовали следующие параметры: selenoid -conf ~/browsers.json -disable-docker -capture-driver-logs -service-startup-timeout 4m -session-attempt-timeout 4m -timeout 6m -limit 2. Указанные таймауты необходимы, так как стандартных может быть недостаточно для скачивания приложения из облачного хранилища и запуска симулятора. Параметром -limit установили максимальное количество запускаемых симуляторов. На это значение в дальнейшем будет опираться GGR. При выставлении параметра опираемся на производительность хоста. Подробнее о параметрах запуска можно почитать в документации Selenoid. При необходимости на каждом Mac mini можно создать PLIST-файл для автозагрузки Selenoid на случай внезапного перезапуска системы. Схема работы кластера теперь выглядит так: При таком подходе мы частично получаем функциональность Selenoid UI и возможность запускать тесты в несколько потоков на одном хосте. Недостатком является то, что на каждом Mac mini надо вручную проделывать много рутинных манипуляций по созданию симулятора и привязыванию его к Appium через указание UUID и назначение портов. Это может стать проблемой, когда в дальнейшем появится необходимость перехода на новую версию iOS. У нас крупная ферма Mac mini, которая будет продолжать расти. Поэтому мы искали способ облегчить масштабирование, чтобы не создавать руками симуляторы и прибивать их к Appium. В прошлой схеме у Appium и симуляторов было бы длительное время жизни, что могло повлечь непредсказуемые последствия.  В процессе поиска решения мы обнаружили, что в файле конфигурации Selenoid в качестве хоста можно указать bash-скрипт: Так он выглядит у нас: В случае использования скрипта обратите внимание на объявленные capabilities и параметры запуска Appium. Здесь они указаны с учётом запуска Appium 2.x — для Appium 1.x не требуется указание вендора в capabilities и нет возможности указать --base-pat.  Скрипт решает проблемы параллельного запуска симуляторов: Когда мы напрямую подключаем к GGR несколько аппиумов с одного Mac mini, возникает проблема с эмуляторами — нельзя запустить один и тот же эмулятор с одинаковыми UDID. Надо каждый раз вручную дублировать и зашивать/хардкодить UDID. Например, в случае необходимости изменения версии iOS или модели симулятора. Плохая масштабируемость. Необходимо каждый раз запускать Appium руками и регулярно проверять его на отсутствие конфликтов с портами и симуляторами. Использование Selenoid позволяет упростить это до одного скрипта, который не создаёт конфликтов между несколькими парами Appium + Simulator в пределах одного хоста. Он запускает Appium и убивает его при получении соответствующего сигнала от Selenoid, а также динамически клонирует симуляторы при запуске и удаляет их после завершения сессии. Получили такую схему: Далее добавляем адреса Selenoid каждого Mac mini в файл конфигурации развёрнутого GGR, объединяя Android- и iOS-структуры: Собранная инфраструктура позволяет прогонять на 36 потоках около 500 UI-тестов в час суммарно на обеих платформах. Добавление нового хоста для Android-тестов полностью автоматизировано с помощью воркфлоу на GitHub Actions и занимает около двух минут. В ближайших планах автоматизировать развёртывание Selenoid-кластера и на Mac mini. В дальнейшем хочется попробовать запускать контейнеры Docker-OSX на Mac mini с Linux, чтобы унифицировать все процессы и облегчить развертывание на них, не нарушая при этом правила использования macOS. Возможно, у вас есть такой опыт — будем рады, если вы поделитесь им в комментариях.   QA Engineer