Как вы относитесь к cucumber? Не любите? Просто у вас рецепт не тот! А если любите, то полюбите ещё больше, когда я расскажу о замороженном кукумбере. Уж мороженое все любят. Меня зовут Юра Синдяшкин, я работаю в М.Видео-Эльдорадо и сегодня покажу, как кукумбер можно сделать ещё более удобным для автотестера.
В подходе BDD (Behaviour Driven Development — «Разработка через поведение») прекрасно все: читаемость сценария, низкий порог входа для начинающего автоматизатора, сценарий мчится на всех парах до своего падения. Если повезёт, то падение произойдёт через несколько недель или месяцев, когда в продукт внесут изменения. И вот тут перед автотестером возникает задача: как после падения быстрее внести правки в код теста?
Когда речь идёт о бэкэнде, артефакты теста можно отобразить в отчёте в удобном виде «Было-Стало», и тогда тест править легко. Но что делать, когда падает UI тест и лучшее, что у вас есть это скриншот? Проходить тест руками? Это не наш метод!
Итак, задача: нужно останавливать сценарий теста в момент падения. А ещё хорошо бы создать инструмент управления, чтобы останавливать сценарий по своему желанию и делать только один шаг или продолжать без вмешательства.
Решение: Хуки кукумбера. А именно хук «@AfterStep», который выполняется после каждого шага. Далее я покажу реализованное мной на Java решение, которым пользуется несколько команд автотестеров в М.Видео-Эльдорадо.
В хук передаётся состояние объекта Scenario, в котором хранится состояние сценария: упал или ещё всё впереди. Напишем процедуру, она будет вызываться в хуке. Будем менять состояние переменной breakpointState в соответствии с состоянием сценария, чтобы управлять заморозкой.
public static volatile String breakpointState = STATE_RESUME;
public static void handleBreakpointActions(boolean shouldBeStopped) {
if (shouldBeStopped && isBreakpointFeatureOn()) {
breakpointState = STATE_PAUSE;
}
if (breakpointState.equals(STATE_PAUSE) || breakpointState.equals(STATE_ONE_STEP)) {
breakpointState = STATE_PAUSE;
makePause();
}
else{
waitForMs(waitBetweenSteps);
}
}
Зачем дополнительная переменная breakpointState, если всё есть в Scenario? Не всё. Состояний теста будет три: STATE_RESUME (ход на всех парах), STATE_ONE_STEP (выполнить один шаг), STATE_PAUSE (заморозить тест). И управлять состояниями будем из разных потоков, где нет объектов Scenario. Об этом позже.
Если тест находится в состоянии STATE_PAUSE, то выполняем бесконечный цикл ожидания
private static void makePause() {
while (breakpointState.equals(STATE_PAUSE)) {
waitForMs(1000);
}
}
За время заморозки теста можно во все ещё открытом браузере проверить сломанный локатор, найти новый и исправить код теста.
Как же прервать бесконечное ожидание? Хочется ведь завершать тест обычным способом со сбором всех артефактов теста. Для управления замороженным сценарием можно использовать любой способ общения микросервисов. Я выбрал rest-запросы инструмента Spark. Он поднимается в отдельном потоке при старте теста и слушает три эндпоинта: «pause», «resume», «onestep».
Если сообщение к нему приходит, он меняет состояние breakpointState. Кто будет посылать сообщения по этим адресам? Команды поступают из двух пультов управления на выбор. Первый пульт управления - плагин для IntelliJ IDEA с красивыми кнопками и шорткатом «shift SPACE».
Инструкция по написанию плагина выходит за рамки статьи, поэтому приведу только фрагмент, относящийся к сути статьи. Это фрагмент файла plugin.xml, где я регистрирую действия плагина.
<actions>
<action id="breakpoint.oneStep" class="ru.mvideo.OneStepButton"
text="One Step" description="Do one step" icon="/icons/oneStep.png">
<add-to-group group-id="ToolbarRunGroup" anchor="last" />
</action>
<action id="breakpoint.pause" class="ru.mvideo.PauseButton"
text="Pause" description="Pause test" icon="/icons/pause.png">
<add-to-group group-id="ToolbarRunGroup" anchor="last" />
</action>
<action id="breakpoint.key" class="ru.mvideo.KeyControl">
<keyboard-shortcut first-keystroke="shift SPACE" keymap="$default" />
</action>
<action id="breakpoint.resume" class="ru.mvideo.ResumeButton"
text="Resume" description="Resume test" icon="/icons/resume.png">
<add-to-group group-id="ToolbarRunGroup" anchor="last" />
</action>
</actions>
Кнопки плагина выполняют простой http-запрос к спарку, в результате чего состояние теста меняется при следующем срабатывании хука «AfterStep». Кнопками удобно пользоваться, когда хочется видеть шаги сценария по мере прохождения. Что, если хочется видеть действия браузера и останавливать сценарий из браузера? Для этого есть второй пульт управления.
Второй пульт управления — это кнопки внутри браузера Chrome, которые появляются там благодаря созданному под эту задачу расширению. На старте тест регистрирует в хроме расширение, которое дополняет код тестируемой страницы элементами управления, то есть такими же тремя кнопками. Для примера, фрагмент создания кнопки с обработчиком нажатия:
pauseElement.addEventListener("click", function (){ sendBreakpointActionFunction(PAUSE_ACTION); }, true);
Итак, итог: кукумбер-тест можно замораживать по воле тестера, а также автоматически в случае падения шага до завершения теста! Как результат — можно в открытом браузере проверить локаторы и данные в том состоянии страницы, которое привело к падению.
Если эта статья была полезной или есть критика, то задавайте вопросы, подписывайтесь на блог М.Видео-Эльдорадо и я постараюсь ответить. Если тема статьи интересна, то дайте знать в комментариях и в следующий раз я планирую рассказать, как кукумбер можно наделить качествами Нео из «Матрицы», чтобы заглядывать в будущее и избегать падений в ходе выполнения сценария.