Пишем автотесты для UI на базе Selenide. Часть 2
Публикуем практическую часть статьи-воркшопа о тестировании интерфейса новогоднего сайта с помощью локаторов CSS и XPath и фреймворка Selenide.
Первая, теоретическая часть статьи тут.
Другие статьи можно найти по тэгу #автоматизация
Практическая часть на Selenide
-
Перед созданием проекта установите Maven, openjdk 1.8 или выше, IDE intellij Community.
Автоматизированное тестирование будет выполнено с помощью Selenide. Selenide – это фреймворк для автоматизированного тестирования веб-приложений на основе Selenium WebDriver.
-
Создайте пустой проект с названием “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. Запустить автотесты локально можно следующими способами:
- Запуск автотестов через терминал, используя 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 можно тут.