From c56bf70cfa6470ae0e9b316e29e240dff59c2250 Mon Sep 17 00:00:00 2001 From: Radislav Date: Wed, 28 Jan 2026 13:26:38 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/confirm_component.py | 45 ++- components_derived/modal_add_user.py | 73 ++-- components_derived/modal_edit_user.py | 37 +- components_derived/modal_rack_edit.py | 442 ------------------------ locators/confirm_locators.py | 6 +- locators/rack_locators.py | 58 ++-- pages/rack_page.py | 36 +- tests/e2e/elements/test_element_rack.py | 130 +++---- 8 files changed, 178 insertions(+), 649 deletions(-) delete mode 100644 components_derived/modal_rack_edit.py diff --git a/components/confirm_component.py b/components/confirm_component.py index 5ee0e37..6467e2c 100644 --- a/components/confirm_component.py +++ b/components/confirm_component.py @@ -17,31 +17,48 @@ logger = get_logger("CONFIRM_WINDOW") class ConfirmComponent(BaseComponent): """Компонент окна подтверждения действий.""" - def __init__(self, page: Page, cancel_button_text: str, allow_button_text: str): + def __init__(self, page: Page, cancel_button_text: str = "", allow_button_text: str = "", + cancel_button_locator: str = None, allow_button_locator: str = None): """Инициализация компонента. Args: page: Экземпляр страницы Playwright. - cancel_button_text: Текст кнопки отмены. - allow_button_text: Текст кнопки подтверждения. + cancel_button_text: Текст кнопки отмены (по умолчанию пустая строка). + allow_button_text: Текст кнопки подтверждения (по умолчанию пустая строка). + cancel_button_locator: Локатор кнопки отмены (опционально). + allow_button_locator: Локатор кнопки подтверждения (опционально). """ super().__init__(page) self.title = Text(page, ConfirmLocators.TITLE, "confirm title") self.text = Text(page, ConfirmLocators.TEXT, "confirm text") - self.close_button = Button(page, ConfirmLocators.BUTTON_CLOSE, "confirm close button") - self.cancel_button = Button( - page, - page.get_by_role("button", name=cancel_button_text).first, - "confirm cancel button" - ) - self.allow_button = Button( - page, - page.get_by_role("button", name=allow_button_text).first, - "confirm allow button" - ) + + # Инициализация кнопок с приоритетом локаторам + if cancel_button_locator: + self.cancel_button = Button(page, cancel_button_locator, "confirm cancel button") + elif cancel_button_text: + self.cancel_button = Button( + page, + page.get_by_role("button", name=cancel_button_text).first, + "confirm cancel button" + ) + else: + self.cancel_button = None + logger.warning("Cancel button not initialized - neither text nor locator specified") + + if allow_button_locator: + self.allow_button = Button(page, allow_button_locator, "confirm allow button") + elif allow_button_text: + self.allow_button = Button( + page, + page.get_by_role("button", name=allow_button_text).first, + "confirm allow button" + ) + else: + self.allow_button = None + logger.warning("Allow button not initialized - neither text nor locator specified") # Действия: def click_allow_button(self) -> None: diff --git a/components_derived/modal_add_user.py b/components_derived/modal_add_user.py index 69a910d..be3fb86 100644 --- a/components_derived/modal_add_user.py +++ b/components_derived/modal_add_user.py @@ -35,7 +35,6 @@ class AddUserModalWindow(ModalWindowComponent): super().__init__(page) # Локаторы элементов формы - text_field_locator = f"//{ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}" input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA # Настройка заголовка и кнопки закрытия тулбара @@ -49,16 +48,16 @@ class AddUserModalWindow(ModalWindowComponent): self.add_toolbar_title(self.window_title) self.add_toolbar_button(locator_button_toolbar_close, "close") - elements_locators = self._get_fields_locators(page) + elements_locators = self.get_input_fields_locators(page.locator(input_form_locator)) # Поле Тип авторизации loc = elements_locators.get("Тип авторизации") - if loc: + if loc: auth_type_selector = SelectionBarComponent(page, loc.get_by_role("combobox")) self.add_content_item("auth_type_selector", auth_type_selector) # Поле Имя - loc = elements_locators.get("Имя").locator(text_field_locator) + loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) name_input = TextInput(page, loc, "name_input") self.add_content_item("name_input", name_input) @@ -76,34 +75,34 @@ class AddUserModalWindow(ModalWindowComponent): # Чекбокс "Блокировка" checkbox_blocking = Checkbox( page, - label_blocking_locator.locator("../..").get_by_role("checkbox"), + page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED), "blocking" ) self.add_content_item("blocking_checkbox", checkbox_blocking) # Поле Роль - role_loc = elements_locators.get("Роль").get_by_role("combobox") + role_loc = elements_locators.get("Роль").get_by_role("combobox").first role_input = TextInput(page, role_loc, "role_input") self.add_content_item("role_input", role_input) self.add_content_item("roles_list", DropdownList(page)) # Поле Пароль - loc = elements_locators.get("Пароль").locator(text_field_locator) + loc = elements_locators.get("Пароль").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_PASSWORD) password_input = TextInput(page, loc, "password_input") self.add_content_item("password_input", password_input) # Поле Комментарий - loc = elements_locators.get("Комментарий").locator(text_field_locator) + loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT) commentary_input = TextInput(page, loc, "commentary_input") self.add_content_item("commentary_input", commentary_input) # Поле E-mail - loc = elements_locators.get("E-mail").locator(text_field_locator) + loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL) email_input = TextInput(page, loc, "email_input") self.add_content_item("email_input", email_input) # Поле Номер для СМС - loc = elements_locators.get("Номер для СМС").locator(text_field_locator) + loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS) phone_input = TextInput(page, loc, "phone_input") self.add_content_item("phone_input", phone_input) @@ -120,7 +119,7 @@ class AddUserModalWindow(ModalWindowComponent): # Чекбокс "Подписка на Push-уведомления" checkbox_push = Checkbox( page, - label_push_locator.locator("../..").get_by_role("checkbox"), + page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE), "push_notification" ) self.add_content_item("push_notification_checkbox", checkbox_push) @@ -161,7 +160,7 @@ class AddUserModalWindow(ModalWindowComponent): auth_type = None auth_type_selector = self.get_content_item("auth_type_selector") - if auth_type_selector: + if auth_type_selector: values = auth_type_selector.get_selected_values() auth_type = values[0] return auth_type @@ -192,7 +191,8 @@ class AddUserModalWindow(ModalWindowComponent): if auth_type == "LDAP": menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM) - elements_locators = self._get_fields_locators(self.page) + elements_locators = self.get_input_fields_locators( + self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) # Добавилось поле Группа group_loc = elements_locators.get("Группа").get_by_role("combobox") @@ -223,7 +223,8 @@ class AddUserModalWindow(ModalWindowComponent): search_button.click() # Если в группе есть пользователи, открывается новое поле, заново вычисляем локаторы - elements_locators = self._get_fields_locators(self.page) + elements_locators = self.get_input_fields_locators( + self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) users_ad_loc = elements_locators.get("Пользователи AD") # users_ad_loc = elements_locators.get("Пользователи LDAP") assert users_ad_loc, f"Selected group {group_name} is empty. Use another group." @@ -323,39 +324,28 @@ class AddUserModalWindow(ModalWindowComponent): def locators_recalculation(self, is_active_directory=False) -> None: """Пересчет локаторов полей ввода""" - text_field_locator = f"//{ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}" + elements_locators = self.get_input_fields_locators( + self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) - elements_locators = self._get_fields_locators(self.page) - - new_loc = elements_locators.get("Имя").locator(text_field_locator) + new_loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) self.get_content_item("name_input").update_locator(new_loc) if not is_active_directory: - new_loc = elements_locators.get("Пароль").locator(text_field_locator) + new_loc = elements_locators.get("Пароль").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_PASSWORD) self.get_content_item("password_input").update_locator(new_loc) - new_loc = elements_locators.get("Роль").get_by_role("combobox") + new_loc = elements_locators.get("Роль").get_by_role("combobox").first self.get_content_item("role_input").update_locator(new_loc) - new_loc = elements_locators.get("Комментарий").locator(text_field_locator) + new_loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT) self.get_content_item("commentary_input").update_locator(new_loc) - new_loc = elements_locators.get("E-mail").locator(text_field_locator) + new_loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL) self.get_content_item("email_input").update_locator(new_loc) - new_loc = elements_locators.get("Номер для СМС").locator(text_field_locator) + new_loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS) self.get_content_item("phone_input").update_locator(new_loc) - def _get_fields_locators(self, page) -> dict: - fields_locators = {} - elements = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA). \ - locator("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[5]") - return fields_locators - # Проверки: def check_content(self): """Проверяет наличие и корректность всех элементов формы создания локального пользователя. @@ -419,8 +409,8 @@ class AddUserModalWindow(ModalWindowComponent): elif name == "roles_list": continue else: - print(f"check item: {name}") - print(item) + # print(f"check item: {name}") + # print(item) item.check_visibility( f"Modal window content item with name '{name}' is missing" ) @@ -442,22 +432,23 @@ class AddUserModalWindow(ModalWindowComponent): auth_type_selector = self.get_content_item("auth_type_selector") if auth_type_selector: self.select_auth_type("LDAP") - - elements_locators = self._get_fields_locators(self.page) - + + elements_locators = self.get_input_fields_locators( + self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) + # Добавилось поле Группа group_loc = elements_locators.get("Группа").get_by_role("combobox") group_input = TextInput(self.page, group_loc, "group_input") self.add_content_item("group_input", group_input) self.add_content_item("group_list", DropdownList(self.page)) - + group_field = self.get_content_item("group_input") group_field.click() - + group_list = self.get_content_item("group_list") group_list.check_visibility(menu_locator, "Groups list is missing") - + is_scrollable_vertically = group_list.check_vertical_scrolling(menu_locator) assert is_scrollable_vertically, "Groups list should be scrollable_vertically" self.page.keyboard.press("Escape") diff --git a/components_derived/modal_edit_user.py b/components_derived/modal_edit_user.py index 83c1e7f..0c0598d 100644 --- a/components_derived/modal_edit_user.py +++ b/components_derived/modal_edit_user.py @@ -34,8 +34,6 @@ class EditUserModalWindow(ModalWindowComponent): super().__init__(page) # Локаторы элементов формы - # text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA - text_field_locator = f"xpath={ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}" input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA # Настройка заголовка и кнопки закрытия @@ -50,31 +48,32 @@ class EditUserModalWindow(ModalWindowComponent): self.add_toolbar_button(locator_button_toolbar_close, "close") # Добавление полей формы - elements_locators = self._get_fields_locators(page) + elements_locators = self.get_input_fields_locators( + self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) # Поле Имя - loc = elements_locators.get("Имя").locator(text_field_locator) + loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) name_input = TextInput(page, loc, "name_input") self.add_content_item("name_input", name_input) # Поле Роль - role_loc = self.page.locator(input_form_locator).get_by_role("combobox") + role_loc = self.page.locator(input_form_locator).get_by_role("combobox").first role_input = TextInput(page, role_loc, "role_input") self.add_content_item("role_input", role_input) self.add_content_item("roles_list", DropdownList(page)) # Поле Комментарий - loc = elements_locators.get("Комментарий").locator(text_field_locator) + loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT) commentary_input = TextInput(page, loc, "commentary_input") self.add_content_item("commentary_input", commentary_input) # Поле E-mail - loc = elements_locators.get("E-mail").locator(text_field_locator) + loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL) email_input = TextInput(page, loc, "email_input") self.add_content_item("email_input", email_input) # Поле Номер для СМС - loc = elements_locators.get("Номер для СМС").locator(text_field_locator) + loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS) phone_input = TextInput(page, loc, "phone_input") self.add_content_item("phone_input", phone_input) @@ -93,7 +92,7 @@ class EditUserModalWindow(ModalWindowComponent): # Чекбокс "Блокировка" checkbox_blocking = Checkbox( page, - label_blocking_locator.locator("../..").get_by_role("checkbox"), + page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED), "blocking" ) self.add_content_item("blocking_checkbox", checkbox_blocking) @@ -111,7 +110,7 @@ class EditUserModalWindow(ModalWindowComponent): # Чекбокс "Подписка на Push-уведомления" checkbox_push = Checkbox( page, - label_push_locator.locator("../..").get_by_role("checkbox"), + page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE), "push_notification" ) self.add_content_item("push_notification_checkbox", checkbox_push) @@ -241,15 +240,15 @@ class EditUserModalWindow(ModalWindowComponent): reset_password_button = self.get_button_by_name("reset_password") reset_password_button.click() - def _get_fields_locators(self, page) -> dict: - fields_locators = {} - elements = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA). \ - locator("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[5]") - return fields_locators + # def _get_fields_locators(self, page) -> dict: + # fields_locators = {} + # elements = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA). \ + # locator("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[5]") + # return fields_locators # Проверки: def check_content(self, user_name, role): diff --git a/components_derived/modal_rack_edit.py b/components_derived/modal_rack_edit.py deleted file mode 100644 index 4d68b03..0000000 --- a/components_derived/modal_rack_edit.py +++ /dev/null @@ -1,442 +0,0 @@ -"""Модуль для работы с модальным окном редактирования стойки.""" - -from dataclasses import dataclass, field -from typing import Optional, Dict -from playwright.sync_api import Page, Locator -from tools.logger import get_logger -from locators.rack_locators import RackLocators -from elements.tooltip_button_element import TooltipButton -from components.toolbar_component import ToolbarComponent -from components.base_component import BaseComponent - -logger = get_logger("MODAL_EDIT_RACK") -#logger.setLevel("INFO") - - -@dataclass -class RackEditData: - """Класс для хранения данных редактирования стойки.""" - - # Основные поля (редактируемые) - name: str = "" - serial: str = "" - inventory: str = "" - comment: str = "" - - # Combobox поля (редактируемые) - cable_entry: str = "" - state: str = "" - owner: str = "" - service_org: str = "" - project: str = "" - - # Дополнительные поля (редактируемые) - power: str = "" - - # Checkbox поля (редактируемые) - если есть - ventilation_panel: Optional[bool] = None - - # Правила доступа (если есть) - read_access_rules: str = "" - write_access_rules: str = "" - sms_access_rules: str = "" - email_access_rules: str = "" - push_access_rules: str = "" - - -class ModalEditRack(BaseComponent): - """Компонент для работы с модальным окном редактирования стойки.""" - - def __init__(self, page: Page) -> None: - """ - Инициализирует компонент редактирования стойки. - - Args: - page (Page): Экземпляр страницы Playwright - """ - super().__init__(page) - self._form_container = None - self._fields_cache = {} - self.toolbar = ToolbarComponent(page, "") - - # Кнопка "Переместить" (data-testid) - replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON) - self.replace_button = TooltipButton(page, replace_button_locator, "replace") - - # Кнопка "Сохранить" (data-testid) - done_button_locator = page.locator(RackLocators.TOOLBAR_DONE_BUTTON) - self.done_button = TooltipButton(page, done_button_locator, "done") - - # Кнопка "Отменить" (data-testid) - close_button_locator = page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON) - self.close_button = TooltipButton(page, close_button_locator, "close") - - # Кнопка "Удалить" (data-testid) - remove_button_locator = page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON) - self.remove_button = TooltipButton(page, remove_button_locator, "remove") - - # Добавляем кнопки в тулбар - self.toolbar.add_tooltip_button(replace_button_locator, "replace") - self.toolbar.add_tooltip_button(done_button_locator, "done") - self.toolbar.add_tooltip_button(close_button_locator, "close") - self.toolbar.add_tooltip_button(remove_button_locator, "remove") - - def click_remove_button(self) -> None: - """ - Кликает на кнопку 'Удалить' и обрабатывает диалог подтверждения. - """ - logger.debug("Clicking on 'Remove' button...") - - # Проверяем видимость кнопки - self.toolbar.check_button_visibility("remove") - self.toolbar.check_button_tooltip("remove", "Удалить") - - # Кликаем на кнопку удаления - self.toolbar.get_button_by_name("remove").click() - self.wait_for_timeout(1000) - - # Ожидаем появления диалога подтверждения - self._handle_remove_confirmation_dialog() - - def click_done_button(self) -> None: - """ - Кликает на кнопку 'Сохранить' и обрабатывает диалог подтверждения. - """ - logger.debug("Clicking on 'Done' button...") - - # Проверяем видимость кнопки - self.toolbar.check_button_visibility("done") - self.toolbar.check_button_tooltip("done", "Сохранить") - - # Кликаем на кнопку сохранения - self.toolbar.get_button_by_name("done").click() - self.wait_for_timeout(1000) - - def confirm_remove_dialog(self, confirm: bool = True) -> None: - """ - Подтверждает или отклоняет удаление в диалоговом окне. - - Args: - confirm (bool): Если True - подтвердить удаление, если False - отменить - """ - logger.debug(f"Confirming remove dialog with: {'Да' if confirm else 'Нет'}") - - # Ждем немного перед поиском диалога - self.wait_for_timeout(1500) - - # Ищем активный диалог - dialog = self.page.locator("div.v-dialog--active") - - # Проверяем, что диалог найден и содержит нужный текст - assert dialog.count() > 0, "No active dialog found" - - # Проверяем текст диалога - dialog_text = dialog.first.text_content() - logger.debug("Dialog text: %s", dialog_text) - - # Должен содержать "Запрос подтверждения" и "Удалить" - assert "Запрос подтверждения" in dialog_text, "Not a confirmation dialog" - - # Ищем кнопку по data-testid - if confirm: - button = self.page.locator(RackLocators.CONFIRM_REMOVE_YES_BUTTON) - else: - button = self.page.locator(RackLocators.CONFIRM_REMOVE_NO_BUTTON) - - # Проверяем, что кнопка найдена - assert button.count() > 0, "Button not found with selector" - - # Кликаем на кнопку - button_text = button.first.text_content() - logger.debug("Clicking button with text: %s", button_text) - button.first.click() - self.wait_for_timeout(2000) - - # Проверяем, что диалог закрылся - self.wait_for_timeout(1000) - - logger.debug("Remove confirmation completed") - - def _handle_remove_confirmation_dialog(self) -> None: - """Обрабатывает диалог подтверждения удаления.""" - logger.debug("Handling remove confirmation dialog...") - self.confirm_remove_dialog(confirm=True) - - # Остальные существующие методы остаются без изменений - def _get_form_container(self) -> Locator: - """ - Получает контейнер формы редактирования. - """ - if self._form_container is None: - form_container = self.page.locator("[data-testid='cabinet-bar__cabinet-form']") - try: - form_container.wait_for(state="visible", timeout=10000) - self._form_container = form_container - except: - raise ValueError("Cabinet form container not found") - - return self._form_container - - def get_available_fields(self) -> list: - """ - Получает список доступных полей. - """ - fields_locators = self._get_form_fields() - return list(fields_locators.keys()) if fields_locators else [] - - def _get_form_fields(self) -> dict: - """ - Получает все поля формы редактирования стойки. - """ - if self._fields_cache: - return self._fields_cache - - form_container = self._get_form_container() - fields_locators = self.get_input_fields_locators(form_container) - self._fields_cache = fields_locators - - return fields_locators - - def _fill_text_field(self, field_name: str, value: str) -> bool: - """ - Заполняет текстовое поле по полному совпадению названия. - """ - fields_locators = self._get_form_fields() - - # Ищем точное совпадение - if field_name not in fields_locators: - logger.debug(f"Text field '{field_name}' not found. Available fields: {list(fields_locators.keys())}") - return False - - field_container = fields_locators[field_name] - - try: - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(300) - - # Ищем input поле - input_field = field_container.locator("input, textarea").first - if input_field.count() == 0: - logger.debug(f"Field '{field_name}' doesn't have input element") - return False - - # Очищаем и заполняем - input_field.click() - self.wait_for_timeout(200) - input_field.fill("") - self.wait_for_timeout(200) - input_field.fill(value) - self.wait_for_timeout(500) - - # Проверяем что значение установлено - actual_value = input_field.input_value() - if actual_value == value: - logger.debug(f"✓ Text field '{field_name}' filled with: '{value}'") - return True - else: - logger.warning(f"Field '{field_name}' value mismatch: expected '{value}', got '{actual_value}'") - return False - - except Exception as e: - logger.error(f"Error filling text field '{field_name}': {e}") - return False - - def _fill_combobox_field(self, field_name: str, value: str) -> bool: - """ - Заполняет combobox поле по полному совпадению названия. - """ - fields_locators = self._get_form_fields() - - # Ищем точное совпадение - if field_name not in fields_locators: - logger.debug(f"Combobox field '{field_name}' not found. Available fields: {list(fields_locators.keys())}") - return False - - field_container = fields_locators[field_name] - - try: - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(300) - - # Ищем кнопку открытия dropdown - dropdown_button = field_container.locator(".v-input__append-inner, [role='button']").first - - if dropdown_button.count() == 0: - # Может быть поле уже открыто - input_field = field_container.locator("input").first - input_field.click() - self.wait_for_timeout(1000) - else: - dropdown_button.click() - self.wait_for_timeout(1000) - - # Ищем выпадающий список - active_menu = None - menu_selectors = [ - ".v-menu__content.menuable__content__active", - ".v-select__menu", - ".v-autocomplete__content", - ".v-menu__content" - ] - - for selector in menu_selectors: - menu = self.page.locator(selector).first - if menu.count() > 0 and menu.is_visible(): - active_menu = menu - break - - if not active_menu: - logger.debug(f"No dropdown menu found for '{field_name}'") - return False - - # Ищем нужный элемент - dropdown_item = active_menu.locator(f"div[role='listitem'], .v-list-item").filter( - has_text=value - ).first - - if dropdown_item.count() == 0: - logger.debug(f"Value '{value}' not found in dropdown for '{field_name}'") - self.page.keyboard.press("Escape") - return False - - # Выбираем значение - dropdown_item.click() - logger.debug(f"✓ Combobox '{field_name}' set to: '{value}'") - self.wait_for_timeout(1000) - - return True - - except Exception as e: - logger.error(f"Error filling combobox '{field_name}': {e}") - self.page.keyboard.press("Escape") - return False - - def _set_checkbox_field(self, checkbox_label: str, value: bool) -> bool: - """ - Устанавливает состояние checkbox используя input[type="checkbox"]. - """ - try: - logger.debug(f"Setting checkbox '{checkbox_label}' to {value}") - - # Ищем все checkbox элементы в форме - form_container = self._get_form_container() - checkboxes = form_container.locator("input[type='checkbox']") - - if checkboxes.count() == 0: - logger.warning("No checkbox elements found in form") - return False - - logger.debug(f"Found {checkboxes.count()} checkbox(es) in form") - - # Если несколько чекбоксов, ищем нужный - target_checkbox = None - - # Вариант 1: По data-testid - for i in range(checkboxes.count()): - checkbox = checkboxes.nth(i) - testid = checkbox.get_attribute("data-testid") - if testid == "cabinet-bar__main__checkbox__available_ventilation_panel": - target_checkbox = checkbox - logger.debug(f"Found checkbox by data-testid: {testid}") - break - - # Вариант 2: По role="checkbox" и aria-checked - if target_checkbox is None: - for i in range(checkboxes.count()): - checkbox = checkboxes.nth(i) - role = checkbox.get_attribute("role") - if role == "checkbox": - target_checkbox = checkbox - logger.debug(f"Found checkbox by role='checkbox'") - break - - # Вариант 3: Первый найденный checkbox - if target_checkbox is None: - target_checkbox = checkboxes.first - logger.debug("Using first found checkbox") - - # Проверяем состояние - current_aria_checked = target_checkbox.get_attribute("aria-checked") - is_currently_checked = current_aria_checked == "true" - logger.debug(f"Checkbox current state: {is_currently_checked}") - - # Если уже в нужном состоянии - if is_currently_checked == value: - logger.debug(f"Checkbox already in desired state ({value})") - return True - - # Кликаем на чекбокс - target_checkbox.click(force=True) - self.wait_for_timeout(800) - - # Проверяем результат - new_aria_checked = target_checkbox.get_attribute("aria-checked") - is_now_checked = new_aria_checked == "true" - - if is_now_checked == value: - logger.info(f"✓ Checkbox '{checkbox_label}' set to {value}") - return True - else: - logger.warning(f"Checkbox state didn't change. Still: {is_now_checked}") - return False - - except Exception as e: - logger.error(f"Error setting checkbox '{checkbox_label}': {e}") - return False - - def fill_rack_data(self, rack_data: RackEditData) -> Dict[str, int]: - """ - Заполняет все доступные поля в форме редактирования. - """ - logger.debug("Filling rack edit form...") - - results = { - "text_fields_filled": 0, - "combobox_fields_filled": 0, - "checkboxes_set": 0 - } - - # Получаем доступные поля - available_fields = self.get_available_fields() - logger.debug(f"Available fields in form: {available_fields}") - - # 1. Заполняем текстовые поля (только если поле существует) - text_fields_mapping = { - "Имя": rack_data.name, - "Серийный номер": rack_data.serial, - "Инвентарный номер": rack_data.inventory, - "Комментарий": rack_data.comment, - "Выделенная мощность": rack_data.power, - "Правила доступа для чтения по умолчанию": rack_data.read_access_rules, - "Правила доступа для записи по умолчанию": rack_data.write_access_rules, - "Правила доступа по умолчанию для получения СМС": rack_data.sms_access_rules, - "Правила доступа по умолчанию для получения email сообщения": rack_data.email_access_rules, - "Правила доступа по умолчанию для получения push уведомлений": rack_data.push_access_rules - } - - for field_name, value in text_fields_mapping.items(): - if value and value.strip() and field_name in available_fields: - if self._fill_text_field(field_name, value): - results["text_fields_filled"] += 1 - - # 2. Заполняем combobox поля (только если поле существует) - combobox_fields_mapping = { - "Ввод кабеля": rack_data.cable_entry, - "Состояние": rack_data.state, - "Владелец": rack_data.owner, - "Обслуживающая организация": rack_data.service_org, - "Проект/Титул": rack_data.project - } - - for field_name, value in combobox_fields_mapping.items(): - if value and value.strip() and field_name in available_fields: - if self._fill_combobox_field(field_name, value): - results["combobox_fields_filled"] += 1 - - # 3. Устанавливаем checkbox (если есть) - if rack_data.ventilation_panel is not None: - if self._set_checkbox_field("Вентиляционная панель", rack_data.ventilation_panel): - results["checkboxes_set"] += 1 - - logger.debug(f"Fill results: {results}") - return results diff --git a/locators/confirm_locators.py b/locators/confirm_locators.py index 75c0e2f..dadbdbf 100644 --- a/locators/confirm_locators.py +++ b/locators/confirm_locators.py @@ -15,6 +15,8 @@ class ConfirmLocators: """ CONFIRM = "//div[contains(@class, 'v-dialog--active')]" - TITLE = "//div[@class='v-card__title']/h3" + TITLE = f"{CONFIRM}//div[contains(@class, 'v-card__title')]" + #TITLE = "//div[@class='v-card__title']/h3" BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']" - TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']" + #TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']" + TEXT = f"{CONFIRM}//div[contains(@class, 'v-card__text')]" diff --git a/locators/rack_locators.py b/locators/rack_locators.py index 214af2f..6b84059 100644 --- a/locators/rack_locators.py +++ b/locators/rack_locators.py @@ -31,38 +31,34 @@ class RackLocators: # Контейнер формы создания/редактирования стойки FORM_INPUT_CONTAINER = "//div[contains(@class, 'flex xs6 pa-0')]" - # Локаторы полей формы создания стойки - RACK_NAME_FIELD = ("//div[contains(@class, 'container')]" - "//label[text()='Имя']/following-sibling::input") - RACK_HEIGHT_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Высота в юнитах']]") - RACK_DEPTH_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Глубина (мм)']]") - RACK_SERIAL_FIELD = ("//div[contains(@class, 'container')]" - "//label[text()='Серийный номер']/following-sibling::input") - RACK_INVENTORY_FIELD = ("//div[contains(@class, 'container')]" - "//label[text()='Инвентарный номер']/following-sibling::input") - RACK_COMMENT_FIELD = ("//div[contains(@class, 'container')]" - "//label[text()='Комментарий']/following-sibling::input") - RACK_CABLE_ENTRY_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Ввод кабеля']]") - RACK_STATE_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Состояние']]") - RACK_OWNER_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Владелец']]") - RACK_SERVICE_ORG_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Обслуживающая организация']]") - RACK_PROJECT_FIELD = ("//div[contains(@class, 'container')]" - "//div[contains(@class, 'v-input__slot') and " - ".//label[text()='Проект/Титул']]") + # Форма редактирования стойки в модальном окне + RACK_EDIT_FORM = "[data-testid='cabinet-bar__cabinet-form']" - # Локаторы для выпадающего меню + # Локаторы полей формы + INPUT_FORM_RACK_DATA = f"{RACK_EDIT_FORM}" + INPUT_FORM_RACK_DATA_FIELD_NAME = "[data-testid='cabinet-bar__main__text-field__name']" + INPUT_FORM_RACK_DATA_FIELD_COMMENT = "[data-testid='cabinet-bar__main__text-field__comment']" + INPUT_FORM_RACK_DATA_FIELD_SERIAL = "[data-testid='cabinet-bar__main__text-field__serial_number']" + INPUT_FORM_RACK_DATA_FIELD_INVENTORY = "[data-testid='cabinet-bar__main__text-field__inventory_number']" + INPUT_FORM_RACK_DATA_FIELD_POWER = "[data-testid='cabinet-bar__main__text-field__allocated_power']" + + # Локаторы для combobox полей + INPUT_FORM_RACK_DATA_FIELD_CABLE_ENTRY = "[data-testid='cabinet-bar__select_enum__select-field__cable_input']" + INPUT_FORM_RACK_DATA_FIELD_CONDITION_TYPE = "[data-testid='cabinet-bar__select_enum__select-field__condition_type']" + INPUT_FORM_RACK_DATA_FIELD_DEPTH = "[data-testid='cabinet-bar__select_enum__select-field__depth']" + INPUT_FORM_RACK_DATA_FIELD_USIZE = "[data-testid='cabinet-bar__select_enum__select-field__usize']" + INPUT_FORM_RACK_DATA_FIELD_OWNER = "[data-testid='cabinet-bar__select__select-field__owner']" + INPUT_FORM_RACK_DATA_FIELD_SERVICE_PROVIDER = "[data-testid='cabinet-bar__select__select-field__service_provider']" + INPUT_FORM_RACK_DATA_FIELD_PROJECT = "[data-testid='cabinet-bar__select__select-field__project']" + + # Чекбоксы + INPUT_FORM_RACK_DATA_CHECKBOX_VENTILATION = "[data-testid='cabinet-bar__main__checkbox__available_ventilation_panel'] input[type='checkbox']" + + # Локаторы для меню combobox + MENU_ACTIVE_RACK_FORM = "//div[contains(@class, 'menuable__content__active')]" + MENU_ACTIVE_ITEMS = "//div[@role='list']//div[@role='listitem']" + + # Локаторы для выпадающего меню (которые использовались в старом коде) DROPDOWN_LIST = 'div.menuable__content__active div[role="list"]' DROPDOWN_ITEM_BY_TEXT = ('div.menuable__content__active ' 'div[role="listitem"]:has(span:has-text("{}"))') diff --git a/pages/rack_page.py b/pages/rack_page.py index 37947a6..fb63dc3 100644 --- a/pages/rack_page.py +++ b/pages/rack_page.py @@ -48,33 +48,27 @@ class RackPage(BasePage): show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON) self.show_button = TooltipButton(page, show_button_locator, "show_rack") - # Кнопка "Переместить" - # replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON) - # self.replace_button = TooltipButton(page, replace_button_locator, "replace") - - # Кнопка "Сохранить" - # done_button_locator = self.page.locator(RackLocators.TOOLBAR_DONE_BUTTON) - # self.done_button = TooltipButton(page, done_button_locator, "done") - - # Кнопка "Отменить" - # close_button_locator = self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON) - # self.close_button = TooltipButton(page, close_button_locator, "close") - - # Кнопка "Удалить" - # remove_button_locator = self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON) - # self.remove_button = TooltipButton(page, remove_button_locator, "remove") - self.toolbar = ToolbarComponent(page, "") self.toolbar.add_tooltip_button(locator_button, "edit") self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack") self.toolbar.add_tooltip_button(show_button_locator, "show_rack") - #self.toolbar.add_tooltip_button(replace_button_locator, "replace") - #self.toolbar.add_tooltip_button(done_button_locator, "done") - #self.toolbar.add_tooltip_button(close_button_locator, "close") - #self.toolbar.add_tooltip_button(remove_button_locator, "remove") - # Действия + + def click_edit_button(self) -> None: + """ + Кликает на кнопку 'Изменить'. + """ + logger.debug("Clicking on 'Edit' button...") + + # Проверяем видимость кнопки + self.toolbar.check_button_visibility("edit") + self.toolbar.check_button_tooltip("edit", "Изменить") + + # Кликаем на кнопку + self.toolbar.get_button_by_name("edit").click() + self.wait_for_timeout(1000) + def get_available_tabs(self) -> list[str]: """ Возвращает список доступных вкладок. diff --git a/tests/e2e/elements/test_element_rack.py b/tests/e2e/elements/test_element_rack.py index 362f7cf..0f0ba22 100644 --- a/tests/e2e/elements/test_element_rack.py +++ b/tests/e2e/elements/test_element_rack.py @@ -120,8 +120,8 @@ class TestRackTab: # Кликаем на кнопку "Изменить" rack_page.toolbar.get_button_by_name("edit").click() - # 3. Создаем экземпляр ModalEditRack - rack_edit = ModalEditRack(browser) + # 3. Создаем экземпляр ModalRackEditRack + rack_edit = ModalEditRack(browser, rack_name) # Используем метод для удаления rack_edit.click_remove_button() @@ -282,97 +282,69 @@ class TestRackTab: # Переходим в режим редактирования logger.debug("Switching to edit mode...") - rt.toolbar.check_button_visibility("edit") - rt.toolbar.get_button_by_name("edit").click() - rt.wait_for_timeout(3000) + rt.click_edit_button() + rt.wait_for_timeout(1000) # Создаем экземпляр ModalEditRack - rack_edit = ModalEditRack(browser) - rack_edit.should_be_toolbar_buttons() + rack_edit = ModalEditRack(browser, RACK_NAME) # ИЗМЕНЕНО: добавлен RACK_NAME - # Получаем список доступных полей (используем точные названия из этого списка) - available_fields = rack_edit.get_available_fields() - logger.info(f"Available fields in form: {available_fields}") + # Создаем тестовые данные для заполнения всех полей + rack_edit_data = RackEditData( + # Основные поля + name="Test-Rack-Functionality", + serial="SN123456789", + inventory="INV987654321", + comment="Тестовый комментарий для стойки (обновленный)", + allocated_power="5500", - # Создаем маппинг: используем ТОЧНЫЕ названия полей из available_fields - field_mapping = {} + # Combobox поля + cable_entry="сверху", + state="Введен в эксплуатацию", + owner="", + service_org="", + project="", - # Текстовые поля - for field_pattern, test_value in [ - ("Имя", "Test-Rack-Functionality"), - ("Серийный номер", "SN123456789"), - ("Инвентарный номер", "INV987654321"), - ("Комментарий", "Тестовый комментарий для стойки"), - ("Выделенная мощность (Вт/ВА)", "55"), - ]: - # Ищем точное совпадение - if field_pattern in available_fields: - field_mapping[field_pattern] = ("text", test_value) + # Checkbox поля + ventilation_panel=True, - # Combobox поля - for field_pattern, test_value in [ - ("Ввод кабеля", "Сверху"), - ("Состояние", "Введен в эксплуатацию"), - ("Владелец", "Тестовый владелец"), - ("Обслуживающая организация", "Тестовая сервисная организация"), - ("Проект/Титул", "Тестовый проект"), - ]: - if field_pattern in available_fields: - field_mapping[field_pattern] = ("combobox", test_value) + # Правила доступа (если есть такие поля в форме) + #read_access_rules="admin" if "Правила доступа для чтения по умолчанию" in available_fields else "", + #write_access_rules="admin" if "Правила доступа для записи по умолчанию" in available_fields else "", + #sms_access_rules="admin" if "Правила доступа по умолчанию для получения СМС" in available_fields else "", + #email_access_rules="admin" if "Правила доступа по умолчанию для получения email сообщения" in available_fields else "", + #push_access_rules="admin" if "Правила доступа по умолчанию для получения push уведомлений" in available_fields else "", + ) - # Заполняем каждое поле вручную - results = { - "text_fields_filled": 0, - "combobox_fields_filled": 0, - "checkboxes_set": 0 - } + # Заполняем все поля формы + logger.info("Filling rack form using fill_rack_data method...") + results = rack_edit.fill_rack_data(rack_edit_data) - logger.info("Filling fields individually...") - - for field_name, (field_type, value) in field_mapping.items(): - logger.info(f"Filling {field_type} field '{field_name}' with '{value}'...") - - if field_type == "text": - success = rack_edit._fill_text_field(field_name, value) - if success: - results["text_fields_filled"] += 1 - logger.info(f"✓ Text field '{field_name}' filled") - else: - logger.warning(f"✗ Failed to fill text field '{field_name}'") - - elif field_type == "combobox": - success = rack_edit._fill_combobox_field(field_name, value) - if success: - results["combobox_fields_filled"] += 1 - logger.info(f"✓ Combobox field '{field_name}' filled") - else: - logger.warning(f"✗ Failed to fill combobox field '{field_name}'") - - # Устанавливаем checkbox - test_ventilation_panel = True - logger.info("Setting ventilation panel checkbox...") - success = rack_edit._set_checkbox_field("Вентиляционная панель", test_ventilation_panel) - if success: - results["checkboxes_set"] += 1 - logger.info("✓ Checkbox set") - else: - logger.warning("✗ Failed to set checkbox") - - # Проверяем результаты logger.info(f"Fill results: {results}") - # Проверяем что хотя бы некоторые поля были заполнены - total_filled = results.get("text_fields_filled", 0) + results.get("combobox_fields_filled", 0) - assert total_filled > 0, f"No fields were filled successfully. Results: {results}" - # Сохраняем изменения logger.info("Saving changes...") - # Используем метод из ModalEditRack для кнопки "Сохранить" rack_edit.click_done_button() rack_edit.wait_for_timeout(3000) - # Проверяем выход из режима редактирования - rt.toolbar.check_button_visibility("edit") - logger.info("✓ Successfully exited edit mode") + # Проверяем поля, которые мы заполнили, действительно заполнены + logger.info("=== Проверка, что все поля корректно заполнены ===") + + # Вход в режим редактирования + rt.click_edit_button() + + # Проверяем поля, пропуская недоступные + verification_results = rack_edit.verify_all_filled_fields( + rack_edit_data, + skip_fields=["Владелец", "Обслуживающая организация", "Проект/Титул"] + ) + logger.info(f"Verification results: {verification_results}") + + # Проверяем результаты + assert verification_results["incorrectly_filled"] == 0, \ + f"Некорректно заполнены доступные поля: {verification_results['field_errors']}" + assert verification_results["not_filled"] == 0, \ + f"Не заполнены доступные поля: {verification_results['field_errors']}" + + rack_edit.click_close_button() logger.info("✓ General Info tab fields test completed")