Пишем автотесты для UI на базе Selenide. Часть 2

Публикуем практическую часть статьи-воркшопа о тестировании интерфейса новогоднего сайта с помощью локаторов CSS и XPath и фреймворка Selenide.

Первая, теоретическая часть статьи тут.

Другие статьи можно найти по тэгу #автоматизация

Практическая часть на Selenide

  1. Перед созданием проекта установите Maven, openjdk 1.8 или выше, IDE intellij Community.

Автоматизированное тестирование будет выполнено с помощью Selenide. Selenide – это фреймворк для автоматизированного тестирования веб-приложений на основе Selenium WebDriver.

  1. Создайте пустой проект с названием “ui-test” со следующими зависимостями в pom файле:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>ui-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <artifactId>junit-jupiter</artifactId>
      <groupId>org.junit.jupiter</groupId>
      <version>5.4.2</version>
    </dependency>
    <dependency>
      <groupId>com.codeborne</groupId>
      <artifactId>selenide</artifactId>
      <version>5.25.1</version>
    </dependency>
  </dependencies>
</project>


 Создайте структуру проекта:

Каталог main в нашем примере не будет использован, все тесты и вспомогательные элементы будет находиться в каталоге test.

4. Описание локаторов для Xpath

Для этого в каталоге model создайте java-класс и назовите его “XpathLocators”. Далее в нем создайте локаторы для Selenide:

public class XpathLocators {
    public SelenideElement homePage = $(By.xpath("//img[@class='untilmerry']"));
    public SelenideElement fullHeaderButton = $(By.xpath("//a[text()='Naughty or Nice List']"));
    public SelenideElement partialHeaderButton = $(By.xpath("//a[contains(text(),'ghty or Nice')]"));
    public SelenideElement openNaughtyOrNiceListByClassAndHrefButton = $(By.xpath("//a[@href='/Den/list.asp'] [@class='home-btn']"));
    public SelenideElement tableQuestionsByPartialIdField = $(By.xpath("//form[contains(@id,'uest')]"));
    public SelenideElement headerTopByIdField = $(By.xpath("//div[@id='top']"));
    public ElementsCollection searchElementAndFilterAndClickRadioButton = $$(By.xpath("//form[@id='questions']/ul[1]//li"));
    public SelenideElement searchElementAndGoToParent = $(By.xpath("//form[@id='questions']/.."));
    public SelenideElement headerNaughtyOrNiceListPage = $(By.xpath("//img[@src='../images/den/title_list.jpg']"));
    public void approveCookieWindow(){
        SelenideElement cookieButton = $(By.xpath("//a[@href='javascript:void(0)']"));
        if (cookieButton.shouldBe(visible, Duration.ofSeconds(10)).exists()){
            cookieButton.click();
        }
    }
}

Где “SelenideElement, который возвращается методом $ - является прокси-элементом. В момент создания с помощью $ реальный элемент на странице не ищется. Зато потом при любой попытке совершить с прокси-элементом какое-то действие или проверку - прокси-элемент получает последнюю актуальную "версию" реального элемента со страницы (типа WebElement) и "проксирует" ей указанное действие или проверку.

ElementCollection - объект этого класса также является прокси точно также как SelenideElement. Его можно получить с помощью вызова метода $$, и он представляет список веб-элементов на странице.”

5. Далее в каталоге UiTest создайте java-класс “BaseTest”.

Укажите в нем 2 условия перед и после выполнения автотеста:

public class BaseTest {
    @BeforeAll
    public static void setUp() {
        Configuration.headless = false;
    }
    @AfterAll
    public static void tearDown() {
        Selenide.closeWebDriver();
    }
}

6. Далее в каталоге UiTest создайте java-класс “XpathTest” с наследованием BaseTest. 

Внутри него опишите автотесты UI с использованием xpath локаторов:

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class XpathTest extends BaseTest {
    private XpathLocators xpath = new XpathLocators();
    private static String fullHeaderButton = "Naughty or Nice List";
    @BeforeEach
    public void prepareForTest() {
        open("http://www.northpole.com/");
    }
    @Test
    @Order(1)
    @DisplayName("Переход на главную страницу c локатором по имени элемента и атрибуту класса")
    public void fullHeaderHome() {
        xpath.approveCookieWindow();
        xpath.homePage.shouldBe(visible, Duration.ofSeconds(10));
    }
    @Test
    @Order(2)
    @DisplayName("Поиск по абсолютному совпадению заголовка 'Naughty or Nice List' кнопки Naughty or Nice List")
    public void fullHeaderButton() {
        xpath.fullHeaderButton.shouldBe(visible, Duration.ofSeconds(10))
                              .shouldHave(ownText(fullHeaderButton));
    }
    @Test
    @Order(3)
    @DisplayName("Поиск по частичному совпадению заголовка 'ghty or Nice' кнопки Naughty or Nice List")
    public void partialHeaderButton() {
        xpath.partialHeaderButton.shouldBe(visible, Duration.ofSeconds(10))
                                 .shouldHave(ownText(fullHeaderButton));
    }
    @Test
    @Order(4)
    @DisplayName("Поиск кнопки по атрибуту href с проверкой перехода на страницу Naughty Or NiceList")
    public void openNaughtyOrNiceListByClassAndHrefButton() {
        xpath.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                       .click();
        xpath.headerNaughtyOrNiceListPage.shouldBe(visible, Duration.ofSeconds(10));
    }
    @Test
    @Order(5)
    @DisplayName("Поиск по частичному совпадению значения атрибута")
    public void tableQuestionsByPartialIdField() {
        xpath.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                       .click();
        xpath.tableQuestionsByPartialIdField.shouldBe(visible, Duration.ofSeconds(10))
                                            .click();
    }
    @Test
    @Order(6)
    @DisplayName("Поиск по id элемента")
    public void searchHeaderTopByIdField() {
        xpath.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                       .click();
        xpath.headerTopByIdField.shouldBe(visible, Duration.ofSeconds(10));
    }
    @Test
    @Order(7)
    @DisplayName("Поиск по индексу в таблице, фильтр элемента по букве 'i', далее взять элемент по совпадению с индексом 0 и найти элемент input и кликнуть его")
    public void searchTextAndGoParentRadioButton() {
        xpath.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                       .click();
        xpath.searchElementAndFilterAndClickRadioButton.filterBy(text("i"))
                                                       .get(0)
                                                       .find(By.xpath("./input"))
                                                       .click();
    }
    @Test
    @Order(8)
    @DisplayName("Поиск элемента по id, далее переход к его родителю и сравнение ожидаемого результата")
    public void searchElementAndGoToParent() {
        xpath.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                       .click();
        xpath.searchElementAndGoToParent.getId().contains("naughtynice");
    }
}

При описании автотестов выполнялись проверки состояния элементов shouldBe() с ключами visible и Duration.ofSeconds(), они означают, что элемент должен был отображаться на странице, появиться элемент должен был в течении 10 секунд (Duration.ofSeconds(10)), если это будет выполнено ранее 10 сек, то выполняется следующая операция.

На некоторые элементы выполняется клик с помощью команды .click. С доступными командами для SelenideElement можно ознакомиться тут.


7. Запустить автотесты локально можно следующими способами:

  1. Запуск автотестов через терминал, используя maven и команды mvn clean install из каталога {folder_project}/ui-test
  2. Запустить автотесты из вкладки Maven
  3. Запустить автотесты в java-классе используя junit фреймворк.

Результат прогона:

8. Повторим тоже самое для css локаторов. Для этого в каталоге model создадим java-класс и назовем его CssLocators:

public class CssLocators {
    public SelenideElement homePage = $(By.cssSelector("img[class='untilmerry']"));
    public SelenideElement fullHeaderButton = $(By.cssSelector("a[href='/Den/list.asp'][class='home-link']"));
    public SelenideElement partialHeaderButton = $(By.cssSelector("a[href='/Den/list.asp'][class*='-link']"));
    public SelenideElement openNaughtyOrNiceListByClassAndHrefButton = $(By.cssSelector("a[href='/Den/list.asp'][class='home-btn']"));
    public SelenideElement tableQuestionsByPartialIdField = $(By.cssSelector("form[id*='uest']"));
    public SelenideElement headerTopByIdField = $(By.cssSelector("#top "));
    public ElementsCollection searchElementAndFilterAndClickRadioButton = $$(By.cssSelector("#questions > ul:nth-child(1) > li"));
    public SelenideElement headerNaughtyOrNiceListPage = $(By.cssSelector("img[src='../images/den/title_list.jpg']"));
    public void approveCookieWindow(){
        SelenideElement cookieButton = $(By.cssSelector("a[onclick='javascript:setnocookiecookie()']"));
        if (cookieButton.shouldBe(visible, Duration.ofSeconds(10)).exists()){
            cookieButton.click();
        }
    }
}

9. Далее в каталоге UiTest создадим java-класс “CssTest” с наследованием BaseTest. Внутри него опишем автотесты UI с использованием css локаторов:

public class CssTest extends BaseTest {
    private CssLocators cssLocators = new CssLocators();
    private static String fullHeaderButton = "Naughty or Nice List";
    @BeforeEach
    public void prepareForTest() {
        open("http://www.northpole.com/");
    }
    @Test
    @Order(1)
    @DisplayName("Переход на главную страницу c локатором по имени элемента и атрибуту класса")
    public void fullHeaderHome() {
        cssLocators.approveCookieWindow();
        cssLocators.homePage.shouldBe(visible, Duration.ofSeconds(10));
    }
    @Test
    @Order(2)
    @DisplayName("Поиск по абсолютному совпадению заголовка 'Naughty or Nice List' кнопки Naughty or Nice List")
    public void fullHeaderButton() {
        cssLocators.fullHeaderButton.shouldBe(visible, Duration.ofSeconds(10))
                                    .shouldHave(ownText(fullHeaderButton));
    }
    @Test
    @Order(3)
    @DisplayName("Поиск по частичному совпадению заголовка 'ghty or Nice' кнопки Naughty or Nice List")
    public void partialHeaderButton() {
        cssLocators.partialHeaderButton.shouldBe(visible, Duration.ofSeconds(10))
                                       .shouldHave(ownText(fullHeaderButton));
    }
    @Test
    @Order(4)
    @DisplayName("Поиск кнопки по атрибуту href с проверкой перехода на страницу Naughty Or NiceList")
    public void openNaughtyOrNiceListByClassAndHrefButton() {
        cssLocators.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                             .click();
        cssLocators.headerNaughtyOrNiceListPage.shouldBe(visible, Duration.ofSeconds(10));
    }
    @Test
    @Order(5)
    @DisplayName("Поиск по частичному совпадению значения атрибута")
    public void tableQuestionsByPartialIdField() {
        cssLocators.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                             .click();
        cssLocators.tableQuestionsByPartialIdField.shouldBe(visible, Duration.ofSeconds(10))
                                                  .click();
    }
    @Test
    @Order(6)
    @DisplayName("Поиск по id элемента")
    public void searchHeaderTopByIdField() {
        cssLocators.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                             .click();
        cssLocators.headerTopByIdField.shouldBe(visible, Duration.ofSeconds(10));
    }
    @Test
    @Order(7)
    @DisplayName("Поиск по индексу в таблице, фильтр элемента по букве 'i', далее взять элемент по совпадению с индексом 0 и найти элемент input и кликнуть его")
    public void searchTextAndGoParentRadioButton() {
        cssLocators.openNaughtyOrNiceListByClassAndHrefButton.shouldBe(visible, Duration.ofSeconds(10))
                                                             .click();
        cssLocators.searchElementAndFilterAndClickRadioButton.filterBy(text("i"))
                                                             .get(0)
                                                             .find(By.xpath("./input"))
                                                             .click();
    }
}

10. Повторим запуск всех написанных тестов удобным вам способом.

Результаты следующие:


XPath позволяет перемещаться по дереву вниз и вверх, дает возможность использовать оси элементов. Бытует мнение, что поиск элемента с помощью xpath выполняется дольше чем css, но по факту незначительно (либо нужно пересмотреть локатор xpath). Подробнее почитать про xpath можно тут.

CSS – короткий при написании, достаточно читабельный, имеет ограничение по переходу от родителя к ребенку (но не от ребенка к родителю). Подробнее почитать по css можно тут.