Эффективные UI-тесты на Selenide

В ожидании чудес

Канун Нового Года - время чудес. В преддверии нового года мы все вспоминаем год уходящий и строим планы на следующий. И надеемся, что все проблемы останутся в прошлом, а в новом году случится чудо, и мы заживём по-новому.

Какой же Java разработчик не мечтает о чуде, которое осенит его и позволит стать Самым Крутым На Свете Java Программистом.

Хорошие новости: я хочу рассказать как раз о таком чуде.

Имя ему - автоматические тесты!

Фу, тесты?

Да. Настоящим мастером своего дела вас сделают не чудо-фреймворки, не микро/пико/нано сервисы, а дисциплина. Дисциплина, которая говорит, что программист может считать дело законченным не тогда, когда код готов, а тогда, когда к нему написаны и запущены автоматические тесты. И если с юнит-тестами всё более-менее ясно, то UI-тесты пока остаются для разработчиков тёмным лесом.

Да ну, это же нудно?

О нет, поверьте мне! Написание грамотных автотестов - это отличный челлендж, тут есть над чем пораскинуть мозгами. И это может быть очень весело и интересно. Только надо использовать правильные инструменты.

Правильный инструмент для написания UI-тестов - это:

Selenide

Selenide - это библиотека для написания лаконичных и стабильных UI тестов с открытым исходным кодом.

Selenide - идеальный выбор для разработчиков, потому что у неё очень низкая кривая обучения. Вам не придётся заморачиваться со всеми этими техническими подробностям, на которые обычно тестировщики-автоматизаторы тратят так много времени: нюансы работы с браузерами, типичные проблемы с таймингом и аяксом.

Посмотрим, как выглядит простенький тест на Selenide:

public class GoogleTest {
  @Test
  public void user_can_search_everything_in_google() {
    open("http://google.com/ncr");
    $(By.name("q")).val("selenide").pressEnter();

    $$("#ires .g").shouldHave(size(10));

    $("#ires .g").shouldBe(visible).shouldHave(
        text("Selenide: concise UI tests in Java"),
        text("selenide.org"));
  }
}

(естественно, вместо Google здесь будет ваше веб-приложение)

Что здесь происходит?

Этот тест легко читается, не правда ли?
Этот тест легко пишется, не правда ли?

А главное, этот тест легко запускается. Убедитесь сами:

Selenide Harlem Shake from Selenide on Vimeo.

Погружаемся глубже

Конечно, в жизни не всё так просто. Написание автотестов подразумевает кучу проблем, ведь не зря разработчики так их боятся - больше, чем любого наисложнейшего фреймворка или технологии.

Но и здесь Selenide облегчает нашу жизнь, решая большинство этих проблем из коробки.

Давайте рассмотрим типичные проблемы UI-тестов подробнее.

Проблемы с аяксом и таймаутами

В наше время веб-приложения все сплошь динамические. Каждый кусочек приложения может быть нарисован/изменён динамически в любой момент времени. Это создаёт проблемы для автоматических тестов. Тест, который ещё вчера был зелёным, может внезапно стать красным безо всяких изменений в коде. Просто потому, что браузер сегодня встал не с той ноги и запустил вон тот javascript чуточку медленнее, и тест успел кликнуть кнопочку раньше, чем она до конца отрисовалась.

Это прям вечная проблема у всех. Поэтому автоматизаторы пихают везде “слипы”.

Тем более удивительно, насколько простым и надёжным способом Selenide решает эту проблему.

Если коротко, в Selenide каждый метод умеет немножко подождать, если надо. Люди называют это “умными ожиданиями”.

Когда вы пишете

$("#menu").shouldHave(text("Hello"));

Selenide проверит, существует ли элемент с ID=”menu”. И если нет, Selenide чуть-чуть подождёт, проверит ещё. Потом ещё подождёт. И только когда элемент появится, Selenide проверит, что у него нужный текст.

Конечно, нельзя ждать вечно. Поэтому Selenide ждёт не больше 4 секунд. Естественно, этот таймаут можно настраивать.

Не сделает ли это мои тесты медленными?

Нет, не сделает. Selenide ждёт, только если надо. Если элемент изначально присутствует на странице - Selenide не ждёт. Если элемент появился через 300 мс - Selenide ждёт только 300 мс. Это именно то, что вам нужно.

Множество встроенных проверок

А что ещё вы можете проверять на странице, помимо текста? Довольно много всего.

Например, вы можете проверить, что элемент видимый (visible). Если пока нет, Selenide подождёт до 4 секунд.

$(".loading_progress").shouldBe(visible);

Вы можете даже проверить, что элемент не существует. Если элемент всё же найден, Selenide предположит, что он вот-вот пропадёт и подождёт до 4 секунд.

$(By.name("gender")).should(disappear);

Вы можете делать несколько проверок в одной строке (т.н. “fluent API” и “method chain”), что сделает ваши тесты ещё более лаконичными:

$("#menu")
  .shouldHave(text("Hello"), text("John!"))
  .shouldBe(enabled, selected);

Коллекции

Selenide позволяет вам очень удобно работать с коллекциями элементов. Вы можете проверять сразу множество элементов в одной строке.

Например, вы можете проверить, что на странице ровно N таких-то элементов:

$$(".error").shouldHave(size(3));

Вы можете отфильтровать подмножество элементов:

$$("#employees tbody tr")
  .filter(visible)
  .shouldHave(size(4));

Вы можете проверить тексты элементов. В большинстве случаев этого достаточно, чтобы проверить целую таблицу или строку в таблице:

$$("#employees tbody tr").shouldHave(
  texts(
      "John Belushi",
      "Bruce Willis",
      "John Malkovich"
  )
);

Скачивание/закачивание файлов

С Selenide закачивать файлы предельно просто:

$("#cv").uploadFile(new File("cv.doc"));

И вы даже можете закачать несколько файлов разом:

$("#cv").uploadFile(
  new File("cv1.doc"),
  new File("cv2.doc"),
  new File("cv3.doc")
);

И скачивание файлов тоже крайне просто:

File pdf = $(".btn#cv").download();

Тестирование “динамичных” веб-приложений

Некоторые веб-фреймворки (такие как GWT) генерируют совершенно нечитаемый HTML, не поддающийся анализу. Там нет постоянных ID, имён или классов.

Это прям вечная проблема у всех. Поэтому автоматизаторы пихают везде длиннющие “xpath” и вынуждены их поддерживать до конца жизни.

Чтобы решить эту проблему, Selenide предлагает искать элементы по тексту.

import static com.codeborne.selenide.Selectors.*;

$(byText("Привет, октябрята!"))             // находит элемент по тексту целиком
   .shouldBe(visible);

$(withText("тябр"))                         // находит элемент по подстроке
   .shouldHave(text("Привет, октябрята!"));

Вопреки распространённому мнению, поиск элементов по тексту - не такая уж плохая идея. Между прочим, именно так ищет элементы реальный пользователь. Он не ищет элементы по ID или классу, и уж тем более не по XPATH. Он ищет по тексту. (ну, ещё по цвету, но это труднее поддаётся автоматизации).

Ещё в Selenide есть несколько полезных методов для поиска дочерних или родительских элементов. Это позволяет вам навигировать между элементами без опознавательных знаков.

$("td").parent()
$("td").closest("tr")
$(".btn").closest(".modal")
$("div").find(By.name("q"))

Например, вы можете найти ячейку в таблице по тексту, затем найти содержащую её строку tr и найти в этой строке кнопку “Save”:

$("table#employees")
  .find(byText("Joshua"))
  .closest("tr.employee")
  .find(byValue("Save"))
  .click();

PageObject

Когда один и тот же элемент или страница используется во многих тестах, имеет смысл вынести логику страницы в отдельный класс. Такой класс называется Page Object, и их тоже очень удобно делать с Selenide.

Приведённый выше пример гугла можно переделать на page object таким образом:

  @Test
  public void userCanSearch() {
    GooglePage page = open("http://google.com/ncr", GooglePage.class);
    SearchResultsPage results = page.searchFor("selenide");
    results.getResults().shouldHave(size(10));
    results.getResult(0).shouldHave(text("Selenide: concise UI tests in Java"));
  }

Page Object для страницы поиска гугл:

public class GooglePage {
  public SearchResultsPage searchFor(String text) {
    $(By.name("q")).val(text).pressEnter();
    return page(SearchResultsPage.class);
  }
}

И для страницы результатов поиска:

public class SearchResultsPage {
  public ElementsCollection getResults() {
    return $$("#ires .g");
  }
  public SelenideElement getResult(int index) {
    return $("#ires .g", index);
  }
}

Но хочу обратить ваше внимание, что UI-тестов должно быть мало. По той простой причине, что это всё-таки браузер, аякс, javascript, а всё это сравнительно медленно и нестабильно. Напишите один-два UI-теста, которые проверят, что приложение в целом работает: страничка открывается, текст отрисовывается, кнопки нажимаются, JavaScript не грохается.

А всевозможные комбинации и редкие случаи обязательно проверяйте с помощью модульных тестов.

Типичная ошибка - проверять всё через UI. Этим особенно страдают тестировщики в тех компаниях, где разработчики не пишут модульных тестов. Бедным тестировщикам просто ничего не остаётся, кроме как городить огромную неповоротливую кучу медленных и нестабильных UI-тестов и впрягаться в их пожизненную поддержку.

Но ты ж программист! Не заставляй людей мучаться.

… и много других полезняшек

В Selenide есть ещё много функций, таких как:

$("div").scrollTo();
$("div").innerText();
$("div").innerHtml();
$("div").exists();
$("select").isImage();
$("select").getSelectedText();
$("select").getSelectedValue();
$("div").doubleClick();
$("div").contextClick();
$("div").hover();
$("div").dragAndDrop()
zoom(2.5);
...

мы не сможем их все здесть описать. Но хорошая новость в том, что вам не нужно всё это запоминать. Просто наберите $, точку и начните писать примерно, что вы хотите. Например “val” или “enter”. И посмотрите, какие варианты предложит ваша IDE.

Используйте мощь IDE! Не засоряйте голову деталями и сконцентрируйтесь на бизнес-логике.

Мощь IDE

Сделаем мир лучше

У верю, что мир станет лучше, когда все разработчики будут писать автоматические тесты для своего кода. Когда разработчики будут спокойно вставать в 17:00 и идти к своим детям, не боясь, что они что-то сломали своими изменениями.

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

Давайте сделаем мир лучше с помощью автоматических тестов! Будьте уверены в своём софте, и вам не придётся скрещивать пальцы на удачу перед каждым релизом.

С Новым Годом!

Андрей Солнцев

ru.selenide.org