262 lines
14 KiB
Python
262 lines
14 KiB
Python
"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации."""
|
||
|
||
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 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 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:
|
||
"""Кликает по вложенному элементу с указанным текстом."""
|
||
|
||
def find_and_click_item(page, root_locator, item_name: str, parent: None|str) -> bool:
|
||
"""Рекурсивно ищет элемент в дереве и кликает по нему."""
|
||
|
||
# Если указан родитель, сначала находим его
|
||
if parent:
|
||
# Ищем родительский элемент
|
||
parent_found = 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
|
||
|
||
# Получаем текст контента для точного сравнения
|
||
content_locator = node.locator('div.v-treeview-node__content')
|
||
if content_locator.count() > 0:
|
||
content_text = content_locator.first.inner_text().strip()
|
||
|
||
# Ищем родителя в тексте контента
|
||
if parent == content_text:
|
||
parent_found = True
|
||
|
||
# Раскрываем родительский элемент если нужно
|
||
toggle_buttons = node.locator(NavigationPanelLocators.TOGGLE_BUTTON)
|
||
if toggle_buttons.count() > 0:
|
||
class_attr = toggle_buttons.first.get_attribute('class')
|
||
is_expanded = class_attr and "v-treeview-node__toggle--open" in class_attr
|
||
|
||
if not is_expanded:
|
||
toggle_buttons.first.click()
|
||
page.wait_for_timeout(1000)
|
||
|
||
# После раскрытия ждем появления дочерних элементов
|
||
page.wait_for_timeout(500)
|
||
|
||
# Ищем целевой элемент как непосредственного ребенка родителя
|
||
children_locator = node.locator('>div.v-treeview-node__children')
|
||
if children_locator.count() > 0:
|
||
# Ищем среди непосредственных детей
|
||
direct_children = children_locator.locator('>div.v-treeview-node')
|
||
direct_children_count = direct_children.count()
|
||
|
||
for child_index in range(direct_children_count):
|
||
child_node = direct_children.nth(child_index)
|
||
child_content = child_node.locator('div.v-treeview-node__content')
|
||
if child_content.count() > 0:
|
||
child_text = child_content.first.inner_text().strip()
|
||
|
||
if child_text == item_name:
|
||
child_content.first.click()
|
||
return True
|
||
|
||
# Если не нашли среди непосредственных детей, ищем рекурсивно
|
||
for child_index in range(direct_children_count):
|
||
child_node = direct_children.nth(child_index)
|
||
found = find_and_click_item(page, children_locator, item_name, parent=None)
|
||
if found:
|
||
return True
|
||
break
|
||
|
||
if not parent_found:
|
||
# Рекурсивный поиск родителя в дочерних элементах
|
||
for index in range(nodes_count):
|
||
node = root_locator.locator(f">div:nth-child({index + 1})").first
|
||
|
||
# Проверяем, имеет ли узел дочерние элементы
|
||
toggle_buttons = node.locator(NavigationPanelLocators.TOGGLE_BUTTON)
|
||
if toggle_buttons.count() > 0:
|
||
class_attr = toggle_buttons.first.get_attribute('class')
|
||
is_expanded = class_attr and "v-treeview-node__toggle--open" in class_attr
|
||
|
||
if not is_expanded:
|
||
toggle_buttons.first.click()
|
||
page.wait_for_timeout(500)
|
||
|
||
children_locator = node.locator('>div.v-treeview-node__children')
|
||
if children_locator.count() > 0:
|
||
found = find_and_click_item(page, children_locator, item_name, parent)
|
||
if found:
|
||
return True
|
||
|
||
return False
|
||
|
||
else:
|
||
# Поиск без указания родителя
|
||
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
|
||
|
||
# Получаем только текст контента
|
||
content_locator = node.locator('div.v-treeview-node__content')
|
||
if content_locator.count() > 0:
|
||
content_text = content_locator.first.inner_text().strip()
|
||
|
||
if content_text == item_name:
|
||
content_locator.first.click()
|
||
return True
|
||
|
||
# Рекурсивный поиск в дочерних элементах
|
||
node_class_attr = node.get_attribute('class')
|
||
if "v-treeview-node--leaf" not in node_class_attr:
|
||
toggle_buttons = node.locator(NavigationPanelLocators.TOGGLE_BUTTON)
|
||
if toggle_buttons.count() > 0:
|
||
class_attr = toggle_buttons.first.get_attribute('class')
|
||
is_expanded = class_attr and "v-treeview-node__toggle--open" in class_attr
|
||
|
||
if not is_expanded:
|
||
toggle_buttons.first.click()
|
||
page.wait_for_timeout(500)
|
||
|
||
children_locator = node.locator('>div.v-treeview-node__children')
|
||
if children_locator.count() > 0:
|
||
found = find_and_click_item(page, children_locator, item_name, parent=None)
|
||
if found:
|
||
return True
|
||
|
||
return False
|
||
|
||
root_locator = self.get_locator(node_root_locator)
|
||
found = find_and_click_item(self.page, root_locator, item_name, parent)
|
||
assert found, f"Navigation panel item {item_name} is missing"
|
||
|
||
def traverse_panel_tree(self, node_root_locator: str | Locator, level=0, debug=False):
|
||
"""
|
||
Рекурсивно обходит дерево v-treeview и выводит информацию об элементах в режиме отладки (debug=True).
|
||
|
||
Args:
|
||
node_root_locator: Локатор для поиска корневых элементов дерева (Локатор элемента или строка с CSS/XPath).
|
||
"""
|
||
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:
|
||
print(f'[{level}][{index}] {node_text} (LEAF, Expanded: {is_expanded}, 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:
|
||
# Выводим информацию об узле
|
||
print(f'[{level}][{index}] {edited_node_text} (NODE, Expanded: {is_expanded}, 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)
|