e-nms_qa_automation/components/navbar_component.py

352 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации."""
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from elements.button_element import Button
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)
# кнопки расширения/сжатия рабочей области вкладки на странице
self.expand_workarea_button = Button(page,
page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA),
"expand_workarea_button")
self.reduce_workarea_button = Button(page,
page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA),
"reduce_workarea_button")
# Действия:
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: Текст элемента для клика.
"""
root_locator = self.get_locator(node_root_locator)
if parent:
parent_loc = self._find_and_click_item(self.page, root_locator, parent, parent=None)
found = self._find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found = self._find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found, f"Navigation panel item {item_name} is missing"
def _find_and_click_item(self, 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 = self._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
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 expand_workarea(self) -> None:
"""Нажатие кнопки для расширения рабочей области страницы"""
if self.page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA).count() > 0:
self.expand_workarea_button.click()
else:
assert False, "Workarea already expanded"
def reduce_workarea(self) -> None:
"""Нажатие кнопки для сжатия рабочей области страницы"""
if self.page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA).count() > 0:
self.reduce_workarea_button.click()
else:
assert False, "Workarea already reduced"
# Проверки:
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()
def check_sub_item_state(self, node_root_locator: str | Locator, item_name: str, parent: None|str) -> str|None:
"""Выполняет рекурсивный поиск по панели навигации
заданного элемента, делает клик по нему, проверяет наличие индикатора состояния.
Если индикатор состояния присутствует, возвращается его цвет. Иначе None"""
root_locator = self.get_locator(node_root_locator)
if parent:
parent_loc = self._find_and_click_item(self.page, root_locator, parent, parent=None)
found_node_loc = self._find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found_node_loc = self._find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found_node_loc, f"Navigation panel item {item_name} is missing"
color = None
sub_item_state_loc_str = f"//span[text()='{item_name}']/preceding-sibling::*[name()='svg'][2]"
sub_item_state_locator = found_node_loc.locator("div.v-treeview-node__label").locator(sub_item_state_loc_str)
if sub_item_state_locator.count() > 0:
color = sub_item_state_locator.get_attribute("fill")
if color: color = color.lstrip('#')
return color
def should_be_expand_workarea_button(self) -> None:
"""Проверяет наличие кнопки расширения рабочей области страницы.
Raises:
AssertionError: Если кнопка отсутствует.
"""
if self.page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA).count() > 0:
self.expand_workarea_button.check_visibility(
"Expand workarea button is missing on page"
)
else:
assert False, "Expand workarea button is missing on page"
def should_be_reduce_workarea_button(self) -> None:
"""Проверяет наличие кнопки сжатия рабочей области страницы.
Raises:
AssertionError: Если кнопка отсутствует.
"""
if self.page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA).count() > 0:
self.reduce_workarea_button.check_visibility(
"Rduce workarea button is missing on page"
)
else:
assert False, "Reduce workarea button is missing on page"