Подскажите примеры использавание Pattern Page Object?

webdriver
java
locators
page-factory
page-object
framework
Теги: #<Tag:0x00007fedb9701260> #<Tag:0x00007fedb9701120> #<Tag:0x00007fedb9700fe0> #<Tag:0x00007fedb9700ea0> #<Tag:0x00007fedb9700d60> #<Tag:0x00007fedb9700c20>

#1

Подскажите парочку или один хороший пример использования Selenium+Java+Pattern Page Object.
Как пример написание тестов!
1. Как работать с динамическими локаторами? как можно вынести в @FindBy?
2. Где должны распологаться assert - в классе теста или в классе где происходит сама реализация теста!
Зарание все спасибо!


А идентификация элементов в Selenide используется "на месте"?
(asolntsev) #2

Вот пример: https://github.com/selenide-examples/hangman/tree/master/test/uitest/selenide_page_objects

  1. Локаторы не надо держать в полях. Для операций с элементами используйте методы.
  2. Assert может быть реализован в методе пиджак обжекта, но вызывать его надо из теста. Т.е. только тест может принять решение о том, что вот сейчас настал момент проверить то-то. Но как именно это проверить, знает пэдж обжект.

(Sergey Korol) #3

Предвкушаю холивар. smile Весьма сомнительные советы.

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

Чем пейджа будет отличаться от теста, если весь verification logic будет в нее заложен? Тест проверяет конкретные вещи. К примеру, кнопочка на 2й формочке в 5й табличке, 10й строке и 24м столбце должна быть enabled. Другому тесту, к примеру, надо проверить текст лейблы из 3й формы, 1й таблицы, 8й строки, 11го столбца. Будете хардкодить ассерт под каждый локальный случай? Понадобится ли другим тестам сей ассерт? Очень маловероятно. Т.е. вы начнете в итоге выдумывать более generic варианты. Насколько это вообще реально при подобном разбросе самих типов проверок и длине пути поиска элементов? Потом вы наверняка заметите, что некоторые generic asserts одной пейджи очень похожи или вовсе одинаковы в сравнении с другой... Дальше продолжать?


#4

Посмотрел ваш пример. Спасибо! но хочется на Selenium!

Уважаемый @ArtOfLife! Вы как уже професиональный автоматизатор , нужна ваша помощь и советы.
Мной был написан тест допустим(Test.class - PageObject.class).
В Test.class я реализовал много тестов и саму логику работы тествов. В PageObject.class реализовал реализацию (действия которые происходят на странице и assert) + вынесены элементы страницы с помощью @Findby.
В конечном итоге мне сказали что я не правильно сделал! Что нужно в PageObject.class только выносить элементы на странице. А в самом Test.class реализация действий и assert!
Правильно это или нет, на ваше усмотрение?
Присутствует очень монго динамических локаторов(где нужно что то подсчитать элементы или открыть вторую ссылку или третию), как их можно вынести?
Если я выношу так @Findby(xpass=".//*[@id='news_container']/div[ + i + ]/div[2]/div[2]/a[3]") , public WebElement linkNews; и подставлю в цыкл linkNews, ничего не работает,(так как он понимает что это один элемент).
Нуждаюся в примере хорошем, как правильно писать тесты. Так как только учуся, лутше сразу писать правильно!


(Sergey Korol) #5

Не хотелось бы в который раз обсуждать, что из себя представляет PageObject. На форуме было достаточно внятных ответов. Достаточно лишь воспользоваться поиском. wink

Стесняюсь спросить, а в каком собственно виде будут представлены эти действия? Посредством какой языковой конструкции они будут объявлены? Если вам кто-то что-то доказывает, просите вначале привести аргументы, почему так делать правильно. На заборе тоже многое могут написать...

Тут нужно смотреть глубже, что из себя представляет данная динамическая конструкция. Возможно, уже существует какой-то готовый элемент (который можно чуток проапгрейдить) для покрытия ваших кейсов? В любом случае selenium из коробки не предоставляет средств работы с динамическими локаторами. Так что без кастомизации не обойдетесь. Либо спускайтесь на уровень PageElement, либо пишите обертки, умеющие работать с динамическими локаторами. Но в вашем случае главной проблемой является то, что вы используете готовую фабрику, которая не предусматривает подобных кейсов. Не уверен, что для новичка есть какой-то очень простой вариант решения.

Вообще говоря, сама идеология динамических локаторов как бы намекает, что изначально мы не знаем их конечного состояния, не говоря уже о моменте времени, когда тот или иной элемент нам может понадобиться. Посему, при данной постановке задачи наиболее подходящей конструкцией будет не чистый FindBy + WebElement, а By, который проповедует религию отложенного поиска.


(sidelnikovmike) #6

Минутка саморекламы. Попробуйте поставить плагин, там можно по дефолту создать проект с просто селениум или с selenide. Там же уже есть примеры, может чем-то вам помогут. На сайте есть описание тестовых проектов, если захочется большего понимания, что в них происходит.


#7

Спасибо, обязательно попробую)


(asolntsev) #8

Так вопрос был - что делать с динамическими локаторами? Их в принципе невозможно держать в полях. Тут просто нет других вариантов.

Ну так это ж не я придумал, это концепция пэдж обжектов: он в принципе существует для того, чтобы скрывать от внешнего мира детали взаимодействия с элементами страницы. Золотое правило: в тесте не должно быть селекторов. Поэтому я просто не вижу, как ещё можно делать по-другому. Тест говорит, что нужно проверить, а пэдж обжект знает, как это проверить. Это по определению единственное правильное использование пэдж обжектов.

Если вам кажется, что в таком виде пэдж обжекты не очень-то удобны, то тут я полностью согласен. Поэтому я и отношусь к этой концепции скептически.


(sidelnikovmike) #9

тут видимо вопрос в следующем - как выглядит тест?
либо это:

assertTrue(loginPage.getButtonsCount() == 3)

или

loginPage.checkButtonsCount()

который уже внутри будет делать любую проверку.

Я лично чаще пользуюсь вторым вариантом. То есть кажется, что оба они имею право на жизнь.


(Sergey Korol) #10

Из коробки - может и нет. Но никто ведь не мешает нам создать собственный элемент с кастомной анотацией для поддержки динамических локаторов...

Есть как минимум 2 варианта:

  • Геттерами возвращать значения / состояния филдов в тест для последующей обработки ассертом. К примеру:

// PageObject
public String getSomeElementText() {
    return getText(locator1); // locator1.getText();
}

public String getOtherElementState() {
    return isSelected(locator2); // locator2.isSelected();
}

// Test
assertEquals(page.getSomeElementText(), "expected");
assertTrue(page.getOtherElementState());
  • Создавать generic verifications. При этом, естественно придется подумать, как мапить локаторы с некими константами, дабы не нарушать принципов инкапсуляции.

verifyTextEquals(page, elementRef, expectedResult) {
    assertEquals(page.getText(elementRef), expectedResult);
}

Да ну бросьте... PageObject - это модель реальной вэб-страницы. С каких пор в саму модель должна закладываться логика проверки ее состояния? Исходя из такой постановки, зачем нам вообще нужны те же юнит тесты? Давайте делать ассерты прямо в методах классов бизнес логики приложения! Давайте пихать ассерты в DB и все прочие сущности! Ааа, это не по-феншую? Так чем же пейдж обджекты хуже?

Данный пример прекрасно отражает суть описанного выше. Реальная ситуация:

Первый тест идет по определенному сценарию -> вы что-то выбрали из дропдауна, у вас появилось 3 кнопочки. Вы ассертите, что их 3.

Второй тест идет по другому сценарию -> вы выбрали другую опцию из дропдауна, теперь кнопочки у вас всего 2 на форме. Т.е. вам надо ассертить, что их 2.

Внимание вопрос... вы правда считаете, что сей вариант:

// Page
public int getButtonsCount() {
    return getElementsAmount(locator);
}

// Test 1
assertTrue(page.getButtonsCount() == 3);

// Test 2
assertTrue(page.getButtonsCount() == 2);

равносилен этому?

// Page
public void getButtonsCountForFirstCase() {
    assertTrue(getElementsAmount(locator) == 3);
}

public void getButtonsCountForSecondCase() {
    assertTrue(getElementsAmount(locator) == 2);
}

// Test 1
assertTrue(page.getButtonsCountForFirstCase());

// Test 2
assertTrue(page.getButtonsCountForSecondCase());

С чего вдруг модель нашей пейджи должна захламляться кучей дубликатов с magic numbers и прочими константами на борту?


(asolntsev) #11

Обязательно должна! Подчёркиваю, пэдж обжект должен знать, КАК проверить, но не ЧТО проверить.
Простой пример. Есть страница счетов, и тест хочет проверить, что первый счёт помечен как "просроченный". Сейчас для этого нужно проверить, что в строке есть слово "просроченный". Но завтра дизайн поменяется, и текста больше не будет, но рядом со счётом будет картинка с красным шариком. По задумке, тест не должен поменяться, должен поменяться только пэдж обжект.

Поэтому в пэдж-обжекте должен быть метод для проверки типа:

page.assertInvoiceIsExpired(invoiсeId)

Конечно, я имел в виду не это. А что-то типа такого:

public void assertButtonsCount(int expectedCount) {...}

Таким образом в пэдж-обжектах не будет дупликатом и магических чисел.


(sidelnikovmike) #12

Да, про кол-во кнопок - разумеется можно сделать метод параметризуемым, тут я согласен с Андреем.


(Sergey Korol) #13

Если дизайн поменялся, и вы физически глазами ищите уже не текст (getText), а воздушный шарик (isPresent), то с чего бы вдруг тест должен оставаться прежним? Логика страницы поменялась, значит и логика самой проверки должна измениться, ибо "читать текст" и "искать шарик" - это 2 концептуально разные проверки. Если вы не измените тест, то как потом кто-либо, включая вас, поймет, ЧТО он тестирует? В тестировании следует придерживаться одной очень важной особенности - be more specific in details. Вы когда будете баг репорт заводить на данный зафейлившийся тест, как думаете, что будет понятней:

  • verify that invoice is expired
  • verify that expiration baloon is present next to invoice

В первом случае, я бы сразу полез в базу, и увидев, что все ок, отмаркировал бы баг, как cannot reproduce. Я бы даже не догадался посмотреть на шарик. А все потому, что верификейшен достаточно размытый и из контекста не ясно, чей это баг - frontent или backend. Что искать, где искать?

Вы когда-нибудь писали ручные тесты? Там нет пейдж обджектов. У вас есть только тест, отражающий процесс проверки бизнес логики приложения. Если она поменялась, но при этом вы не изменили сам тест, то ЧТО вы в итоге будете тестировать во время регрессии? Функционал, которого уже нет? Это концептуальный вопрос. PageObject предоставляет нам возможность осуществить высокоуровневые операции для того, чтобы добраться до места самого verification, а так же - получить актуальное состояние проверяемых компонентов. Его задача - предоставить actual result (ровно также, как мы можем его лицезреть на странице), но логика проверки актуального результата при этом лежит на плечах самого теста, "знающего" некий expected result.


(asolntsev) #14

Именн так. Я хочу увидеть в отчёте, что счёт больше не помечается как просроченный. Это главное "business value". Что именно это значит, может увидеть, провалившись в детали этого теста. Там будет видно, по каким именно признакам сделали такой вывод (по тексту, картинке или как-то ещё).

Это очень странная логика. Когда я вижу в Jenkins красный тест, ни в какую базу я не лезу. Я смотрю в этот самый Jenkins, открываю отчёт о прохождении теста, и там чёрным по белому написано, какая именно картинка ожидалась. И скриншот тут же.


(asolntsev) #15

Ой-ой-ой! Это очень плохо. Это как раз то, о чём я говорю на конференциях.
Не надо использовать пэдж обжекты, чтобы прокликать все предыдущие страницы и добраться до нужного места. Это приводит к медленным и нестабильным тестам. На нужную страницу надо попадать сразу, ничего не прокликивая. Например, используя прямой линк (который, конечно, работает только в тестовом режиме): http://idemo.bspb.ru/security/fakeLogin?username=demo&url=/payments/history - или используя модные нынче Rest API или что угодно ещё.

А вот пэдж обжекты нужны, чтобы инкапсулировать (т.е. скрывать!) специфику работы с элементами страницы. Это же их определение, ёлки.


(Sergey Korol) #16

Это high-level business requirement, которое представляет ценность только для product owner'а. Но весьма бесполезно в качестве verification степа, ручного воспроизведения и для создания баг репорта. Я бы посмотрел, как бы вы писали верификейшены для медицинских проектов. wink

Вы опять смотрите на вещи со своей колокольни девелопера: сам нашел, сам проанализировал, сам пофиксил. Давайте смотреть на вещи более реально, когда в большинстве случаев QA / QAA сами не фиксят баги. Т.е. их еще нужно завести в трекер для начала. Так вот, если вы напишите столь неочевидный expectaition в баг-репорте, девелоперы (особенно если они из экзотических стран) могут совсем не понять, какой же сакральный смысл вы вкладывали в столь обобщенное определение. Уж не говоря о регрессии, когда вы через месяц будете пытаться вспомнить, что же имелось ввиду. Ну и зачем мне в таком случае в тесте нужны эти обобщения, если от них пользы 0? Команде нужны четкие детали, а не абстрактные фразочки из user story.

А каким образом кол-во степов влияет на стабильность выполнения тестов? Нестабильные тесты появляются либо из-за неопытности "писателей", либо из-за багов используемых инструментов / приложения в целом.

На следующей конференции будьте добры рассказать о том, как бы вы решали задачу быстрого доступа к какой-либо странице, в случае наличия csrf tokens, двухфакторной аутентификации, динамически формируемых URL, кастомных браузерных плагинов / экстеншенов, реальных устройств по типу smart-cards / HSM и т.п. Я уже не первый раз задаю вам этот вопрос. Но вы почему-то постоянно уклоняетесь от ответа. wink

Правильно, только как это связано с верификейшенами? Спрошу еще раз: вы юнит тесты встраиваете прямо в бизнес модель? Думаю нет. Так чем PageObject отличается от любой другой сущности приложения? Почему вы там не используете ассерты, а тут вдруг все, обязательно надо впихнуть.


(asolntsev) #17

Вы тут мешаете разные вещи.
Вы говорите о том, что отчёт об ошибке должен давать максимально подробные детали - что ожидали, что получили, куда пытались кликнуть, чего не нашли и т.д. Согласен. Бесспорно, тест при своём падении должен максимально чётко всё сказать.

Но для этого вовсе не обязательно писать пэдж обжекты именно таким образом. Для этого вообще не обязательно писать пэдж обжекты. Это просто разные вещи. Можно сделать очень хороший отчёт как с пэдж обжектами, так и без них. И можно сделать очень плохой отчёт как с пэдж обжектами, так и без них.

Пэдж обжекты задуманы не для подробных отчётов. А для скрытия деталей взаимодействия с элементами страницы. Это их определение.

P.S. Количество степов самым прямым образом влияет на стабильность тестов. Чем больше шагов, тем больше шансов, что тест упадёт на каком-нибудь "предварительном" шаге (из-за аякса, javascript, тормознутого браузера или багов-полубагов на других страницах). Запускаешь тест для "страницы X", ждёшь полчаса, смотришь - а он упал уже на логине. Думаешь, да, логин у нас иногда падает. Команда страницы логина всё обещает разобраться, да всё никак. Ладно, запускаешь тест ещё раз, ждёшь ещё полчаса - о, теперь зелёный. Хорошо-то как! smile

P.P.S. Я не уклоняюсь от ответа, я с удовольствием расскажу про часть этих вещей. Решается просто, единственная (непреодолимая для многих) сложность - необходимость взаимодействовать с разработчиками. А часть проблем я просто не понимаю - например, кастомные плагины. Не понимаю, как они могут мешать попасть на нужную страницу. С реальными устройствами типа smart cards сложнее, но тут и пэдж обжекты точно так же не помогут. В общем, хорошая тема, надо её развить отдельно.


(Sergey Korol) #18

Простой пример - плагин для подписи / аутентификации / отправки платежей. Он должен быть физически установлен в системе / браузере. При его отсутствии вы просто не сможете дойти даже до степа подписи. При его наличии, вы должны осуществить нетривиальное взаимодействие клиент -> плагин -> сервер -> плагин -> устройство / эмулятор -> сервер. Сама роль плагина в этой схеме - получить SHA1 fingerprint от устройства или из фейковых сертификатов, отдать PKCS сигнатуру по SHA1 и challenge key, присланному от backend. При этом, для осуществления любого запроса вам также понадобятся x-csrf token / session id и куки, сгенеренные на предыдущих шагах, иначе просто получите 401. А если еще учесть тот факт, что URL формируются динамически из предыдущих шагов, то никакой REST, прямой редирект или тестовые режимы вам не помогут обойти эту связку. Самое забавное то, что это невозможно покрыть никакими unit / component integration tests.