При наличии класса с локаторами, где хранить логику работы с ними
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Где хранить логику работы с локаторами в тестовом фреймворке
Ключевой вопрос при построении устойчивого и поддерживаемого тестового фреймворка. При наличии отдельного класса (или модуля) с локаторами, хранение логики работы с ними требует продуманного архитектурного подхода. Основная цель — соблюсти принципы DRY (Don't Repeat Yourself), инкапсуляции и низкой связанности.
Основные архитектурные подходы и паттерны
1. Page Object Model (POM) — классический и наиболее распространенный
Логика работы с локаторами инкапсулируется внутри Page Object классов. Класс локаторов выступает как поставщик селекторов, а Page Object использует их, добавляя поведение.
# Класс с локаторами (селекторами)
class LoginPageLocators:
USERNAME_INPUT = "#username"
PASSWORD_INPUT = "#password"
SUBMIT_BUTTON = "button[type='submit']"
ERROR_MESSAGE = ".alert-error"
# Page Object, который хранит логику работы с этими локаторами
class LoginPage:
def __init__(self, driver):
self.driver = driver
def enter_username(self, username):
element = self.driver.find_element(By.CSS_SELECTOR, LoginPageLocators.USERNAME_INPUT)
element.clear()
element.send_keys(username)
def login(self, username, password):
self.enter_username(username)
self.driver.find_element(By.CSS_SELECTOR, LoginPageLocators.PASSWORD_INPUT).send_keys(password)
self.driver.find_element(By.CSS_SELECTOR, LoginPageLocators.SUBMIT_BUTTON).click()
def get_error_message(self):
return self.driver.find_element(By.CSS_SELECTOR, LoginPageLocators.ERROR_MESSAGE).text
Преимущества:
- Полная инкапсуляция: тест знает только о методах
LoginPage. - Упрощение поддержки: изменения в верстке вносятся в 2 местах (локаторы + возможно, логику), а не во всех тестах.
- Читаемость тестов: тесты выглядят как бизнес-сценарии.
2. Page Element (или Component) Pattern — для сложных UI-компонентов
Когда элементы интерфейса повторяются (таблицы, выпадающие списки, модальные окна), логику работы с ними выносят в отдельные классы компонентов.
# Класс с локаторами для таблицы
class UserTableLocators:
TABLE = "table.users"
HEADER = "thead th"
ROWS = "tbody tr"
CELL = "td"
# Класс, инкапсулирующий логику работы с таблицей
class UserTableComponent:
def __init__(self, driver, container_locator=UserTableLocators.TABLE):
self.driver = driver
self.container = driver.find_element(By.CSS_SELECTOR, container_locator)
def get_row_count(self):
return len(self.container.find_elements(By.CSS_SELECTOR, UserTableLocators.ROWS))
def get_cell_text(self, row_index, column_index):
rows = self.container.find_elements(By.CSS_SELECTOR, UserTableLocators.ROWS)
cells = rows[row_index].find_elements(By.CSS_SELECTOR, UserTableLocators.CELL)
return cells[column_index].text
# Использование в Page Object
class AdminPage:
def __init__(self, driver):
self.driver = driver
self.user_table = UserTableComponent(driver)
def get_first_user_name(self):
return self.user_table.get_cell_text(0, 1)
3. Использование Миксинов (Mixins) — для переиспользуемой логики
Если одинаковая логика работы с элементами (ожидание, клик, ввод текста) используется на многих страницах, ее можно вынести в миксины или базовые классы.
# Базовый класс с общей логикой
class BasePage:
def __init__(self, driver):
self.driver = driver
def _type(self, locator, text):
element = self.wait.until(EC.element_to_be_clickable(locator))
element.clear()
element.send_keys(text)
def _click(self, locator):
self.wait.until(EC.element_to_be_clickable(locator)).click()
# Page Object наследует логику и использует локаторы
class LoginPage(BasePage, LoginPageLocators):
def __init__(self, driver):
super().__init__(driver)
self.wait = WebDriverWait(driver, 10)
def login(self, username, password):
self._type((By.CSS_SELECTOR, self.USERNAME_INPUT), username)
self._type((By.CSS_SELECTOR, self.PASSWORD_INPUT), password)
self._click((By.CSS_SELECTOR, self.SUBMIT_BUTTON))
4. Полное отделение: Локаторы → Страницы → Шаги
В BDD-подходе или многослойном фреймворке может использоваться трехуровневая структура:
- Уровень локаторов: чистые константы.
- Уровень Page Object: методы работы с элементами.
- Уровень шагов (Step Definitions или бизнес-логика): композиция методов Page Object в бизнес-действия.
Рекомендации и лучшие практики
- Единая ответственность: Класс с локаторами должен только хранить локаторы, без логики. Логика — ответственность Page Object или компонентов.
- Именование локаторов: Имена должны отражать бизнес-сущность, а не детали реализации (
LOGIN_BUTTON, а неBLUE_BUTTON). - Ленивая инициализация элементов: Вместо поиска всех элементов в
__init__, используйте property или методы для поиска по требованию, чтобы избежатьStaleElementReferenceException.@property def username_input(self): return self.driver.find_element(By.CSS_SELECTOR, self.USERNAME_INPUT) - Ожидания (Waits): Интегрируйте явные ожидания внутрь методов Page Object, это сделает тесты стабильнее.
- Отчетность и логирование: Добавляйте в методы логирование действий, что упрощает отладку падающих тестов.
Заключение
Логику работы с локаторами следует хранить в Page Object классах и, при необходимости, в отдельных классах компонентов. Это обеспечивает:
- Сопровождаемость: Изменения в UI затрагивают минимальное количество мест.
- Читаемость: Тесты описывают бизнес-сценарии, а не технические детали.
- Переиспользуемость: Общая логика выносится в базовые классы или компоненты.
- Надежность: Централизованное управление ожиданиями и обработкой ошибок.
Такой подход превращает коллекцию локаторов в действующую модель пользовательского интерфейса, с которой взаимодействуют автотесты, что является сутью эффективной автоматизации веб-приложений.