Пишем автотесты для UI на базе Selenide. Часть 2
Публикуем практическую часть статьи-воркшопа о тестировании интерфейса новогоднего сайта с помощью локаторов CSS и XPath и фреймворка Selenide.
Первая, теоретическая часть статьи тут.
Другие статьи можно найти по тэгу #автоматизация
Практическая часть на Selenide
-
Перед созданием проекта установите Maven, openjdk 1.8 или выше, IDE intellij Community.
Автоматизированное тестирование будет выполнено с помощью Selenide. Selenide — это фреймворк для автоматизированного тестирования веб-приложений на основе Selenium WebDriver.
-
Создайте пустой проект с названием «ui-test» со следующими зависимостями в pom файле:
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"> 4.0.0 org.example ui-test 1.0-SNAPSHOT UTF-8 3.8.1 11 11 junit-jupiter org.junit.jupiter 5.4.2 com.codeborne selenide 5.25.1
Создайте структуру проекта:
Каталог 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. Запустить автотесты локально можно следующими способами:
- Запуск автотестов через терминал, используя maven и команды mvn clean install из каталога {folder_project}/ui-test
- Запустить автотесты из вкладки Maven
- Запустить автотесты в 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 можно тут.
