Здравствуйте.
Данное сообщение, хоть и является вопросом, который основан на моём неведении, возможно, будет полезен кому-то из новичков как и я.
Текста будет много ( я не лаконичен ), потому, буду благодарен человеку, который всё-таки уделит время.
Обращаюсь к людям, которые имеют практику в реальных проектах. К сожалению, все отказываются принять стажёром,чтобы укрепить свои знания и понять грамотную реализацию qa на исходнках в боевых условиях
Проблема в том, что недавно начал изучать selenium и pytest и наткнулся на такое понятие как Page Object. Без осознания как устроен данный паттерн, я не могу продолжить своё обучения.
Не из-за того, что оно нужно для изучения selenium, а из-за того, что это основа, на которой всё должно быть устроено у человека, который хочет чтобы его труд не был напрасен.
Я попробую реализовать данный паттерн на pytest и selenium и столкнулся с проблемами. Но прежде, буду благодарен за проверку моего понимания данного паттерна:
- У нас есть базовый класс, который имеет только одну функцию: функцию инициализации
__init__
или конструктор, если мы говорим о другом языкеC++, JAVA, C#
. Данный метод класса привязывает драйвер браузера к переменной втурни него.
- Page Object - это представление одной и только одной страницы в виде потомка базового класса.
- При переходе на другую страницу, в рамках одного теста, мы передаём наш драйвер в качестве аргумента в Page Object соответствующей страницы и работает дальше с ним.
- Каждый Page Object имеет методы, которые описывают его работу со страницей, которую он представляет и локаторы элементов,которые на ней находятся. Ничего больше.
С какими проблемами или вилками я столкнулся из-за того, что реализации данного паттерна отличаются от курса к курсу и в различных видео гайдах на youtube.
Прошу сказать насколько это актуально.
- Класс браузера. Я его объявил в качестве фикстуры для того, чтобы он был доступен везде, без
import
. Добавляю его в conftest черезpytest_plugins = ['Driver']
Почему так ? Главный conftest не засоряется и вся работа с драйвером браузера в одном файле - Page Object страницы. Внутри него, как я писал выше, методы работы внутри конкретной страницы.
Пример:
def open(self):
#self.driver.maximize_window()
self.driver.get('http://google.com')
assert self.driver.title == 'Google', 'Page not found?
- Локаторы и создание элемента.
!Это самый проблемный вопрос!
Как они реализованы у меня: Есть класс базовых элементов. Вот определение и небольшой кусочек
BaseElement.py
class BaseElement:
def __init__(self,driver,locator):
self.driver = driver
self.locator = locator
self.web_element = None
self.find()
def find(self):
element = WebDriverWait(self.driver,10).until(
EC.visibility_of_element_located(locator=self.locator))
self.web_element = element
return None
def send_keys(self,value):
self.web_element.send_keys(value)
return None
Как инициализирую элемент интерфейса внутри Page Object конкретной страницы:
@property
def searchField(self):
locator = find(By.CSS_SELECTOR, 'input[name="q"]')
return BaseElement(self.driver, locator=locator)
@property для того, чтобы убрать необходимость в ()
Пример:
с @propety: main_page.searchField.send_keys('ny times')
без @property: main_page.searchField().send_keys('ny times')
также есть файл
Find.py
from collections import namedtuple
find = namedtuple('find',['by','value'])
Для того, чтобы аргументы были
find(By.CSS_SELECTOR, 'input[name="q"]')
a не
find(by=By.CSS_SELECTOR,value = 'input[name="q"]')
- +В чём я вижу плюсы данного метода:
3.1. Все методы по работе с элементом внутри одного класса.
3.2. Легко объявляются и проверяются при создании на отображение на странице.
-!Минусы и вопросы! :
3.3 Принимает только 1 элемент. со списками не работает. Стоит ли рассматривать реализацию решения этой проблемы , как это сделано в webium ? Созданием класса-потомка от BaseElement с переопределенной функцией поиска ?
3.4 Т.к определение элементов находятся вместе с методами, то наш Page Object страницы уверено растёт. Надо ли выводить все локаторы в отдельный файл, потом передавать их как аргумент при создании элемента? Как мне кажется это глупо. Придётся работать с двумя файлами отдельно. Также, если они указаны внутри Page Object страницы, мы сразу видим и определение и локатор
Правильно ли всё это ?
webium - пример реализации паттерна от wargaming. Если подумать, он хорош. Но в нём, при создании элемента, не проверяется его наличие на странице. Не проблема, можно добавить. Но получаем другую проблему:
Т.к мы хотим дать всем элементам, которые нам нужны - свои элементы класса BaseElement. Мы никогда не сможем получить title страницы вышеописанными методами.
Пример:
MainPage.py
@property
def title(self):
locator = find(By.CSS_SELECTOR, 'input[name="q"]')
return BaseElement(self.driver, locator=locator)
def isAt(self):
assert self.title.text == '- Поиск в Google'
test_mainpage.py
class mainpageTests(object):
def test_main_page_loaded(self,Driver):
main_page = MainPage(Driver)
main_page.open()
main_page.isAt()
Т.к элемент title
будет ожидаться через EC.visibility_of_element_located
,
который мы указали в методе BaseElement
,который ищет элемент на странице, он никогда не будет найден.
Можно решить через:
def isAt(self):
assert self.driver.title == 'Google'
Но насколько это правильно ? Ведь мы ушли от нашего правила элемент на странице = экземпляр класса BaseElement
Можно, конечно, добавить 3й аргумент, который будет указывать тип элемента. который мы ищем.
И в методе BaseElement
,который ищет элемент на странице, через if/else указывать нужный нам expected_conditions
для данного элемента, в случае, если он вообще нам нужен.
Это наверное самые беспокоящие меня проблемы, которые не дают мне покоя и возможности понять данный паттерн.
Не исключаю, что я слишком парюсь о чем-то. Но так я устроен, что я не могу считать, что я понимаю что-то , пока не реализую это в виде, пригодном для использования. Можно сделать и так и сяк, но как же всё таки правильно ?
Буду действительно благодарен человеку, который подскажет ,скажем так “Best practice”, правильно ли я вижу паттерн и его реализацию и поможет с пониманием моих проблем в любом удобном для Вас формате. ( устный, письменный литературный, либо готовый шаблон или просто куски кода на любом языке ).
С уважением, Дмитрий.