[Из песочницы] UI тесты: Cucumber + Selenide
Сегодня поговорим о создании UI smoke-теста для сайта с использованием фреймворков Cucumber и Selenide. Статья рассчитана на junior, который совсем ничего не знает про данные фреймворки. Опытный junior найдет во второй части интересные моменты, до которых я доходил пару месяцев.
Статья состоит из двух частей:
- в первой описано создание нашего теста простейшим способом — чтобы запускалось и при этом никаких сложных вещей из фреймворков не использовалось. Только создадим описание фичи (.feature файл) и класс описания степов с использованием Selenide.
- во второй части в тот же самый тест добавим всякие интересные штуки от Selenide, посмотрим, как создавать красивые отчеты, которые будут содержать текст фич (мн.ч от слова «фича»).
Фреймворки
Selenide — фреймворк (а точнее библиотека), обертывающий Selenium. Чем он отличается, прекрасно описано автором, Андреем Солнцевым. Главное отличие — Selenide позволяет сократить кучу строчек кода при написании UI тестов, что является одной из главных задач при создании тестов/написании кода, ибо Вы должны заботиться о том тестере, который придет после Вас и должен будет разбирать Ваше творение.
Cucumber — это фреймворк, реализующий подход BDD/TDD.
Я не претендую на глубокое теоретическое знание BDD/TDD, пока что для меня они суть одно и тоже.
BDD/TDD с практической точки зрения:
- От бизнеса приходит тех. задание, на основании которого программисты должны запилить новую функциональность — создать фичу
-
Прежде чем программисты начнут писать код (как это делается в большинстве случаев), и тестеры и программисты садятся за круглый стол и обсуждают — как именно фича будет работать. Результатом круглого стола является записанная на бумаге фича — набор действий клиента/пользователя, который приводит к некому результату: а) нажал сюда; б) ввел цифры туда; в) получил результат там
В результате такого круглого стола создается одно понимание на всех данной фичи, задокументированное на бумаге
- Далее программисты начинают писать код, согласно описанной фичи. Тестеры также начинают параллельно писать тесты, ибо записанная фича, благодаря Cucumber, является будущим тестом. Понятно, что тест может быть закончен только после того, как закончат кодить программисты, но таким образом написание кода и тестов идет параллельно, что ускоряет процесс разработки
Еще плюсы Cucumber:
- ненадобность логирования при написании тестов — каждый степ (действие пользователя) по сути своей является логированием.
- человеко-понятное описание тестов — тесты будут понятны даже людям из бизнеса, что может пригодиться при демонстрировании отчетов о тестировании.
- при описании бага не нужно придумывать steps to reproduce — необходимые степы берутся из отчета копипастом
Проект на гитхабе
Видео исполнения теста на youtube
Разберем первую, простую часть simple_selenide_cucumber.
Структура проекта:
Используем Intellij IDEA, Maven и Junit.
В mail.txt записаны логины, пароли аккаунтов для работы с тестом. ВНИМАНИЕ: если будете запускать у себя, имейте ввиду, что система выкинет одного из юзеров, которые будут логиниться под одним логином/паролем. Поменяйте мейл
В pom.xml прописываем следующие dependency:
com.codeborne
selenide
3.5
info.cukes
cucumber-java8
1.2.3
junit
junit
4.12
Файл smoketest#1.feature является той самой фичей (описанием фичи), которую согласовали программисты и тестеры за круглым столом (в идеальном мире:). Как видим, это описание действий пользователя на сайте, записанные в человеко-понятной форме, т.е. это еще и ваш файл логирования при условии, что каждый степ (действие) не подразумевает очень сложной логики:
Feature: smoke test #1, go through the service to Yandex-pay-page
Scenario: go through the service to button "Купить"
#actions at first page
Given open riskmarket.ru
When press button with text "Вход в кабинет"
And type to input with name "userName" text: "riskmarket.testoviy2016@yandex.ru"
And type to input with name "password" text: "l0dcfJMB"
And press element with value "Войти"
...
Scenario: go through service to yandex pay-page
Given press button with text "КУПИТЬ"
#actions at third page
When type to input with name "lastName" text: "TESTOVIY"
...
Создание вашего UI теста начинается именно с этого файла, файл с расширением .feature. Вы должны поместить его в пакет test/java/…/features/
Фича должна начинаться с ключевого слова:
Feature:
Здесь указывается общими словами что именно делает фича. В нашем случае smoke-теста это «Пройти через сервис до страницы Яндекс.платежей»
Далее идет ключевое слово:
Scenario:
Сценарий фактически является отдельным тестом, т.е. фича может содержать сколько угодно сценариев (тестов). Все сценарии, очевидно, должны относиться к данной фиче. В нашем случае будет два сценария, первый — пройти до кнопки «Купить» и второй — пройти до страницы платежей. По правилам тестирования, сценарии (тесты) должны быть независимыми, т.е. успех прохождения одного сценария не должен зависеть от успеха прохождения второго сценария. ВНИМАНИЕ: в нашем случае это не выполняется — второй сценарий начинается на месте остановки первого сценария, и если первый свалится, то второй тоже.
У сценария также есть краткое описание — что именно он делает.
Далее идут сами степы. Перед каждым степом должно быть одно из ключевых слов Given, When, Then, And
или But
.
Given
— обозначает начальные условия, «Дано: то-то и то-то»
When
— действия пользователя: нажать сюда, подождать то
Then
— результат, который получается: чаще всего это некая проверка, как в нашем случае
Then element with tag "search-result-item" should exist
и
Then verify that page with url "http://money.yandex.ru/cashdesk" is opened
And
, But
— используется как союз «и», «но», чтобы легче читалось. С «и» все ясно. «но» может использоваться, например, в степах, описывающих мысль »… эта штука должна быть видна, НО вот эта должна быть скрыта»
Старайтесь соблюдать разделение степов на указанные три части (Given, When, Then
), т.к. это правила BDD/TDD.
После написания фичи вы можете запустить тест (правой клавишей по файлу фичи → Run). Результатом будет много Undefined step: <текст степа>
. Система намекает, что она не знает как выполнить каждый степ. Нужно подсунуть логику исполнения степа. Если вы пишете в ИДЕЕ, то у вас каждый неопределенный степ подсвечен. Нажмите Alt+Enter и пройдите по всем диалоговым окнам, не меняя значений. Будет создан класс MyStepdefs
(я для удобства поместил его в пакет steps
). Вы увидите что-то типа:
@Given("^open riskmarket\\.ru$")
public void openRiskmarketRu() throws Throwable
{
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
По умолчанию методы, определяющие степы кидают PendingException()
. Это нужно, чтобы не было неопределенных степов, и чтобы при этом можно было продолжать писать тесты. Т.е. пока фича пишется программистами, некоторые степы уже можно определить, а некоторые должны дождаться написания кода программистами. Каждый раз при запуске теста система будет напоминать вам какие именно степы еще не определены.
Вы также можете использовать лямбда-выражения для описания степов. Но я не буду разбирать это здесь, т.к. это отдельная тема. Будем делать по старинке.
Разберем определение степа подробнее:
@Given("^open riskmarket\\.ru$")
public void openRiskmarketRu()
Первая строчка — это аннотация, с помощью которой Cucumber понимает к какому именно степу относится данное определение. На месте @Given
, как говорилось ранее, может стоять @And/@Then/@But/@When
. Далее в аргументе аннотации используется регулярное выражение (regex).
Regex — это тема отдельной статьи, почитайте где-нибудь, материала полно.
Приведу ключевые используемые символы regex, которые нужны для старта:
- ^ — начало строки
- $ — конец строки
- (*.) — какой угодно текст
- »([^»]*)» — какой угодно текст, но в кавычках
Следующая строка public void openRiskmarketRu()
это название метода. Метод, определяющий степ, всегда должен быть public void
. Если вы используете Alt+Enter, то ИДЕЯ сама синтезирует название метода, чаще всего этого достаточно.
Разберем некоторые степы.
В описании логики степов используется Selenide
-
Вид в фиче:
Given open riskmarket.ru
Вид в MyStepdefs:
@Given("^open riskmarket\\.ru$") public void openRiskmarketRu() { open("http://riskmarket.ru"); }
Благодаря методу
open(…)
от Selenide в одной строчке создается instance WebDriver (по умолчанию Firefox) и происходит переход на указанный url. Закрывать/убивать instance не нужно, это сделает Selenide -
Вид в фиче:
When press button with text "Вход в кабинет"` And press button with text "Рассчитать полис" Given press button with text "КУПИТЬ" And press button with text "Оплатить"
Вид в MyStepdefs:
@When("^press button with text \"([^\"]*)\"$") public void press(String button) { $(byText(button)).waitUntil(Condition.visible, 15000).click(); }
Перед вами пример переиспользования степа. Старайтесь переиспользовать степы как можно чаще, не плодите код. В нашем примере в аргументе аннотации указываем, что «кнопка может содержать какой угодно текст, но в кавычках». Что прикольно, можно использовать любой язык.
Вообще говоря, для описания степов также можно использовать любой язык — можно писать так:
And нажать кнопку с текстом "Оплатить"
Раз название кнопки — это аргумент, то указываем его в сигнатуре метода:
public void press(String button)
$()
— это метод Selenide для поиска элемента на странице. У него есть много разных, удобных параметров. В данной случае ищем элемент, который содержит наш текст. Пишу статью из места с не очень быстрым интернетом, поэтому нужно добавить увеличенное ожидание, пока элемент не появится, т.к. встроенного таймаута на 4с не хватает.$(byText(button)
дает нам объект типаSelenideElement
, у которого среди прочих методов есть такое ожидание —waitUntil(Condition, timeout)
.Condition
— условие, которое мы ждем.Condition
— это класс Selenide, в котором описаны много разных условий, посмотрите, пригодится.И в конце, когда мы дождались появления элемента, кликаем по нему.
К слову, то, что здесь описано в одну строку, в чистом Selenium у вас бы заняло несколько строчек кода, с созданием WebDriverWait. -
Вид в фиче:
And type to input with name "userName" text: "riskmarket.testoviy2016@yandex.ru" And type to input with name "password" text: "l0dcfJMB” When type to input with name "lastName" text: "TESTOVIY" And type to input with name "firstName" text: "TEST"
Вид в MyStepdefs:
@And("^type to input with name \"([^\"]*)\" text: \"([^\"]*)\"$") public void typeToInputWithNameText(String input, String text) { sleep(1000); $(byName(input)).sendKeys(text); }
Данный степ используется в один из разов после появления фрейма, поэтому нужно сделать паузу, чтобы input успел появится — делается с помощью Selenide
sleep(timeout with ms)
.sendKeys(String)
— отрпавляет текст в элемент. -
Вид в фиче:
And select countries: Шенген, Финляндия, Китай
Вид в MyStepdefs:
@And("^select countries: (.*)$") public void selectCountries(List
countries) { for (String str : countries) { $("#countryInput").sendKeys(str); $("#countryInput").pressEnter(); } } При описании степов как параметр можно принимать списки — элементы перечисляются через запятую.
Остальные степы похожи на описанные выше.
В pom.xml в этот проект был добавлен Junit только из-за последнего степа, где проверка, что открылся нужный url, происходит с помощью assertThat ().
На этом первая часть заканчивается. Читайте во второй части про автоматические скриншоты, кастомные Condition
, PageObject, аннотацию элементов и создание красивых отчетов.