"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации."""
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()