Как стать автором
Обновить

Революция или эволюция Page Object Model?

Время на прочтение12 мин
Количество просмотров23K
Всего голосов 21: ↑21 и ↓0+21
Комментарии14

Комментарии 14

Выглядит классно, и идеи интересные.
Получилась эдакая реализация того, что в руби делается через модули. Конечно, некоторые моменты достаточно спорны (получилась-то по сути композиция через наследование интерфейсов), но все равно зачетно.
Спасибо за обратную связь. А какие спорные моменты вы нашли? Распишите, пожалуйста.
Привет. Спасибо за статью Артему и автору поста за перевод (если это разные люди), я надеюсь он где-то тут будет и сможет прочитать этот коммент.

Не совсем уверен, что я уловил все изменения, которые вы добавили по сравнению с прошлым HtmlElements, на который я когда ориентировался по репозиторию Артема Ерошенко webtests-example (которому уже почти 5 лет).
Вы добавили к нему еще мобилку, листенер и кое-какие фичи с селениде.

Есть пара вопросов:
1) Статья появилась сегодня (оригинал 7 ноября, что впринципе то же самое), а презентации Атласа датируются аж год назад. Были какие-то кардинальные изменения с того времени или почему статья выходит только сейчас?
2) Вроде как сам Артем Е. говорил, что на некоторых своих или других проектах использует selenide, как ваши ретраи, ожидания и условия совмещаются с ним? Или от чего-то в «идеальном» подходе проектирования проекта отказываетесь?
3) Есть разница какой Listener подключать? Который сейчас используется с аллюром — это обычно TestListenerAdapter + например, листенер степов io.qameta.allure.model.StepResult; Или лучше сразу нативный Atlas'а?
4) И касательно параметризации элементов, наверное более личный интерес:
Если на странице чередуются различные элементы и раз через раз это инпуты, селекторы со списками, чекбоксы и т.д. — не вызывает ли это какой-то фрустрации когда смотришь на описание локаторов в классе? Т.е. обычно это описывается «как есть» на странице сверху-вниз, но в случае с использованием Атласа это будет:
@FindBy("//div[text()='{{ text }}']/input")
AtlasWebElement input(@Param("text") String text);

@FindBy("xpath")
AtlasWebElement selectorField(@Param("idk") String text);

@FindBy("xpath")
ElementsCollection<selectorDropdownList> selectorsList();

@FindBy("xpath")
AtlasWebElement randomElement();


И заполнение страницы будет как:
public void fillAnket() {
 onPage().input("Заголовок").sendKeys("Что-то");
 onPage().selectorField().click();
 onPage().selectorsList().get(0).click();
 onPage().input("Опять инпут").sendKeys(" ");
 onPage().randomElement().getAttribute("");
}

Т.е. простота бектрекинга теряется, нет? Что уже описано, что нет становится не так очевидно. И какие-то элементы возможно будет стандартизировать под один формат параметризации, а какие-то нет на одной и той же странице. Если элемент фронтом описан «криво-косо» (часто с таким сталкиваюсь).

И есть может быть какие-то проекты как всё это вместе может выглядеть?
Привет!

Отвечаю на вопросы (оригинал и перевод, делал один человек).

1. Кардинальных перемен не было, добавился новый функционал: поддержка работы с Appium, исправлены баги, добавлены расширения. Весь перечень изменений, можно найти здесь.

2. Если правильно понял вопрос – возможно ли использовать selenide и atlas вместе? – ответ не пробовал. Можете поисследовать, наверное получится интересный гибрид.

3. Лучше сразу нативный, на который идет ссылка в статье.

4. Скорее поначалу будет не привычно, но со временем вы почувствуйте разницу. Например: у вас будет десять элементов на весь проект, чем 100 параметризированных. Понятно, текст может поменяться, но так у вас будет все в одном месте в тесте, чем ходить и править по каждому PO. Нужно попробовать, дальше сделаете вывод — нравится или нет.

5. “Если элемент фронтом описан «криво-косо» (часто с таким сталкиваюсь)” – сталкивался, приходилось делать костыли и договариваться с разработкой на нормальный элемент.

6. Примеры, можно найти в исходниках.
Глобальные параметры из HtmlElements 2.0 уже поддержали?
Привет. Еще не делали.
Печально. Без этого будет тяжело в мобильных использовать xPath, а иногда без этого не обойтись.
Проработка этой идеи какая нибудь уже есть?
Вот решил опробовать фреймворк, сделал простейший тест просто протыкающий несколько ссылок в википедии, даже без ассертов и нашел такую вещь, что вот такая последовательность операций не работает:
        onMainPage().menu().menuLink("Форум").click();
        onMainPage().menu().menuLink("Сообщество").click();
        onMainPage().menu().menuLink("English").click(); 

При этом если между ними поставить задержку хотя бы 0,5 секунды между каждым — все отрабатывает нормально. Вот и хочу учтонить — это я где-то что-то не учел или это баг?
Прошу прощения, оказывается проблема где-то в ChromeDriver, на FirefoxDriver все нормально отрабатывает.
Привет! для .NET такого пока нет?
Видимо можно просто повторить какие-то идеи в своих тестах.

Считается, что литералы вообще и локаторы (индентификаторы контролов) не должны повторяться. А эта идея с одним input и локаторами в виде параметров заставляет их повторять получается?
Считается, что литералы вообще и локаторы (индентификаторы контролов) не должны повторяться.
Кем считается? О_О

Из моей практики, параметризованные локаторы очень нужны для обработки ситуаций, где у вас есть повторяющиеся элементы.
Примером этого может служить список файлов, где каждый элемент идентифицируется двумя признаками — идентификатором контрола файла и конкретным именем файла.
@FindBy("//[id='file' and text='{{ fileName }}']")

Если вы будете прописывать каждый возможный файл в виде отдельного элемента без параметризации — тесты станут более сложно поддерживаемыми.
Кроме того, в некоторых тестах есть смысл генерить литералы случайным образом и здесь параметризация выручает еще сильнее.
Опять же в моей практике встречалась ситуация, где мне требовалось подтянуть часть локатора из какого то внешнего источника — например, имя пользователя на странице приветствия.

На мой взгляд, автор загнался и начал унифицировать ту часть Page Object, которую унифицировать не стоило бы.
P.S.: Я не настоящий автоматизатор, так, погулять вышел, ногами сильно не бейте. Atlas не использовал, использовал более старые HtmlElements 2.0 от того же автора.
Привет!
Кем считается? О_О


Ну вот принцип «Не повтряйтесь» — если использовать один локатор несколько раз, то и менять его придется в нескольких местах.
Позитивных сторон описывать не буду. В любом фреймворке есть смысл, если он упрощает написание тестов. При этом условия применения фреймворков разные, и то, что подходить в одних условиях, совершенно не подходит в иных. Опишу лишь то, что мне не понравилось. Сразу оговорюсь, что автоматизировать и поддерживать приходиться тесты, которые гоняются на множестве однотипных, но немного разных сайтах с разной локализацией которые (иногда) пишутся разными командами разработчиков. И да — разработка идет паралелльно с автоматизацией.
Первое: IMHO параметризируемые элементы в том виде, как они описаны в статье — шаг назад в плане разделения UI слоя от логики страницы. Описанный пример хорош только при определенном построении нелокализуемых приложений. При изменении юзер-интерфейса и/или структуры DOM придется не только менять локаторы, но и бегать по методам в поисках строк типа «Сохранить» если девелоперы решили ее вдруг переделать с button на div. Рефакторинг тут мало поможет. Плюс при наборе кода тестов никакого intellisense-a и надо очень внимательно следить за тем, чтобы не ошибиться в названии кнопки и не напечатать при очередном использовании «Сreate» вместо «Create» (это не ошибка и слова здесь разные). Так что как по мне удобнее потратить лишних 2 минуты на копиаст элементов, но зато саппорт кода и использование этих классов будет в разы удобнее.
Второе: Интерфейсы вместо классов для множественного наследования — тоже не лучший вариант. Я, например, предпочитаю композицию, которая в том числе повысит читаемость тестов и избавит от проблем с одинаковыми именами методов в интерфейсах. А дефолтую имплементацию в интерфейсах оставить для того, для чего они, собственно, и предназначены — обеспечения обратной совместимости библиотек. К примеру, в случае с композицией на гитхабе (у него если линка Issues как в хеадере, так и на странице с проектом):
projectPage.header().openIssues()
projectPage.repoNavigator().openIssues()

В подходе с наследованием в джаве придется уже по-разному именовать методы, что уже неудобно и налагает определенные ограничения когда тесты автоматизирует не 1 человек, а команда. Либо делать как в статье — подключать Header через дополнительный «служебный» интерфейс WithHeader. Но зачем мне еще +1 класс вместо одной строки кода? К тому же страница «состоит из» Header-а, а не «является» WithHeader-ом. (А описанное в начале статьи дублирование селектора для полей типа Header решается просто размещением дефолтного селектора на классе Header)

И еще одно замечание к пункту «При описании стандартных PageObject используются интерфейсы вместо классов.» PageObject — это НЕ набор описаний UI элементов через FindBy. FindBy — это все к шаблну PageFactory. PageObject — это подход к написанию классов таким образом, чтобы его паблик методы описывали то, ЧТО делается на страничке (сервиса, которые предоставляет страница)ь и никаким образом не раскрывали внутренностей реализации (КАК это делается). PageObject можно сделать вообще на «голом» webDriver:
PageObject без PageFactory:
class  MyPage extends BasePage{
  public void login(Credentials creds) {
    wd.findElement(By.name('user')).sendKeys(creds.user);
    ...
  }
} 

PageFactory без PageObject:
class  MyPage extends BasePage{
  @FindBy(css='.name')
  WebElement user;

  public WebElement getUser() { return user;}
} 


Кому интересно, здесь Саймон описывает разницу (смотреть с 17:00): youtu.be/y1pSkqIJvAw?t=1021
Зарегистрируйтесь на Хабре, чтобы оставить комментарий