Часть 1


Сегодня поговорим о создании UI smoke-теста для сайта с использованием фреймворков Cucumber и Selenide. Статья рассчитана на junior, который совсем ничего не знает про данные фреймворки. Опытный junior найдет во второй части интересные моменты, до которых я доходил пару месяцев.
Статья состоит из двух частей:


  • в первой описано создание нашего теста простейшим способом – чтобы запускалось и при этом никаких сложных вещей из фреймворков не использовалось. Только создадим описание фичи (.feature файл) и класс описания степов с использованием Selenide.
  • во второй части в тот же самый тест добавим всякие интересные штуки от Selenide, посмотрим, как создавать красивые отчеты, которые будут содержать текст фич (мн.ч от слова «фича»).

Фреймворки


Selenide – фреймворк (а точнее библиотека), обертывающий Selenium. Чем он отличается, прекрасно описано автором, Андреем Солнцевым. Главное отличие – Selenide позволяет сократить кучу строчек кода при написании UI тестов, что является одной из главных задач при создании тестов/написании кода, ибо Вы должны заботиться о том тестере, который придет после Вас и должен будет разбирать Ваше творение.


Cucumber – это фреймворк, реализующий подход BDD/TDD.



Я не претендую на глубокое теоретическое знание BDD/TDD, пока что для меня они суть одно и тоже.


BDD/TDD с практической точки зрения:


  1. От бизнеса приходит тех. задание, на основании которого программисты должны запилить новую функциональность – создать фичу
  2. Прежде чем программисты начнут писать код (как это делается в большинстве случаев), и тестеры и программисты садятся за круглый стол и обсуждают – как именно фича будет работать. Результатом круглого стола является записанная на бумаге фича – набор действий клиента/пользователя, который приводит к некому результату: а) нажал сюда; б) ввел цифры туда; в) получил результат там


    В результате такого круглого стола создается одно понимание на всех данной фичи, задокументированное на бумаге


  3. Далее программисты начинают писать код, согласно описанной фичи. Тестеры также начинают паралл��льно писать тесты, ибо записанная фича, благодаря 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 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


  1. Вид в фиче: Given open riskmarket.ru


    Вид в MyStepdefs:


    @Given("^open riskmarket\\.ru$")
    public void openRiskmarketRu()
    {
        open("http://riskmarket.ru");
    }

    Благодаря методу open(…) от Selenide в одной строчке создается instance WebDriver (по умолчанию Firefox) и происходит переход на указанный url. Закрывать/убивать instance не нужно, это сделает Selenide


  2. Вид в фиче:


    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.


  3. Вид в фиче:


    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) — отрпавляет текст в элемент.


  4. Вид в фиче:


    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, аннотацию элементов и создание красивых отчетов.