Compare commits

..

2 Commits

Author SHA1 Message Date
Radislav 5f21e197f6 Merge branch 'main' of http://192.168.2.61/AlexL/e-nms_qa_automation 2026-01-13 14:19:56 +03:00
Radislav 5bbd4e2d46 refactor: Универсальный метод get_input_fields_locators_
- Создан универсальный метод get_input_fields_locators в BaseComponent
- Метод поддерживает разные структуры: xs4->xs8, xs4->xs1, любые парные flex
- Добавлены специализированные локаторы для числовых полей в SettingsFormLocators
- Обновлены тесты и компоненты для использования нового метода
- Удалена зависимость от конкретных CSS классов xs1/xs4/xs6/xs8
2026-01-13 14:18:06 +03:00
7 changed files with 84 additions and 98 deletions

View File

@ -30,59 +30,51 @@ class BaseComponent:
# Действия: # Действия:
def get_input_fields_locators_(self, container_locator: Locator, search_class: str = "layout") -> dict: def get_input_fields_locators(self, container_locator: Locator) -> dict:
"""Получение объекта словаря имя поля ввода : Locator. """Находит пары "метка-поле ввода" в контейнере с layout структурой.
Метод ищет элементы в структуре div.layout > div.flex, где:
- Первый div.flex содержит метку (текст в input элементе)
- Второй div.flex содержит соответствующее поле ввода
Поддерживает различные структуры:
- xs4 (метка) -> xs8 (поле ввода)
- xs4 (метка) -> xs1 (поле ввода)
- Любые другие парные flex контейнеры
Args: Args:
container_locator: объект Locator. container_locator: Контейнер, в котором искать поля ввода.
search_class: css класс для поиска (по умолчанию layout)
Returns: Returns:
dict: словарь имя поля ввода : Locator xs8 контейнера Словарь, где ключ - текст метки, значение - Locator контейнера с полем ввода.
""" """
fields_locators = {} fields_locators = {}
if search_class == "layout": layouts = container_locator.locator("div.layout")
# Поиск по структуре layout -> xs4 (label) + xs8 (input контейнер)
layout_containers = container_locator.locator("div.layout")
for i in range(layout_containers.count()): for i in range(layouts.count()):
layout_container = layout_containers.nth(i) layout = layouts.nth(i)
flex_containers = layout.locator("div.flex")
xs4_div = layout_container.locator("div.flex.xs4").first # Обрабатываем пары контейнеров
xs8_div = layout_container.locator("div.flex.xs8").first for j in range(0, flex_containers.count() - 1):
label_container = flex_containers.nth(j)
if xs4_div.count() > 0 and xs8_div.count() > 0: input_container = flex_containers.nth(j + 1)
# Ищем input в label_container
label_input = xs4_div.locator("div.v-text-field__slot > input").first
if label_input.count() > 0:
label_text = label_input.input_value().strip()
# Извлекаем текст метки
inputs = label_container.locator("input")
if inputs.count() > 0:
label_text = inputs.first.input_value().strip()
if label_text: if label_text:
# Возвращаем xs8 контейнер # Проверяем поле ввода
fields_locators[label_text] = xs8_div has_input = input_container.locator(
"input, textarea, select"
).count() > 0
return fields_locators if has_input:
fields_locators[label_text] = input_container
def get_input_fields_locators(self, container_locator: Locator, css_class: str) -> dict:
"""Получение объекта словаря имя поля ввода : Locator.
Args:
locator: объект Locator.
css_class: css класс для уточнения положения искомого объекта в DOM.
Returns:
dict: словарь имя поля ввода : Locator.
"""
fields_locators = {}
elements = container_locator.locator(f"div.flex.{css_class} div.v-text-field__slot > input").all()
for el in elements:
val = el.input_value().strip()
if val:
fields_locators[val] = el.locator("../ancestor::div[6]")
return fields_locators return fields_locators
def get_locator(self, locator: str | Locator) -> Locator: def get_locator(self, locator: str | Locator) -> Locator:

View File

@ -57,10 +57,13 @@ class RackObjectMaker(BaseComponent):
logger.debug("Rack data filled successfully") logger.debug("Rack data filled successfully")
def _fill_text_fields(self, rack_data: RackData) -> None: def _get_form_fields(self) -> dict:
"""Заполняет текстовые поля.""" """
Получает все поля формы стойки.
logger.debug("Filling text fields...") Returns:
dict: Словарь {название поля: Locator контейнера поля}
"""
# Получаем контейнер формы (второй элемент) # Получаем контейнер формы (второй элемент)
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
@ -69,8 +72,15 @@ class RackObjectMaker(BaseComponent):
logger.error("Form container not found") logger.error("Form container not found")
raise ValueError("Form container not found") raise ValueError("Form container not found")
# Используем метод из базового класса для получения всех полей return self.get_input_fields_locators(container_locator)
fields_locators = self.get_input_fields_locators_(container_locator, "layout")
def _fill_text_fields(self, rack_data: RackData) -> None:
"""Заполняет текстовые поля."""
logger.debug("Filling text fields...")
# Получаем все поля формы
fields_locators = self._get_form_fields()
logger.debug(f"Available text fields: {list(fields_locators.keys())}") logger.debug(f"Available text fields: {list(fields_locators.keys())}")
@ -81,7 +91,7 @@ class RackObjectMaker(BaseComponent):
logger.debug(f"Skipping empty value for field '{field_name}'") logger.debug(f"Skipping empty value for field '{field_name}'")
return return
# Получаем xs8 контейнер поля # Получаем контейнер поля
field_container = fields_locators.get(field_name) field_container = fields_locators.get(field_name)
if not field_container: if not field_container:
@ -137,51 +147,49 @@ class RackObjectMaker(BaseComponent):
def _fill_combobox_fields(self, rack_data: RackData) -> None: def _fill_combobox_fields(self, rack_data: RackData) -> None:
"""Заполняет combobox поля.""" """Заполняет combobox поля."""
# Получаем все поля формы
fields_locators = self._get_form_fields()
# Обязательные поля. # Обязательные поля.
if rack_data.height: if rack_data.height:
self._fill_combobox_field("Высота в юнитах", rack_data.height) self._fill_combobox_field("Высота в юнитах", rack_data.height, fields_locators)
logger.debug(f"Selected height: {rack_data.height} units") logger.debug(f"Selected height: {rack_data.height} units")
if rack_data.depth: if rack_data.depth:
self._fill_combobox_field("Глубина (мм)", rack_data.depth) self._fill_combobox_field("Глубина (мм)", rack_data.depth, fields_locators)
logger.debug(f"Selected depth: {rack_data.depth} mm") logger.debug(f"Selected depth: {rack_data.depth} mm")
# Опциональные поля. # Опциональные поля.
if rack_data.cable_entry: if rack_data.cable_entry:
self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry) self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry, fields_locators)
logger.debug(f"Selected cable entry: {rack_data.cable_entry}") logger.debug(f"Selected cable entry: {rack_data.cable_entry}")
if rack_data.state: if rack_data.state:
self._fill_combobox_field("Состояние", rack_data.state) self._fill_combobox_field("Состояние", rack_data.state, fields_locators)
logger.debug(f"Selected state: {rack_data.state}") logger.debug(f"Selected state: {rack_data.state}")
if rack_data.owner: if rack_data.owner:
self._fill_combobox_field("Владелец", rack_data.owner) self._fill_combobox_field("Владелец", rack_data.owner, fields_locators)
logger.debug(f"Selected owner: {rack_data.owner}") logger.debug(f"Selected owner: {rack_data.owner}")
if rack_data.service_org: if rack_data.service_org:
self._fill_combobox_field("Обслуживающая организация", rack_data.service_org) self._fill_combobox_field("Обслуживающая организация", rack_data.service_org, fields_locators)
logger.debug(f"Selected service organization: {rack_data.service_org}") logger.debug(f"Selected service organization: {rack_data.service_org}")
if rack_data.project: if rack_data.project:
self._fill_combobox_field("Проект/Титул", rack_data.project) self._fill_combobox_field("Проект/Титул", rack_data.project, fields_locators)
logger.debug(f"Selected project/title: {rack_data.project}") logger.debug(f"Selected project/title: {rack_data.project}")
def _fill_combobox_field(self, field_name: str, value: str) -> None: def _fill_combobox_field(self, field_name: str, value: str, fields_locators: dict) -> None:
""" """
Заполняет combobox поле. Заполняет combobox поле.
Args: Args:
field_name: Название поля field_name: Название поля
value: Значение для установки value: Значение для установки
fields_locators: Словарь с найденными полями формы
""" """
# Получаем контейнер формы (второй элемент)
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
# Используем метод из базового класса BaseComponent
fields_locators = self.get_input_fields_locators_(container_locator, "layout")
# Получаем контейнер поля по его названию # Получаем контейнер поля по его названию
field_container = fields_locators.get(field_name) field_container = fields_locators.get(field_name)
@ -270,14 +278,8 @@ class RackObjectMaker(BaseComponent):
logger.debug("Checking rack fields presence...") logger.debug("Checking rack fields presence...")
# Получаем контейнер формы (второй элемент) # Получаем все поля формы
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) fields_locators = self._get_form_fields()
# Проверяем наличие контейнера
assert container_locator.count() > 0, "Form container not found"
# Используем метод из базового класса BaseComponent для получения всех полей
fields_locators = self.get_input_fields_locators_(container_locator, "layout")
logger.debug(f"Found fields in form: {list(fields_locators.keys())}") logger.debug(f"Found fields in form: {list(fields_locators.keys())}")

View File

@ -61,15 +61,13 @@ class CreateChildElementFrame(BaseComponent):
# Получаем контейнер формы # Получаем контейнер формы
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = self.get_input_fields_locators(container_locator)
# Используем метод get_input_fields_locators для получения всех полей
fields_locators = self.get_input_fields_locators_(container_locator, "layout")
if field_name not in fields_locators: if field_name not in fields_locators:
logger.warning(f"Field '{field_name}' not found in form") logger.warning(f"Field '{field_name}' not found in form")
return return
# Получаем xs8 контейнер поля # Получаем контейнер поля
field_container = fields_locators[field_name] field_container = fields_locators[field_name]
# Прокручиваем до поля # Прокручиваем до поля
@ -121,9 +119,7 @@ class CreateChildElementFrame(BaseComponent):
"""Открывает выпадающий список combobox.""" """Открывает выпадающий список combobox."""
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER)
fields_locators = self.get_input_fields_locators(container_locator)
# Используем метод из базового класса BaseComponent
fields_locators = self.get_input_fields_locators_(container_locator, "layout")
combobox_container = fields_locators.get("Класс объекта учета") combobox_container = fields_locators.get("Класс объекта учета")
if not combobox_container: if not combobox_container:
@ -149,7 +145,7 @@ class CreateChildElementFrame(BaseComponent):
# Открываем combobox # Открываем combobox
self.open_object_class_combobox() self.open_object_class_combobox()
# Выбираем значение из списка # Выбирает значение из списка
self.selection_bar.select_value(class_name) self.selection_bar.select_value(class_name)
# Даем время на применение выбора # Даем время на применение выбора
@ -171,7 +167,7 @@ class CreateChildElementFrame(BaseComponent):
# Получаем контейнеры всех полей # Получаем контейнеры всех полей
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER)
fields_locators = self.get_input_fields_locators_(container_locator, "layout") fields_locators = self.get_input_fields_locators(container_locator)
# Получаем контейнер конкретного поля # Получаем контейнер конкретного поля
field_container = fields_locators.get(field_name) field_container = fields_locators.get(field_name)
@ -204,7 +200,7 @@ class CreateChildElementFrame(BaseComponent):
# Получаем контейнеры всех полей # Получаем контейнеры всех полей
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER)
fields_locators = self.get_input_fields_locators_(container_locator, "layout") fields_locators = self.get_input_fields_locators(container_locator)
# Получаем контейнер конкретного поля # Получаем контейнер конкретного поля
field_container = fields_locators.get(field_name) field_container = fields_locators.get(field_name)

View File

@ -19,8 +19,9 @@ class SettingsFormLocators:
SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]" SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]"
SETTINGS_FORM_INPUT_FORM_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/following-sibling::div" SETTINGS_FORM_INPUT_FORM_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/following-sibling::div"
SETTINGS_FORM_INPUT_FIELD = "div:nth-child(2) div.v-text-field__slot > input"
SETTINGS_FORM_INPUT_VALUE_SUFFIX = "div:nth-child(2) div.v-text-field__slot div.v-text-field__suffix" SETTINGS_FORM_INPUT_FIELD = "div.v-text-field__slot > input"
SETTINGS_FORM_INPUT_VALUE_SUFFIX = ".v-text-field__suffix"
DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]" DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]"
SELECTED_VALUES = "//div[@class='v-select__selections']" SELECTED_VALUES = "//div[@class='v-select__selections']"

View File

@ -36,9 +36,8 @@ class PushNotificationsSettingsTab(BasePage):
self.settings_form = SettingsFormComponent(page) self.settings_form = SettingsFormComponent(page)
self.settings_form.add_toolbar_title("Общие") self.settings_form.add_toolbar_title("Общие")
css_class = "xs6"
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator, css_class) self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
loc = self.input_fields_locators.get("Сообщение") loc = self.input_fields_locators.get("Сообщение")
loc_message_input = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first loc_message_input = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first

View File

@ -44,32 +44,32 @@ class SessionSettingsTab(BasePage):
self.settings_form.add_toolbar_title("Время жизни сеанса") self.settings_form.add_toolbar_title("Время жизни сеанса")
css_class = "xs4"
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator, css_class) self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
# Используем локаторы для числовых полей
loc = self.input_fields_locators.get("Администратор") loc = self.input_fields_locators.get("Администратор")
loc_admin = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD) loc_admin = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
admin_setting = TextInput(page, loc_admin, "admin_setting") admin_setting = TextInput(page, loc_admin, "admin_setting")
self.settings_form.add_content_item("admin_setting", admin_setting) self.settings_form.add_content_item("admin_setting", admin_setting)
loc = self.input_fields_locators.get("Оператор") loc = self.input_fields_locators.get("Оператор")
loc_oper = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD) loc_oper = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
operator_setting = TextInput(page, loc_oper, "operator_setting") operator_setting = TextInput(page, loc_oper, "operator_setting")
self.settings_form.add_content_item("operator_setting", operator_setting) self.settings_form.add_content_item("operator_setting", operator_setting)
loc = self.input_fields_locators.get("Контактное лицо") loc = self.input_fields_locators.get("Контактное лицо")
loc_manager = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD) loc_manager = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
manager_setting = TextInput(page, loc_manager, "manager_setting") manager_setting = TextInput(page, loc_manager, "manager_setting")
self.settings_form.add_content_item("manager_setting", manager_setting) self.settings_form.add_content_item("manager_setting", manager_setting)
loc = self.input_fields_locators.get("Специалист информационной безопасности") loc = self.input_fields_locators.get("Специалист информационной безопасности")
loc_inform_secur_user = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD) loc_inform_secur_user = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
inform_secur_user_setting = TextInput(page, loc_inform_secur_user, "inform_secur_user_setting") inform_secur_user_setting = TextInput(page, loc_inform_secur_user, "inform_secur_user_setting")
self.settings_form.add_content_item("inform_secur_user_setting", inform_secur_user_setting) self.settings_form.add_content_item("inform_secur_user_setting", inform_secur_user_setting)
loc = self.input_fields_locators.get('$collector') loc = self.input_fields_locators.get('$collector')
loc_collector = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD) loc_collector = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
collector_setting = TextInput(page, loc_collector, "collector_setting") collector_setting = TextInput(page, loc_collector, "collector_setting")
self.settings_form.add_content_item("collector_setting", collector_setting) self.settings_form.add_content_item("collector_setting", collector_setting)
@ -220,8 +220,8 @@ class SessionSettingsTab(BasePage):
) )
for name in actual_input_field_names: for name in actual_input_field_names:
value_suffix_loc = self.input_fields_locators.get(name).\ # Для суффикса "минут"
locator(SettingsFormLocators.SETTINGS_FORM_INPUT_VALUE_SUFFIX) value_suffix_loc = self.input_fields_locators.get(name).locator(SettingsFormLocators.SETTINGS_FORM_INPUT_VALUE_SUFFIX)
value_suffix = value_suffix_loc.text_content().strip() value_suffix = value_suffix_loc.text_content().strip()
assert value_suffix == "минут", f"Incorrect value suffix for field {name}" assert value_suffix == "минут", f"Incorrect value suffix for field {name}"

View File

@ -213,9 +213,7 @@ class TestCreateRackElement:
# Получаем контейнер формы # Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = create_child_frame.get_input_fields_locators(container_locator)
# Используем метод get_input_fields_locators для получения всех полей
fields_locators = create_child_frame.get_input_fields_locators_(container_locator, "layout")
logger.debug(f"Available fields: {list(fields_locators.keys())}") logger.debug(f"Available fields: {list(fields_locators.keys())}")
@ -227,7 +225,7 @@ class TestCreateRackElement:
logger.debug(f"Field '{field_name}' not found in fields_locators") logger.debug(f"Field '{field_name}' not found in fields_locators")
return False return False
# Получаем xs8 контейнер поля # Получаем контейнер поля
field_container = fields_locators[field_name] field_container = fields_locators[field_name]
if not field_container.is_visible(): if not field_container.is_visible():
@ -409,9 +407,7 @@ class TestCreateRackElement:
# Получаем контейнер формы # Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = create_child_frame.get_input_fields_locators(container_locator)
# Используем метод get_input_fields_locators для получения всех полей
fields_locators = create_child_frame.get_input_fields_locators_(container_locator, "layout")
# Очищаем поле "Высота в юнитах" если оно заполнено # Очищаем поле "Высота в юнитах" если оно заполнено
if "Высота в юнитах" in fields_locators: if "Высота в юнитах" in fields_locators: