Часть 1
Сегодня поговорим о создании 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:
<dependencies>
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java8</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>Файл 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 openedAnd, 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<String> countries) { for (String str : countries) { $("#countryInput").sendKeys(str); $("#countryInput").pressEnter(); } }
При описании степов как параметр можно принимать списки – элементы перечисляются через запятую.
Остальные степы похожи на описанные выше.
В pom.xml в этот проект был добавлен Junit только из-за последнего степа, где проверка, что открылся нужный url, происходит с помощью assertThat().
На этом первая часть заканчивается. Читайте во второй части про автоматические скриншоты, кастомные Condition, PageObject, аннотацию элементов и создание красивых отчетов.