Выбор инструментов для UI тестирования: Selenium или Selenide?
Написать хороший UI тест непросто. AJAX запросы, таймауты и страницы с динамическим контентом - лишь часть проблем, с которыми сталкиваются тестировщики. Один из наиболее популярных продуктов в сфере UI тестов - это Selenium WebDriver. Однако стоит понимать, что это низкоуровневая библиотека для управления браузером, она не разрабатывалась для удобного написания тестов.
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 позволяет начать пользоваться всеми преимуществами фреймворка в любой момент. Почему бы не рассмотреть возможность познакомиться с новым инструментом?