271 lines
14 KiB
Python
271 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 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()
|
||
node_texts = node_text.splitlines()
|
||
if len(node_texts) > 1:
|
||
node_text = node_texts[1]
|
||
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
|
||
|
||
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()
|