"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации.""" from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.navigation_panel_locators import NavigationPanelLocators from components.base_component import BaseComponent logger = get_logger("NAVIGATION_PANEL") class NavigationPanelComponent(BaseComponent): """Компонент панели навигации. Предоставляет методы для взаимодействия с ней.""" def __init__(self, page: Page): """Инициализирует компонент панели навигации. Args: page: Экземпляр страницы Playwright. """ super().__init__(page) # Действия: def click_item(self, locator: str | Locator, item_name: str) -> None: """Кликает по элементу с указанным текстом. Args: locator: Локатор элемента или строка с CSS/XPath. item_name: Текст элемента для клика. """ loc = self.get_locator(locator) loc.get_by_text(item_name).click() def click_sub_item(self, node_root_locator: str | Locator, item_name: str, parent: None|str) -> None: """Кликает по вложенному элементу с указанным текстом. Args: node_root_locator: Локатор для поиска корневых элементов дерева. item_name: Текст элемента для клика. """ def find_and_click_item(page, root_locator, item_name: str, parent: None|str) -> Locator|None: # Находим все локаторы корневых узлов на текущем уровне nodes_count = root_locator.locator('>div.v-treeview-node').count() # Если искомый элемент находится на данном уровне, вычисляем локатор и делаем клик if parent is None: for index in range(nodes_count): node = root_locator.locator(f">div:nth-child({index + 1})").first node_content = node.locator('div.v-treeview-node__content') if node_content.count() > 0: node_text = node_content.first.inner_text().strip() if item_name == node_text: node_attr = node.get_attribute('class') if "v-treeview-node--leaf" not in node_attr: toggle_button = node.locator( NavigationPanelLocators.NODE_ROOT ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first toogle_class_attr = toggle_button.get_attribute('class') if "v-treeview-node__toggle--open" not in toogle_class_attr: toggle_button.click() else: node.locator(NavigationPanelLocators.NODE_ROOT).click() page.wait_for_timeout(1000) return node # Если элемента нет, рекурсивно ищем дальше for index in range(nodes_count): node = root_locator.locator(f">div:nth-child({index + 1})").first # Извлекаем аттрибуты из корневого узла node_class_attr = node.get_attribute('class') is_expanded = False has_children = False # Проверяем лист это или начало поддерева if "v-treeview-node--leaf" not in node_class_attr: # Проверяем, является ли узел раскрытым class_attr = node.locator( NavigationPanelLocators.NODE_ROOT ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first.get_attribute('class') if "v-treeview-node__toggle--open" in class_attr: is_expanded = True # Если узел закрыт можем его раскрыть if is_expanded is False: toggle_button = node.locator( NavigationPanelLocators.NODE_ROOT ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first toggle_button.click() # Ждем, пока дочерние элементы прогрузятся/появятся page.wait_for_timeout(1000) is_expanded = True # Проверяем, имеет ли узел дочерние элементы children_count = node.locator('>div.v-treeview-node__children').count() content = node.locator('>div.v-treeview-node__children').inner_html() if children_count > 0 and len(content) != 0: has_children = True # Рекурсивный вызов для дочерних элементов # Ищем дочерние элементы *внутри* текущего узла if has_children and is_expanded: child_nodes_locator = root_locator.locator( f">div:nth-child({index + 1})" ).locator('>div.v-treeview-node__children') found_loc = find_and_click_item( page, child_nodes_locator, item_name, parent=None ) if found_loc: if parent is None: return found_loc else: root_texts = root_locator.locator( f">div:nth-child({index + 1})" ).inner_text().splitlines() if parent in root_texts: return found_loc # закрываем узел, если в нем ничего не нашли if is_expanded: toggle_button = node.locator( NavigationPanelLocators.NODE_ROOT ).locator(NavigationPanelLocators.TOGGLE_BUTTON).first toggle_button.click() page.wait_for_timeout(1000) # элемент с заданным именем не найден return None root_locator = self.get_locator(node_root_locator) if parent: parent_loc = find_and_click_item(self.page, root_locator, parent, parent=None) found = find_and_click_item( self.page, parent_loc.locator('>div.v-treeview-node__children'), item_name, parent=None ) else: found = find_and_click_item(self.page, root_locator, item_name, parent=None) assert found, f"Navigation panel item {item_name} is missing" def get_item_names(self, locator: str | Locator) -> list[str]: """Возвращает тексты всех элементов по указанному локатору. Args: locator: Локатор элементов или строка с CSS/XPath. Returns: Список текстов элементов. """ loc = self.get_locator(locator) return loc.all_inner_texts() def traverse_panel_tree(self, node_root_locator: str | Locator, level=0, debug=False): """ Рекурсивно обходит дерево v-treeview и выводит информацию об элементах. Args: node_root_locator: Локатор для поиска корневых элементов дерева. """ def traverse_tree(page, root_locator, level=0, debug=False): # Находим все локаторы корневых узлов на текущем уровне nodes_count = root_locator.locator('>div.v-treeview-node').count() for index in range(nodes_count): node = root_locator.locator(f">div:nth-child({index + 1})").first # Извлекаем текст и аттрибуты из корневого узла node_text = node.inner_text() node_class_attr = node.get_attribute('class') is_expanded = False has_children = False # Проверяем лист это или начало поддерева if "v-treeview-node--leaf" in node_class_attr: if debug: leaf_msg = f'[{level}][{index}] {node_text} (LEAF, Expanded: {is_expanded}' print(f"{leaf_msg}, Has Children: {has_children})") print("-----------------------------------------") else: # Проверяем, является ли узел раскрытым class_attr = node.locator(NavigationPanelLocators.TOGGLE_BUTTON).get_attribute('class') if "v-treeview-node__toggle--open" in class_attr: is_expanded = True # Если узел закрыт можем его раскрыть if is_expanded is False: toggle_button = node.locator(NavigationPanelLocators.TOGGLE_BUTTON) toggle_button.click() # Ждем, пока дочерние элементы прогрузятся/появятся page.wait_for_timeout(300) is_expanded = True # Проверяем, имеет ли узел дочерние элементы children_count = node.locator('>div.v-treeview-node__children').count() content = node.locator('>div.v-treeview-node__children').inner_html() if children_count > 0 and len(content) != 0: has_children = True edited_node_text = node_text.replace("expand_more\n", "") if debug: # Выводим информацию об узле node_msg = f'[{level}][{index}] {edited_node_text} (NODE, Expanded: {is_expanded}' print(f"{node_msg}, Has Children: {has_children})") print("-----------------------------------------") # Рекурсивный вызов для дочерних элементов # Ищем дочерние элементы *внутри* текущего узла if has_children and is_expanded: child_nodes_locator = root_locator.locator( f">div:nth-child({index + 1})" ).locator('>div.v-treeview-node__children') traverse_tree(page, child_nodes_locator, level+1, debug) root_locator = self.get_locator(node_root_locator) traverse_tree(self.page, root_locator, level=level, debug=debug) # Проверки: def check_item_visibility(self, locator: str | Locator, item_name: str) -> None: """Проверяет видимость элемента с указанным текстом. Args: locator: Локатор элемента или строка с CSS/XPath. item_name: Текст элемента для проверки. Note: Временная обработка для элементов с текстом 'Шаблоны'. """ msg = f"Navigation panel item '{item_name}' is not visible" ## временно: в навигационной панели есть две панели с именем Шаблоны ## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны ## Шаблоны_2 для Настройки/ZTP/Шаблоны loc = self.get_locator(locator) if item_name == "Шаблоны_1": loc = loc.get_by_text("Шаблоны").first elif item_name == "Шаблоны_2": loc = loc.get_by_text("Шаблоны").nth(1) else: loc = loc.get_by_text(item_name) self.check_visibility(loc, msg) def is_item_visible(self, locator: str | Locator, item_name: str) -> bool: """ Проверяет видимость элемента с указанным текстом без выбрасывания исключения. Args: locator: Локатор элемента или строка с CSS/XPath. item_name: Текст элемента для проверки. Returns: bool: True если элемент видим, False если нет. """ element_locator = self.page.locator(locator).filter(has_text=item_name) # Сначала проверяем что элемент вообще существует if element_locator.count() == 0: return False return element_locator.is_visible()