Лаконичный PageObject с GroovyPage + Selenide
Не буду писать много вступительных речей, скажу сразу: если вы давно задумывались над оптимизацией ваших page объектов или хотели бы видеть, как это делают другие, то вам сюда.
Если вы не знакомы с паттерном page object, то вот ссылка напочитать.
Если же вы знакомы с этим паттерном, то вы его пишете примерно так, как показано в этих примерах.
Не имею ничего против этого паттерна, сам такое же писал. Более того, применение page объектов, я считаю обязательным, при том в самом начале проекта. Привыкайте делать все правильно с самого начала.
Но достаточно ли стандартного page оbject в суровой реальности? Беру на себя смелость сказать "нет", так как web приложения стали компонентными, страницы и их контент становится все динамичнее и динамичнее, а наши фреймворки все так же статичны.
Перед тем, как перейти к самому главному, стоит отметить, что самый крутой на нынешний момент фреймворк для написания UI тестов Selenide поддерживает работу с page объектами.
Казалось бы, все есть и очередной блогер решил "закапитанить" о всем давно известном паттерне. Нет! На самом деле я хочу поделиться с вами своей реализацией этого паттерна.
Еще очень давно я узнал о таком фреймворке для написания UI тестов как Geb. Отличный инструмент, написанный на Groovy и предназаченый для написания тестов на нем же. Если вы окунетесь в документацию, то можете заметить, что Geb - это тот же Selenide, с той лишь только разницей, что Geb не умеет так "умно" ждать состояний элементов. Плюс ко всему, в репозиторий Geb последний раз коммитили 5 лет назад, в отличие от Selenide, который активно развивается.
И тем не менее есть у Geb одна классная штука - page object.
Внимание!!! Все знаки $ в куске кода, показанного ниже, никакого отношения к Selenide не имеют - это все чистый Geb. Да, у него тоже поиск элементов через знак доллара.
import geb.Page
class LoginPage extends Page {
static url = "http://myapp.com/login"
static at = { heading.text() == "Please Login" }
static content = {
heading { $("h1") }
loginForm { $("form.login") }
loginButton(to: AdminPage) { loginForm.login() }
}
}
Лаконичный и читабельный page object. НО! Как всегда есть один недостаток - эта штука работает только с нативными методами Geb, нельзя так просто взять его и использовать с тем же Selenide или чистым WebDriver. Вот такая пИчалька, можно было бы ставить точку и закрывать лавочку.
Но нет, я потратил достаточное количество времени и сил,чтобы разобраться в том, как работает Geb и сделал свою реализацию geb page object, которую можно использовать как с Selenide, так и с WebDriver. Я решил назвать эту штуку GroovyPage.
Весь код ниже теперь уже будет использовать Selenide!!!
import static com.codeborne.selenide.Selenide.$
class MainPage extends Page{
static url = "http://ukr.net"
static content = {
login {$ '.login input'}
password {$ '.password input'}
submitBtn {$ '.submit button'}
}
}
Тесты в таком исполнении будут выглядеть так:
@Test
public void shouldLogin(){
MainPage mainPage = go MainPage
mainPage.login.val "test"
mainPage.password.val "test"
mainPage.submitBtn.click()
}
Заметили разницу? Обращение к элементам идет как к свойствам объекта. Все поля, объявленные внутри блока 'content', являются SelenideElement. Но и это еще не все приятности на данный момент. Используя силу Groovy, есть возможность писать тесты так:
@Test
public void shouldLogin(){
MainPage mainPage = go MainPage
mainPage.login << "test"
mainPage.password << "test"
mainPage.submitBtn.click()
}
В этом случае метод leftShift переопределен и выполняет те же действия, что и метод val.
Ну и самая последняя фича, доступная на данный момент - Page компоненты. Перепишем нашу MainPage c использованием компонента LoginForm:
class LoginForm extends Page{
static content = {
login {$ '.login input'}
password {$ '.password input'}
submitBtn {$ '.submit button'}
}
}
class MainPageWithComponent {
static url = "http://ukr.net"
@Component
LoginForm loginForm
}
Тест в таком случае будет выглядеть так:
@Test
public void shouldLogin(){
MainPageWithComponent mainPage = go MainPageWithComponent
mainPage.loginForm.login << "test"
mainPage.loginForm.password << "test"
mainPage.loginForm.submitBtn.click()
}
Применяя магию, тест будет выглядеть так:
@Test
public void shouldLogin(){
MainPageWithComponent mainPage = go MainPageWithComponent
mainPage.loginForm.with{
login << "test"
password << "test"
submitBtn.click()
}
}
Какие недостатки? Недостаток один - такой код можно писать только используя динамику Groovy, строгий компилятор Java такие выкрутасы не пропустит. И да, Intelij Idea весь этот синтаксис понимает и везде работает автодополнение.
На этом у меня все, если у вас буду вопросы или пожелания, пишите - будем развивать отрасль вместе.
Оставайтесь на связи, подписывайтесь на группы в Facebook и Вконтакте.