"""Модуль фрейма создания дочернего элемента.""" import re from playwright.sync_api import expect, Page, Locator from tools.logger import get_logger from locators.rack_locators import RackLocators from locators.selection_bar_locators import SelectionBarLocators from components.alert_component import AlertComponent from components.base_component import BaseComponent from components.toolbar_component import ToolbarComponent from components_derived.selection_bar_component import SelectionBarComponent logger = get_logger("CREATE_CHILD_ELEMENT_FRAME") logger.setLevel("INFO") class CreateChildElementFrame(BaseComponent): """Фрейм создания дочернего элемента.""" def __init__(self, page: Page) -> None: """ Инициализирует фрейм создания дочернего элемента. Args: page: Экземпляр страницы Playwright """ super().__init__(page) # Инициализация компонентов self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в") self.selection_bar = SelectionBarComponent(page, "Класс объекта учета") self.alert = AlertComponent(page) # Кнопка "Добавить" - первая кнопка в тулбаре фрейма создания add_button_locator = self.page.get_by_role("navigation").filter( has_text="Создать дочерний элемент в" ).get_by_role("button").nth(0) # Кнопка "Отменить" - используем рабочий локатор из старой версии cancel_button_locator = self.page.get_by_role("navigation").filter( has_text=re.compile('Создать дочерний элемент в') ).get_by_role("button").nth(1) # Инициализация кнопок self.toolbar.add_tooltip_button(add_button_locator, "add") self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") # Действия: def clear_combobox_field(self, field_name: str) -> None: """ Очищает combobox поле по его названию. Args: field_name: Название поля для очистки """ logger.debug(f"Clearing combobox field '{field_name}'...") # Получаем контейнер формы container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) fields_locators = self.get_input_fields_locators(container_locator) if field_name not in fields_locators: logger.warning(f"Field '{field_name}' not found in form") return # Получаем контейнер поля field_container = fields_locators[field_name] # Прокручиваем до поля field_container.scroll_into_view_if_needed() self.wait_for_timeout(300) # Проверяем видимость if not field_container.is_visible(): logger.debug(f"Field '{field_name}' is not visible after scrolling") return # Ищем кнопку закрытия (крестик) внутри контейнера поля close_button = field_container.locator("i.mdi-close").first # Проверяем наличие и видимость кнопки закрытия if close_button.count() > 0: logger.debug(f"Found close button for field '{field_name}'") # Если кнопка закрытия видима - кликаем на нее close_button.click(force=True) self.wait_for_timeout(300) logger.debug(f"Combobox field '{field_name}' cleared using close button") else: logger.debug(f"Close button (i.mdi-close) not found for field '{field_name}'") def click_add_button(self) -> None: """Кликает на кнопку 'Добавить'.""" logger.debug("Clicking on 'Add' button...") self.toolbar.click_button("add") def click_cancel_button(self) -> None: """Кликает на кнопку 'Отменить'.""" logger.debug("Clicking on 'Cancel' button...") self.toolbar.click_button("cancel") def get_selected_object_class(self) -> str: """ Получает выбранный класс объекта учета. Returns: str: Выбранный класс объекта или пустая строка если ничего не выбрано """ return self.selection_bar.get_selection_bar_title() def is_field_filled(self, field_name: str, container_locator: Locator = None) -> bool: """ Проверяет, заполнено ли combobox или текстовое поле. Args: field_name: Название поля для проверки container_locator: Локатор контейнера формы (опционально) Returns: bool: True если поле заполнено, False в противном случае """ logger.debug(f"Checking if field '{field_name}' is filled...") # Если контейнер не передан, используем контейнер по умолчанию if container_locator is None: container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) # Получаем словарь всех полей формы fields_locators = self.get_input_fields_locators(container_locator) if field_name not in fields_locators: logger.debug(f"Field '{field_name}' not found in fields_locators") return False # Получаем контейнер поля field_container = fields_locators[field_name] if not field_container.is_visible(): logger.debug(f"Field '{field_name}' not visible") return False # Проверяем наличие выбранного значения через v-chip (чип выбранного значения в combobox) selected_chip = field_container.locator(".v-chip").first # Проверяем наличие текста в поле field_text = field_container.text_content() or "" has_text = bool(field_text.strip()) # Проверяем наличие чипа has_chip = selected_chip.count() > 0 and selected_chip.is_visible() # Для текстовых полей проверяем значение input if not has_chip: input_field = field_container.locator("input").first if input_field.count() > 0: input_value = input_field.input_value() or "" has_input_value = bool(input_value.strip()) logger.debug(f"Field '{field_name}' - has input value: {has_input_value}") has_text = has_text or has_input_value logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}") return has_chip or has_text def open_object_class_combobox(self) -> None: """Открывает выпадающий список combobox.""" container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER) fields_locators = self.get_input_fields_locators(container_locator) combobox_container = fields_locators.get("Класс объекта учета") if not combobox_container: logger.error("Combobox 'Класс объекта учета' not found") return # Проверяем, не открыт ли уже выпадающий список menu_selector = "div.v-menu__content.menuable__content__active" is_menu_open = self.page.locator(menu_selector).count() > 0 if not is_menu_open: # Используем OPEN_PARAMETERS_LIST_BUTTON из SelectionBarLocators open_button = combobox_container.locator(SelectionBarLocators.OPEN_PARAMETERS_LIST_BUTTON) open_button.click(force=True, timeout=5000) else: logger.debug("Combobox menu is already open") def select_object_class(self, class_name: str) -> None: """Выбирает класс объекта из выпадающего списка.""" logger.debug(f"Selecting object class: '{class_name}'...") # Открываем combobox self.open_object_class_combobox() # Выбирает значение из списка self.selection_bar.select_value(class_name) # Даем время на применение выбора self.wait_for_timeout(300) logger.debug(f"Object class '{class_name}' successfully selected") # Проверки: def check_field_error_highlighted(self, field_name: str) -> None: """ Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). Args: field_name: Название поля для проверки """ logger.debug(f"Checking field '{field_name}' for error highlighting...") # Получаем контейнеры всех полей container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER) fields_locators = self.get_input_fields_locators(container_locator) # Получаем контейнер конкретного поля field_container = fields_locators.get(field_name) if not field_container: raise ValueError(f"Field '{field_name}' not found in form") # Ищем элементы с классами ошибки внутри контейнера поля error_elements = field_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS) # Проверяем, что есть хотя бы один элемент с классом ошибки has_error = error_elements.count() > 0 assert has_error, ( f"Field '{field_name}' has no elements with error classes. " f"Expected to find elements matching: {SelectionBarLocators.ERROR_CSS_SELECTORS}" ) logger.debug(f"Field '{field_name}' is correctly highlighted with error color") def check_field_error_not_highlighted(self, field_name: str) -> None: """ Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). Args: field_name: Название поля для проверки """ logger.debug(f"Checking field '{field_name}' for absence of error highlighting...") # Получаем контейнеры всех полей container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER) fields_locators = self.get_input_fields_locators(container_locator) # Получаем контейнер конкретного поля field_container = fields_locators.get(field_name) if not field_container: raise ValueError(f"Field '{field_name}' not found in form") # Ищем элементы с классами ошибки внутри контейнера поля error_elements = field_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS) # Проверяем, что нет элементов с классами ошибки has_error = error_elements.count() > 0 assert not has_error, ( f"Field '{field_name}' has {error_elements.count()} elements with error classes. " f"Expected no elements matching: {SelectionBarLocators.ERROR_CSS_SELECTORS}" ) logger.debug(f"Field '{field_name}' correctly has no error highlighting") def check_object_class_selected(self, expected_class: str) -> None: """ Проверяет что выбран указанный класс объекта. Args: expected_class: Ожидаемый выбранный класс объекта """ logger.debug(f"Checking selected object class: '{expected_class}'...") self.wait_for_timeout(500) actual_class = self.get_selected_object_class() is_match = (expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower()) assert is_match, ( f"Selected class does not match expected. " f"Expected: '{expected_class}', Got: '{actual_class}'" ) logger.debug( f"Object class '{expected_class}' successfully selected " f"(actual: '{actual_class}')" ) def check_toolbar_title(self, expected_title: str) -> None: """ Проверяет заголовок тулбара. Args: expected_title: Ожидаемый заголовок тулбара """ logger.debug(f"Checking toolbar title: '{expected_title}'...") # Используем метод тулбара с фильтрацией по тексту actual_text = self.toolbar.get_toolbar_title_text( filter_text="Создать дочерний элемент в" ) assert expected_title in actual_text, ( f"Title does not match. Expected: '{expected_title}', " f"Got: '{actual_text}'" ) logger.debug(f"Toolbar title is correct: '{actual_text}'") def should_be_toolbar_buttons(self) -> None: """ Проверяет наличие и функциональность кнопок тулбара. """ self.toolbar.check_button_visibility("add") self.toolbar.check_button_tooltip("add", "Добавить") self.toolbar.check_button_visibility("cancel") self.toolbar.check_button_tooltip("cancel", "Отменить") self.toolbar.click_button("cancel") self.wait_for_timeout(500)