"""Модуль для работы с модальным окном редактирования стойки.""" 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