Environment Watcher или как бороться с нестабильным окружением

Те, кто уже смог познать все прелести масштабирования, знают, что нередко удаленное окружение начинает сбоить: зависшие сессии грида, незакрытые браузеры, блокирующие ОС диалоги, висящие драйвера в таск менеджере и т.п. Все это приводит к рэндомным падениям тестов, утечкам памяти, увеличению времени прогона и т.д. Возникает логичный вопрос - как с этим бороться?

На просторах сети можно найти интересные попытки решения, и даже целый проект, посвященный гриду и его саппорту. Идея, заложенная в основы 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’у? Ответ очевиден - написать плагин. :blush: Благо, недавно уже была соответствующая статья в БЗ. Так что думаю, с этим проблем возникнуть уже не должно.

Помимо всего прочего, в исходниках вы сможете также обнаружить несколько дополнительных фич, а именно:

  • опциональная возможность переконфигурации адреса хаба на нодах (если вы используете 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. :wink: Ну и конечно же Java 8 is required для запуска. Хотя, при небольших правках можно спуститься и к семерке.

3 лайка
  1. В WinXP/WinServer2003 tokens=1 есть имя процесса, нужно tokens=2
  2. Зачем вообще эти “танцы” с for/tasklist/findstr, если можно просто “прибить” по taskkill /f /im ie*
  3. Ну и главный вопрос - зачем так усложнять задачу, накручивая REST/плагины/кучу_кода, если тот же эффект можно получить простым раном батника через PSExec перед стартом джоба?
1 лайк

Хорошая статья. Про “Selenium Grid Extras” не знал.

Присоединяюсь к вопросу. Отмечу также, что API для выполнения команд на windows уже существует: Windows Remote Management. Я пользуюсь его клиентом под python.

1 лайк
  1. Кто-то еще ранит тесты под WinXP/WinServer2003? Come on… Давайте еще о Win98 вспомним в преддверии Win 10. Даже наши олд-скульные американцы уже заморозили саппорт WinXP виртуалок.
  2. Интересно, сколько процессов в принципе может начинаться с ie*, которые вы можете случайно прибить? Можно сделать легче? Супер! Я только “за”. Сильно ли изменится общая картина? Нет.
  3. Разница в том, что если вы будете ранить PSExec напрямую из Jenkins, придется указывать рутовые креденшалы тачек, куда надо отправлять команды. Как бы креденшалы могут быть везде разные. При данном подходе никаких креденшалов не нужно. Да и всегда ли они у вас будут? Интересно посмотреть на простыню команд PSExec для разных тачек: убить таски, убить джава процессы, перезаписать IP, запустить батник, свернуть все окна. Как, кстати, собираетесь через PSExec реплейсить hub ip у нода? Как будете сворачивать окна? Сможет ли PSExec обработать команду с разным набором кавычек? Нигде, кроме Jenkins, не запустить (без доп. телодвижений). Добавление новых фич под большим вопросом. Да и где вы увидели много кода? 2 сервиса на стандартной серверной платформе, 1 мини-клиент, и полуавтоматически сгенерированный плагин. Вот в selenium grid extras - да, кода действительно много. Единственный более вменяемый вариант тут будет - расширение возможностей самого батника для решения всех этих задач. Но опять-таки, не для всех тасков это подходит.

Если честно, вообще не вижу повода для споров. Плюс этого подхода в гибкости и расширяемости. Придумали более удобный способ? Захотели новые фичи? Да не вопрос. Клиент-серверная платформа с базовым набором утилит готова / плагин есть. Минимум изменений, и код будет делать то, что вам нужно.

1 лайк