Эффективные UI-тесты на Selenide
31 Dec 2015В ожидании чудес
Канун Нового Года - время чудес. В преддверии нового года мы все вспоминаем год уходящий и строим планы на следующий. И надеемся, что все проблемы останутся в прошлом, а в новом году случится чудо, и мы заживём по-новому.
Какой же 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 здесь будет ваше веб-приложение)
Что здесь происходит?
- Вы открываете браузер всего-навсего одной командой
open(url)
- Вы ищете элемент на странице командой
$
.
Вы можете найти элемент по имени, ID, CSS селектору, атрибуту, xpath и даже по тексту. - Вы совершаете некие действия с элементом: в данном случае вводите текст командой
val()
и нажимаете ввод с помощью командыpressEnter()
. - Вы проверяете результат: ищете все результаты поиска с помощью
$$
(она возвращает коллекцию элементов). Вы проверяете размер и содержимое коллекции.
Этот тест легко читается, не правда ли?
Этот тест легко пишется, не правда ли?
А главное, этот тест легко запускается. Убедитесь сами:
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! Не засоряйте голову деталями и сконцентрируйтесь на бизнес-логике.
Сделаем мир лучше
У верю, что мир станет лучше, когда все разработчики будут писать автоматические тесты для своего кода. Когда разработчики будут спокойно вставать в 17:00 и идти к своим детям, не боясь, что они что-то сломали своими изменениями.
Поверьте мне, я чувствую себя уверенно оттого, что пока я ходил за кофе, мои тесты уже проверили мой код. И я точно знаю, что то, что я сдаю в тестирование - работает.
Давайте сделаем мир лучше с помощью автоматических тестов! Будьте уверены в своём софте, и вам не придётся скрещивать пальцы на удачу перед каждым релизом.
С Новым Годом!