Пишем автотесты для 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 можно тут.