Выбор инструментов для UI тестирования: Selenium или Selenide?
Написать хороший UI тест непросто. AJAX запросы, таймауты и страницы с динамическим контентом - лишь часть проблем, с которыми сталкиваются тестировщики. Один из наиболее популярных продуктов в сфере UI тестов - это Selenium WebDriver. Однако стоит понимать, что это низкоуровневая библиотека для управления браузером, она не разрабатывалась для удобного написания тестов.
![Выбор инструментов для UI тестирования: Selenium или Selenide?](assets/images/image.jpg)
Selenide же является фреймворком, «обёрткой» вокруг Selenium WebDriver. Selenide создавался именно как инструмент для написания автоматических тестов. Среди особенностей этого фреймворка можно выделить следующие:
- Лаконичный синтаксис;
- Умные ожидания;
- Простая конфигурация.
Давайте разберем, как Selenide может упростить работу тестировщиков и какие преимущества имеет Selenide перед Selenium в контексте UI тестирования.
Работа с явными и неявными ожиданиями
В Selenium предусмотрены механизмы для ожидания различных событий на странице, но в них предусмотрено не все. Например, нет возможности ожидать, пока элемент пропадет со страницы. Для неявного ожидания можно задать дефолтный таймаут:
driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
Явное ожидание выглядит так и занимает несколько строк:
new WebDriverWait(webDriver, 30)
.until(ExpectedConditions
.visibilityOfElementLocated(By.className("banner-items"));
В Selenide для неявного ожидания по умолчанию задан таймаут на 4 секунды. То есть если страница не прогрузилась сразу же, Selenide выдержит паузу перед тем, как засчитать тест неудачным. В конфигурации вы можете легко настроить значение таймаута по умолчанию:
Configuration.timeout = 3000;
Явные ожидания в Selenide визуально выглядят проще. Все, что вам нужно сделать, - это взять какой-то элемент и сообщить Selenide, сколько нужно ждать до выполнения определенного условия:
$(By.className("banner-items")).waitUntil(visible, 30000);
Также существует возможность подождать, пока элемент исчезнет со страницы:
$(“.test”).should(disappear);
Работа с драйвером
В Selenium не предусмотрено механизма для удобного доступа к объекту веб-драйвера, поэтому каждый тестировщик придумывает механизмы в меру своей фантазии. Это неудобно, к тому же дубликаты не ускоряют тестирование:
@Test
public void searchSelenideInGoogle() {
WebDriver driver = Browser.launch();
driver.findElement(By.name("q"));
...
driver.quit();
}
В Selenide вам не нужно создавать веб-драйвер. Более того, разработчики подразумевают, что вы даже не будете этот драйвер видеть – все уже готово к работе. Так, тест может выглядеть следующим образом:
@Test
public void searchSelenideInGoogle() {
open("https://google.com/ncr");
$(By.name("q")).val("selenide").pressEnter();
...
}
Конечно, если все-таки драйвер вам понадобился, вы всегда можете воспользоваться классом со статическим методом, который возвращает текущий веб-драйвер:
WebDriverRunner.getWebDriver();
Работа с элементами
Работа с элементами в Selenium и Selenide имеет ряд отличий. В Selenium действия осуществляются посредством драйвера. Вы получаете элемент, а затем вызываете необходимые методы:
@Test
public void searchSelenideInGoogle() {
WebDriver driver = Browser.launch();
driver.get("https://www.google.com");
WebElement element = driver.findElement(By.name("q"));
element.sendKeys("selenide");
element.submit();
driver.findElement(By.id("res"));
assertThat(driver.findElement("#ires .g").getText(), containsString("Selenide"));
driver.quit();
}
Отличия в Selenide начинаются уже с открытия страницы, да и в целом работа с элементами устроена проще. Давайте рассмотрим это на примере:
@Test
public void searchSelenideInGoogle() {
open("https://google.com/ncr");
$(By.name("q")).val("selenide").pressEnter();
$$("#ires .g").shouldHave(sizeGreaterThan(1));
$("#ires .g").shouldBe(visible).shouldHave(
text("Selenide"),
text("selenide.org"));
}
Итак, драйвер уже определен. Символ $ позволяет получить один элемент (если под селектор попадает сразу несколько элементов, то будет выбран первый попавшийся). Методы взаимодействия с элементами можно вызывать один за другим. Так, устанавливаем значение Selenide и указываем, что дальше следует нажатие кнопки Enter.
Два символа $ позволяют получить коллекцию элементов. Это отдельный объект, который наследуется от collection. С этим объектом можно работать таким же образом, как и с collection, при этом у него есть свои уникальные методы. Например, можно сразу сделать проверку, что количество результатов из поисковой системы больше 1. Также можно взять первый попавшийся элемент из выдачи и удостовериться, что он видимый и имеет текст Selenide и selenide.org.
Важную роль в удобстве играют матчеры. В данном примере это shouldHave и shouldBe. Матчеры работают с неявным ожиданием, то есть если условие не выполняется сразу же, то перед тем, как тест свалится, пройдет некоторое время. Про матчеры будет сказано подробнее в пункте «Матчеры, ошибки, скриншоты».
Поиск элементов
Поиск по тексту
Используйте метод byText, который позволяет найти элемент по тексту, не прописывая элемент xpath:
$(byText("Some Text"));
Фильтрация результатов
Получите коллекцию и отфильтруйте результаты при помощи метода filter, который позволяет указать необходимые критерии. Затем метод find найдет среди отфильтрованных элементов необходимые, удовлетворяющие определенным условиям:
$$(".item").filter(visible).find(matches("Specific .* item text"));
Поиск по родителям
Осуществляйте поиск не только внутри элемента, но и выше по структуре. Разработчики Selenide говорят о том, что использовать xpath больше нет необходимости.
$$(".cell").findBy(text("Some Text")).parent();
Поиск в пределах элемента
Этот функционал аналогичен представленному в Selenium.
$(byText("Some Text")).$(".inner_item");
Работа с объектами страницы
Отдельно стоит упомянуть о разнице при работе с объектами страницы. В Selenium нужно объявить поля с элементами, которые вам нужны, прописать аннотацию FindBy, а для инициализации использовать PageFactory. Проблема такого подхода в том, что с помощью аннотаций не получится осуществить поиск внутри заданного элемента, а не по всей странице:
public class SomePage {
@FindBy(id = "text")
private WebElement searchField;
@FindBy(css = "input[type=\"submit\"]")
private WebElement searchButton;
public void init(final WebDriver driver) {
PageFactory.initElements(driver, this);
}
}
Совсем другой подход применяется в Selenide. Использование ленивой инициализации позволяет перечислить элементы, а поиск начнется только в тот момент, когда вы обратитесь к конкретному элементу:
public class SomePage {
private SelenideElement searchField = $(By.id("text"));
private SelenideElement searchFieldInnerElement = searchField.$(".inner");
private SelenideElement searchButton = $("input[type=\"submit\"]");
}
Матчеры, ошибки, скриншоты
Использование матчеров, среди прочего, позволяет проверить:
- Содержание текста;
- Определенный размер коллекции;
- Видимость;
- Исчезание элемента со страницы;
- Фокусировка элемента.
$("#ires .r").shouldHave(text("SomeText"));
$$("#ires .r").shouldHave(sizeGreaterThan(1));
$("#ires .r").shouldBe(visible);
$("#ires .r").should(disappear);
$("#ires .r").shouldBe(focused);
В случае ошибки вы увидите понятное сообщение. Например, Selenide укажет, какой текст вы искали, какой селектор использовали, какие атрибуты учитывали и какой элемент был фактически найден.
Selenide умеет автоматически делать скриншоты в случае возникновения ошибок (если вы указали, что это необходимо сделать).
Кастомизация
Если понадобилось проверить какое-то условие, которого нет из коробки, вы можете написать его самостоятельно. Сделать это легко. Давайте разберем пример написания условия CSS.
В любом удобном вам классе необходимо создать статический метод с произвольным набором принимаемых параметров. Этот метод должен возвращать экземпляр класса Condition. В конструкторе указывается новое название матчера. В этом классе необходимо реализовать метод apply, который выполняет необходимую проверку, и метод actual value, который возвращает действительное значение проверяемой сущности:
public static Condition css(final String propName, final String propValue) {
return new Condition("css") {
@Override
public boolean apply(WebElement element) {
return propValue.equalsIgnoreCase(element.getCssValue(propName));
}
@Override
public String actualValue(WebElement element) {
return element.getCssValue(propName);
}
};
}
Затем вы можете проверить, имеет ли определенный элемент конкретные параметры. Как видите, все условия легко читаются:
$("h1").shouldHave(css("font-size", "16px"));
Кроме того, вы можете кастомизировать встроенные операции над элементами. Методы, присущие элементам, можно переопределять. Для каждого метода создан отдельный класс, который поддерживает наследование. У вас есть возможность учитывать особенности тестирования приложений на мобильных устройствах и на пк, как показано в примере ниже. При работе на Android или iOS будет вызываться метод tapElement, а если используется браузер, то будет использоваться стандартный клик:
public class CustomClick extends Click {
@Override
public void click(WebElement element) {
if (isIOS() || isAndroid()) {
tapElement(element);
} else {
super.click(element);
}
}
}
При инициализации нужно будет указать, что для click следует использовать ваш новый кастомный класс:
Commands.getInstance().add("click", new CustomClick());
Встроенный профайлер
В Selenide есть встроенный профайлер тестов. Достаточно прописать специальную аннотацию для тестового класса, чтобы после выполнения каждого теста в консоль выводилась таблица. В ней будут указаны все произведенные в течение теста действия и время, потраченное на каждую из этих операций. Используйте профайлер, чтобы определить самые времязатратные операции и оптимизировать время выполнения тестов.
Простые названия методов
Названия методов в Selenide довольно просты. Во время работы в IDE попробуйте начать писать то действие, которое хотите выполнить. Как правило, оно легко находится, а вам не приходится искать его в документации. shouldBe, shouldHave, shouldNot – вот яркие примеры того, что названия говорят сами за себя.
Совместимость с Selenium
Не бойтесь перехода на Selenide даже если у вас уже есть сотни тестов в Selenium. Совместимость с Selenium позволяет начать использовать Selenide в любой момент. Единственное, что необходимо сделать, это указать экземпляр веб-драйвера, с которым необходимо работать:
WebDriverRunner.setWebDriver(driver);
Интерфейс WebElement расширяется при помощи SelenideElement. Это позволяет передавать элементы Selenide в старые методы, которые работают с WebElement. С другой стороны, любой WebElement можно легко преобразовать в SelenideElement следующим способом:
$(oldFashionedWebElement)
Таким образом, переход на Selenide можно осуществить плавно и без потери уже существующих тестов.
Selenide помогает писать стабильные тесты и экономить время. Его просто изучить, а совместимость с Selenium позволяет начать пользоваться всеми преимуществами фреймворка в любой момент. Почему бы не рассмотреть возможность познакомиться с новым инструментом?