Те, кто уже смог познать все прелести масштабирования, знают, что нередко удаленное окружение начинает сбоить: зависшие сессии грида, незакрытые браузеры, блокирующие ОС диалоги, висящие драйвера в таск менеджере и т.п. Все это приводит к рэндомным падениям тестов, утечкам памяти, увеличению времени прогона и т.д. Возникает логичный вопрос - как с этим бороться?
На просторах сети можно найти интересные попытки решения, и даже целый проект, посвященный гриду и его саппорту. Идея, заложенная в основы Selenium Grid Extras, показалась мне довольно привлекательной. Но особенности реализации несколько оттолкнули. Посему, я взялся за изобретение своего собственного велосипеда, но более приближенного к современным тенденциям разработки.
Основная концепция, заложенная в Environment Watcher, крутится вокруг понятий CI и REST. Лично для меня, более или менее универсальным вариантом “лечения” зафейлившихся сервисов стало их принудительное умерщвление с последующей возможностью рестарта. Но я не хотел привязываться конкретно к гриду. Что если помимо хаба / нодов у вас еще висят, к примеру, сервера SikuliX, BMP, HarStorage? Допиливать endpoints в каждый из них - не вариант. Да и что делать, если сервис попросту завис и не отвечает на команды? Остается только шагать на виртуалку и руцями все закрывать / переоткрывать. Но что, если таких виртуалок много? Что, если к ним нет прямого доступа? Слишком много нюансов, зависящих от многих факторов. Посему, универсального варианта, пожалуй, не придумаешь. Но в рамках текущего проекта я попытался свести к минимуму возможные телодвижения на подготовку и саппорт.
Итак, начнем с идеи. В маленький http сервер встраиваются API по уничтожению любых задач в task manager’е. К таким задачам я как минимум отношу висящие браузеры и их драйвера. А сделать это можно при помощи команд tasklist
/ taskkill
.
for /f \"tokens=1\" %i in ('tasklist ^| findstr \"?\"') do (taskkill /F /PID %i)
Этого конечно же не хватит для java tasks, ибо все они, как на подбор, имеют одно и то же имя - java.exe
. Но мы ведь не хотим прибить лишние процессы, так ведь? Для этого в наш http сервер встраиваются API по точечной борьбе с java tasks при помощи встроенной в JDK утилиты - JPS.
for /f \"tokens=1\" %i in ('jps -l ^| findstr \"?\"') do (taskkill /F /PID %i)
Естественно, приведенные выше строки скармливаются cmd executor’у. При этом, как вы наверное уже сами догадались, вместо ? осуществляется подстановка имен процессов, которые нам необходимо прибить.
Допустим мы научились беспощадно убивать все, мешающие нам спокойно жить, задачи. Теперь надо бы научиться их еще и перезапускать. Попробовав разные варианты, я пришел к выводу, что самым простым и безболезненным является запуск всего 1 процесса, который в свою очередь сам запустит все остальные. Речь идет о простом скрипте по типу:
@echo off
set proxyPort=9090
pushd %~dp0
START CMD /C CALL proxy\bin\browsermob-proxy.bat -port %proxyPort%
START CMD /C CALL hub.bat
CD storage
START CMD /C CALL storage.cmd
popd
Раз уж мы умеем обращаться к CMD, почему бы не воспользоваться этим для запуска нашего скрипта? При этом, нам не стоит заморачиваться о целой пачке параметров, которые надо было бы передавать каждому сервису в отдельности. Конечно, тут тоже есть и свои подводные камни. К примеру, пожалуй ни для кого не секрет, что запуск процесса, бесконечно ждущего user input (hub / node), бескопромиссно вешает корневой, вызвавший его процесс. Зачастую спасает вариант запуска корневого процесса в отдельном потоке с возможностью “обрыва” по таймауту. Но это работает не всегда. Посему, как вариант, можно воспользоваться утилитой PSExec в качестве рутового процесса, которая успешно справляется с этой проблемой.
Далее, для основных API создаются соответствующие end-points - пожалуй наиболее универсальный способ доступа via REST. Теперь осталось внедрить наш сервер в автозагрузку на тачки, где будет проходить тестирование, настроить универсальный скрипт запуска, и все - сервер готов к бою.
Сразу отвечу на потенциально возможный вопрос - почему нельзя дописать парочку сервлетов и внедрить их непосредственно в selenium grid node? Вообще говоря, можно, никто не мешает. Но, во-первых, мне больше импонирует лаконичный REST-style. Во-вторых, чтобы внедрить REST-сервис в нод, нужно перелопатить исходники селениума, ибо разработчики видимо поленились добавить подобную поддержку (мол только сервлеты и точка). В-третьих, как уже было сказано выше, я не хотел привязываться сугубо к гриду, ибо терялась бы вся универсальность.
Итак, как вы сами понимаете, написать REST клиент теперь - нет никакой проблемы. Но открытым остается вопрос, откуда этот код собственно запускать? Из фреймворка - скажите? Как вариант. Но тогда по какому условию? Автоматизаторов в команде может быть много. И если каждый начнет прибивать все процессы налево и направо во время дебага, то начнется хаос и беспредел.
Гораздо эффективней будет отталкиваться от более глобальных задач, а именно - CI. В процессе планового запуска на Jenkins, маловероятно постороннее вмешательство на территорию VMs under test. Еще менее вероятно, что вы будет запускать джобы в параллели на одном и том же окружении. Т.е. Jenkins - прекрасное изолированное место, где наш код идеально бы прижился. Лично для себя нашел наиболее эффективной стратегию перезапуска сервисов до непосредственного старта прогона тестов. В таком случае мы гарантировано “подчистим” за всеми редисками, оставившими кучу зависших браузеров, сессий и т.п. Можно конечно это делать и после завершения прогона, но тут придется предусматривать доп. варианты развития событий в плане зависания самой джобы, непредвиденных фейлов и т.п.
Теперь вопрос состоит в том, как же подсунуть наш REST клиент Jenkins’у? Ответ очевиден - написать плагин. Благо, недавно уже была соответствующая статья в БЗ. Так что думаю, с этим проблем возникнуть уже не должно.
Помимо всего прочего, в исходниках вы сможете также обнаружить несколько дополнительных фич, а именно:
- опциональная возможность переконфигурации адреса хаба на нодах (если вы используете json файл) - полезно, если вы адрес хаба задаете динамически через переменную окружения Jenkins’a;
- автоматическое сворачивание всех окон после перезапуска сервисов.
Последнее было добавлено в следствии наблюдения странного эффекта, когда браузеры начинали открываться позади command-line окошек. Что может быть критичным, если вы работаете с image recognition based инструментами по типу SikuliX, либо снимаете скриншоты не встроенными в selenium средствами.
Ок, пожалуй прерву свое затянутое вступление. Более подробно ознакомиться с деталями вы можете, заглянув ко мне в блог. Исходники env-watcher и selenium-utils - как всегда на GitHub.
Сам плагин и console output выглядят следующим образом:
Как видите, вы можете сконфигурировать его так, чтобы он рестартил любые задачи хоть на десятке окружений. Стоит лишь добавить новый нод и магия начнет действовать. Подключив плагин в уже существующую джобу до непосредственного запуска maven сборки, вы сможете быть уверенными в том, что окружение приведено в пригодное для тестирования состояние. В случае фейла, сборка просто не стартанет, ибо нет смысла, к примеру, 2 часа ломиться на тачку, с которой нет связи.
П.С. Для линуксоидов - сорри, некоторые фичи только под винду. But you’re welcome with PRs. Ну и конечно же Java 8 is required для запуска. Хотя, при небольших правках можно спуститься и к семерке.