Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

Page component object паттерн - проблема с разделением логики для авторизованных и неавторизованных юзеров

page-factory
page-object
selenium
Теги: #<Tag:0x00007f7b6508b848> #<Tag:0x00007f7b6508b690> #<Tag:0x00007f7b6508b4b0>

(Tatyana Durova) #1

Cтрою страницы из блоков, обычным объявлением агрегируемых страниц в виде полей. Переходы межу страницами идут иногда через обобщенные страницы (делегированием), иногда напрямую; методы страниц возвращают либо себя, либо другую страницу (Flow).

Но как быть с незначительными отличиями некоторых страниц/блоков (и в тоже время полной идентичностью некоторых других страниц) для авторизованных и неавторизованных пользователях? Пыталась применить паттерн Стратегия, но так и не поняла куда девать класс контекст и как его использовать, ну и в самих алгоритмах для авторизованного и неавторизованного юзера мне не понятно что написать. Сейчас у меня обычные if/else и метод IsLogged() (+ методы GoToLogin(AccountData accountData), GoToLogout), а также по-сути некоторые блоки имеют замену для авторизованных пользователей (например есть HeaderForLoggedUser с кнопками “Logout”, “Cart”, “My Profile” и его аналог HeaderForUnLoggedUser, у которого есть только кнопка “Sign in” ). + мне надо как то написать тест на проверку разной цены на странице выдачи товаров и на странице товара для пользователей разных категорий и совсем неавторизованных.

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

Как это все лучше реализовать? Пыталась переделать пример с наследованием от AuthTestBase или просто TestBase, не помогло, потом начала смотреть в сторону двух разных предков AuthPageBase и PageBase, но как тоже тупик. Вариант с множеством классов для страниц кажется все равно проще…

Не могу понять, надо ли делать какое то статичное поле с данными аккаунта пользователя, под которым я зашла, и нужно ли периодически проверять, что я залогинена именно под данным “Васей”, а не просто под кем-нибудь? Как это реализуется, если я все-таки решу делать нечто подобное? Каждой странице-блоку добавить свойство с данными аккаунта + метод, проверяющий под каким пользователем я авторизованна и делающим перелогин под нужным мне, если залогинена не под тем?


(Keiga) #2

А почему нельзя сделать универсальные классы для страниц\элементов независимо от того авторизован пользователь или нет. При этом в тесте уже в зависимости от требований проверять, что один элемент должен отображаться, а другой не должен.


#3

Keiga правильно пишет, логику лучше выносить в сами тестовые сценарии, а страничные объекты и блоки должны быть просто универсальными точками доступа.
Добавлю, что чем сложнее делаем блочную модель, тем больше потом переделывать при изменении логики и требований. Сам уже проходил через эту боль, не советую.
Связка должна быть однонаправленной: [Требования] -> [Тест-дизайн] -> [Автоматизация]. Если накидывать в эту схему еще дополнительную связь от требований к автоматизации, запутаться легко.


(Tatyana Durova) #4

Можете конректней расписать, я не совсем понимаю… то есть описать один блок Хидер, а дальше в тесте выполнить логин и проверить, что у хидера есть кнопка “в корзину”? А потом выполнить логаут, и проверить, что этой кнопки нет, зато есть кнопка sign in?

И все это делать через агрегацию или без разницы, можно и так?


(Tatyana Durova) #5

Распишите плз подробнее про связку [Требования] -> [Тест-дизайн] -> [Автоматизация], что-то не совсем доходит…


(Keiga) #6

Да, все правильно. Вы пишете универсальную структуру по работу с вашим приложением. Расписываете страницы, элементы, потом в тестах уже проверяете то что нужно. Как реализуете, это уже на Ваше усмотрение, лишь бы было удобно.

Проще всего по схеме: элемент\страница -> шаги для работы с элементом\страницей -> тесты использующие шаги которые используют элементы.

В данном случае если надо проверить что для авторизованного пользователя не отображаться какие-то ссылки. То вы делаете в тесте log in и проверяете что одни элементы отображаются, а другие нет.


#7

Ну, допустим, на сайте automated-testing.info у нас есть кнопка “Войти и ответить” внизу темы, если пользователь не залогинен. Если залогинен, есть кнопка “Ответить” и еще несколько других.

Мы исходим из требований и пишем тесты, которые проверяют доступность кнопок для авторизованной и неавторизованной сессии. Логика работы кнопок нам важна только в этих тест-кейсах. Далее будут функциональные тесты на другие части, которые подразумевают, что эти кнопки есть или нет, потому что предыдущими кейсами мы это проверили, и просто их используют.

То есть, логика доступности кнопок присутсвует в тест-кейсах, но она не нужна в страничных блоках. Страничный блок универсальный и позволяет проверить наличие или нажать любую из кнопок, даже если ее там нет.

Теперь представим, что для неавторизованного пользователя разрешили кнопку “Пожаловаться”, или вообще добавили совершенно новую кнопку, доступную всем. Если логика зашита в тестах, мы обновляем только тестовые сценарии, а новую кнопку добавляем в только один блок. Если же у нас еще и блоки задизайнены как разные классы в зависимости от авторизации, надо менять как минимум 2 блока, или разбираться с наследованием. А если к этому прикручена еще какая-то логика в других классах, то на изменения может уйти много времени.

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


(Tatyana Durova) #8

А вариант, когда в шагах страниц у меня возвращаются другие страницы совсем плох? Чем это может грозить? Ну когда после шага логин из блока “HeaderforUnloggedUsers” я автоматом получаю headerForLoggedusers?


(Tatyana Durova) #9

так чтобы такую логику туда не впиливать, получается самый простой вариант это то, как я и пытаюсь делать - считать эти различные виды разными классами и в некоторых тестах проверять, что у меня после действий с объектом одного класса стал видим объект второго класса, а если тест не касается этого блока, то просто не использовать объекты ни первого, ни второго класса. Или это не самый удобный для дальнейшего использования путь и вскоре я пойму, что надо все было делать по-другому… ?! =(


(Keiga) #10

Если это Вас не путает, то ничем, напротив можно строить красивые цепочки методов :grinning: Но вообще в ситуациях где результат действия не однозначный лучше ничего не возвращать, т.к. в указанный logIn можно передать не правильные данные пользователя(или авторизация может не работать) и тогда после не успешной авторизации все равно мы останемся на HeaderforUnloggedUsers. А в самом лучшем случае как уже писали выше, просто сделать Header и там хранить весь блок независимо Logged у вас юзер или нет.


(Keiga) #11

Вам так хочется расписывать все страницы по два раза? Для тех кто авторизован и для тех кто нет? Это же напрасное дублирование кода.


(Tatyana Durova) #12

даже саму себя? почему? хм…немного страшно так делать, будет потом путаница какая-нибудь… …

Ну если данные для логина не правильные, то тест упадет, а смоук тест на логин бует зеленый. Если уже и смоук тест упадет, то временно выключу/вставлю заглушку в шаг, который делает логин…?!


(Keiga) #13

Себя можно. Если Вы точно знаете что возвращаете правильный класс, то это только упростит работу.

Все что я советую Вам, это не клонировать классы\страницы в зависимости от факта авторизации, это плохо и не универсально.


(Tatyana Durova) #14

не все, только те, которые имеют отличия.

Я просто не вижу другого варианта, я же писала, что пыталась применить паттерн стратегия и как то не дошло, как нормально это сделать (хотя вроде сам конфепт паттерна поняла нормально). Просто так мне еще хуже все кажется, поэтому и задала вопрос, расписывать все по два раза тоже не нравиться, хотелось бы одним параметров решать, но я даже не знаю где этот параметр юзать…в тестах самих его использовать или в фабрике страниц или при их инвоках?


(Keiga) #15

Я видимо не совсем понимаю суть. Параметром чего? Вы пишите тест, вся логика в тесте. Вот пользователь авторизовался (или нет), все равно на странице это один блок header с одним локатором, просто с разным содержанием. Зачем его в проекте делить на два разных класса?


(vmaximv) #16

Я думаю диалог будет гораздо продуктивнее, если вы будете “говорить” хотя бы абстрактным псевдо-кодом.


(Tatyana Durova) #17

Ну параметром в тесте, или даже разделить все тесты на AuthTestBase и без авторизации TestBase, или при подходе flows и pageobject особой пользы от этого нет…?!

Еще есть страх, что при агрегации страниц в большие я какие то блоки перепутаю или вообще пропущу. + есть страх что в итоге у меня даже меню будет сделано агрегацией нескольких страничек menuItem1 и тд, или не надо этого бояться и это допустимо?


#18

Flow objects - красивый подход. Но надо не путать это в один клубок, а отделить flow как метод построения фреймворка, и логику тестирования. Ведь верификацию лучше делать по фактическому наличию элементов, а не по возвращаемому классу. И блок не обязательно должен содержать только методы, которые всегда будут работать; часть может работать в один момент, а часть - в другой, как нужно тесту.

Дело в том, что авторизация может оказаться далеко не единственным параметром, из-за которого меняется интерфейс. Что же, делать разные классы на каждый такой параметр?
Если в одном классе будут методы для авторизованных и неавторизованных юзеров - это не страшно. А если они поделены по разным классам - это может показаться удобно вначале, но удобно ли будет поддерживать? Впрочем, допускаю, что это зависит от индивидуального склада ума :smile:

А не надо очень большие, пусть будут узкотематические блоки, иерархически объединенные в страницу.

Просто не видя страницу, сложно сказать - может быть, у Вас блок для авторизованного и не авторизованного юзера - это совершенно разные блоки с разной структурой, тогда действительно не стоит их объединять. А проверку наличия и возврат блока нужного класса оформить как метод страницы/блока уровнем выше.
Тогда, допустим, методы getHeaderForUser() и getHeaderForGuest(), будут расположены рядом и одновременно доступны, а уже тест будет решать, что вызвать.


(Tatyana Durova) #19

у меня несколько блоков, похожих по структуре, но имеющие различия либо в данных (цена оптовая/обычная) + отсутствие/наличие каких то элементов, казалось бы в одинаковых блоков(поэтому вот думаю делать их вообще отдельными) + отсутвие/наличие каких то блоков.

Сайт cutwise.com (он весь состоит из блоков и подблоков, то видимых, то нет)


(Oleksandr Khotemskyi) #20

Если очень хочется разделять страницу для залогиненых и не залогиненых пользователей можно унаследовать 2 версии страницы от одного общего интерфейса (или абстрактного класса, или даже обычного класса), и объявить все функции там. Потом имплементировать этот интерфейс для обеих страниц, и те методы которые не применимы для страницы - бросать Exception с сообщением - например -

   throw new Exception("Method applicable only when user logged in. Check that login was successful");

Ну и работать потом уже с интерфейсом.

Я так понимаю проект на Java? Какая Java на проекте используется? если 8 - то рекомендую глянуть на Interface Default Methods:


Можно по умолчанию бросать исключения там, тогда в классах которые имплементируют этот интерфейс - реализовывать только те функции которые применимы к этой странице (залогинен, не залогинен)

Но мое мнение - лучше не дробить на несколько страниц, а держать всю логику в одном классе и для залогиненых и для незалогиненых. Вы сами должны вызывать правильные методы в правильном месте.
Если класс становится слишком большой - используйте готовый велосипед от Яндекса - http://habrahabr.ru/company/yandex/blog/158787/ - это то что вы назвали Page Component. Эта ссылка может быть устаревшей, вот гитхаб проект - https://github.com/yandex-qatools/htmlelements

А насчет контекста - его можно хранить как static переменную и обновлять при определенных действиях (пользователь залогинился, пользователь вылогинился и т.д.)