Реальный опыт с Selenide: покупка авиабилетов

Реальный опыт с Selenide: покупка авиабилетов

тестируем airtickets.ru
17.04.16

Привет!

Иногда хочется посмотреть использование фреймворка в реальных боевых условиях, а не только хелловорды на кошечках. А почему бы нет!

В серии Selenide Examples сегодня мы протестируем совершенно настоящий сайт airtickets.ru с настоящими покупками, деньгами и карточками. Всё как в жизни.

Для разнообразия попробуем использовать шаблон PageObject, причём в разных вариациях:

Что тестируем

Тест покупает авиабилеты СПб-Москва туда и обратно, вводя все необходимые значения, и доходит до кнопки “Купить” после заполнения данных кредитной карточки.

Вот как это выглядит:


Как это работает

Давайте посмотрим, как этот тест устроен изнутри.

Проект на гитхабе

Что такое паттерн PageObject

PageObject - это способ работы с Web UI, при котором селекторы(локаторы) элементов веб-страницы задаются в отдельном классе. Делается это для того, чтобы при изменении структуры html достаточно было обновлять селекторы всего в одном месте и не искать их по всему коду.

Какие у нас страницы

Как видно из видео, мы работаем с 5-ю страницами.

В самом тесте (класс TestBuyingTickets) описана логика работы всего теста. Из названия методов понятно, что делает каждая строчка. Посмотрим поподробнее на некоторые из них:

первая строка теста

открываем страницу и в этой же строчке “засовываем” в переменную firstPageDirection все данные об элементах/логике первой страницы.

  FirstPageDirection firstPageDirection = open("http://www.airtickets.ru/...", FirstPageDirection.class);

Теперь

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

  • указать город отправления:
    firstPageDirection.addDirectionFrom("Санкт", "LED");
  • указать город прибытия:
    firstPageDirection.addDirectionTo("Москва", "MOW");

стоит пояснить, зачем нужен цикл while - иногда после выбора рейсов и нажатия кнопки “далее” на второй странице может выпасть редкое сообщение о том, что рейсы не доступны, и предложение выбрать другие рейсы. Этот цикл как раз проверяет, что это сообщение не появилось. Если оно появится, то цикл будет повторять выбор билета, пока сообщение будет появляться.

Окончанием теста

является проверка, что кнопка “Купить” отображена. В Selenide это делается очень просто благодаря мощному механизму проверки условий. Из самого названия метода понятно, что он проверяет:

  fifthPage.getButton().shouldBe(Condition.visible);

Какие тут пэдж объекты?

Первые три страницы реализованы в виде “селенидовских” PageObject. Т.е. делаем отдельные классы для каждой страницы (FirstPageDirection, SecondPageVariants, …) и в каждом описываем логику, которая нам нужна, в виде отдельных методов. При этом селекторы необходимых элементов прописываются в методах.

Например, добавить пункт отправления на первой странице (класс FirstPageDirection):

  public void addDirectionFrom(String city, String airport) {
    $("#from").setValue(city);
    $("#autocomplete").$(withText(airport)).click();
  }

Селекторы

По умолчанию $ (поиск элемента по селектору) принимает как аргумент CSS селектор. Поэтому $("#from") - означает “найти элемент с ID “from”.

Можете убедиться в том, что input “Откуда” имеет среди аттрибутов именно такой ID откройте в любом браузере страницу сервиса, наведите мышку на строку ввода пункта отправления “Откуда” и в контекстном меню (правая кнопка мыши) выберите “Просмотреть код”. Браузер откроет html код страницы и подсветит нужный нам input. Среди атрибутов этого элемента есть id=”from”.

Методы пэдж объекта

Далее вносим значение в этот input с помощью метода Selenide.setValue(String). Почему это написано в одну строку? Потому что первая часть $("#from") означает, что мы получили SelenideElement, у которого есть метод setValue(String). Также Вы можете использовать как аргумент $() и xpath, тогда вызов будет выглядеть так: $(By.xpath(....)).

Также в FirstPageDirection вы можете увидеть вспомогательные методы, которые нужны только для работы с первой страницей - получение текущего месяца из System. Это нужно для проверки, что сервис автоматически подставляет как месяц отправления либо текущий месяц, либо следующий. Проверка осуществляется с помощью assert’ов в тесте. Это, например, строка:

Assert.assertThat("Месяц отправления не равен текущему месяцу или следующему месяцу", 
  firstPageDirection.getMonthDeparture(), anyOf(is(firstPageDirection.getCurrentMonth()), is(firstPageDirection.getNextMonth())));

Кстати, крайне рекомендую освоить assertThat от hamcrest (которая уже давно в составе JUnit). Отличие assertThat от assertTrue, assertEquals и др. в читаемости, а также в том, что вы всегда при непрохождении проверки будете получать в логи фразы типа: пришло “то-то”, а ожидали “то-то”. Очень удобно.

Работа с коллекциями

В PageObject второй страницы (SecondPageVariants) используется селенидовский метод $$. Он возвращает массив всех элементов, найденных по заданному селектору.

Есть задача случайно выбрать рейс из предлагаемых. Для этого находим на странице все рейсы:

List<SelenideElement> radioList = priceTable.$$(".radio");

Здесь опять использован CSS-селектор, только в этом случае мы ищем элементы по аттрибуту class. Получаем массив всех существующих на странице .radio и выбираем среди них случайный:

SelenideElement radio = radioList.get((int)(Math.random() * (radioList.size()-1)));

(Немного упростил описание, но разобраться в том, как действительно отрабатывает логика, несложно.)

Классический пэдж объект с блоками

Четвертая страница (FourthPagePassengerInfo) реализована классическим PageObject c применением блоков. Блоки - это отдельные группы элементов на странице, которые для простоты обработки можно вынести в отдельный PageObject (использовать инкапсуляцию).

На четвертой странице у нас есть два фрейма, для заполнения информации о пассажирах и контактных данных. В начале класса мы говорим, что на странице нас интересуют два фрейма, задаем для них селекторы с Selenium аннотацией FindBy:

  @FindBy(xpath = "(.//*[@class='frame'])[1]")
  private FramePassengerInfo passengerInfo;

  @FindBy(xpath = "(.//*[@class='frame'])[2]")
  private FrameContactPersonInfo contactPersonInfo;

Как видите, тип фреймов у нас не WebElement и не SelenideElement, а соответствующий для каждого фрейма блок, который вы можете посмотреть в пакете ForthPagePassengerInfoElements.

Каждый блок описан в классическом PageObject, с аннотацией @FindBy и своими уникальными методами. Для того, чтобы показать, что каждый фрейм - это блок, необходимо наследовать ElementsContainer (в объявлении класса : public class FramePassengerInfo extends ElementsContainer).

Итоги

  1. Те, кто ранее писал тесты на чистом Selenium, надеюсь, увидели, что Selenide позволяет значительно сократить написание тестов.

  2. Паттерн PageObject, как и свойственно любому шаблону, является только идеей подхода написания кода. Вы можете описать селекторы в начале с аннотацией @FindBy (классический вариант), или же вы можете написать PageObject как предлагает Андрей Солнцев - страница содержит методы, внутри которых пишутся селекторы. Вы даже можете создать синглтон, который будет содержать мапу со всеми селекторами вашего проекта. И в описании логики тестов выдергивать селекторы из мапы и вставлять их как аргумент в селенидовский $ (именно такое решение применил у себя на проекте, где очень много элементов с похожими названиями, мало уникальных классов и айдишников). Главное, старайтесь описывать селектор для конкретного элемента в одном месте. Чтобы вы или тот автоматизатор, что придет на ваше место в будущем, могли это место легко отыскать и изменить селектор, если разработчики решат поменять html структуру.

Василий Ковальченко

17.04.16