Выбор инструментов для UI тестирования: Selenium или Selenide?

Написать хороший UI тест непросто. AJAX запросы, таймауты и страницы с динамическим контентом - лишь часть проблем, с которыми сталкиваются тестировщики. Один из наиболее популярных продуктов в сфере UI тестов - это Selenium WebDriver. Однако стоит понимать, что это низкоуровневая библиотека для управления браузером, она не разрабатывалась для удобного написания тестов.

Выбор инструментов для UI тестирования: Selenium или Selenide?

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 позволяет начать пользоваться всеми преимуществами фреймворка в любой момент. Почему бы не рассмотреть возможность познакомиться с новым инструментом?