From e88c9b2a1b8719863ac0602cd55440b0ce43d637 Mon Sep 17 00:00:00 2001 From: nsubbot Date: Thu, 28 Aug 2025 10:57:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B0=D1=8F=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B0=D0=B6=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=8F=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=D1=85=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D0=BD=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=B3=D0=B0=D1=86=D0=B8=D0=B8=20=D1=81=20=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D1=83=D1=80=D1=81=D0=B8=D0=B2=D0=BD=D1=8B=D0=BC=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=BE=D0=BC=20=D0=BF=D0=BE=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=D0=BC=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/navbar_component.py | 80 ++++++++++++++---- pages/main_page.py | 21 ++--- tests/components/test_json_container.py | 3 +- tests/components/test_services_table.py | 4 +- tests/components/test_user_modal_window.py | 2 +- tests/e2e/test_expand_navigation_panel.py | 30 +++++++ tests/e2e/test_license_tab.py | 3 +- tests/e2e/test_service_status_tab.py | 4 +- tests/e2e/test_sessions_tab.py | 97 ++++++++++++++-------- tests/e2e/test_users_tab.py | 36 ++++---- 10 files changed, 187 insertions(+), 93 deletions(-) diff --git a/components/navbar_component.py b/components/navbar_component.py index 7119d90..69384d6 100644 --- a/components/navbar_component.py +++ b/components/navbar_component.py @@ -46,29 +46,79 @@ class NavigationPanelComponent(BaseComponent): loc = self.get_locator(locator) loc.get_by_text(item_name).click() - def click_sub_item(self, locator: str | Locator, sublevel_number: int, item_name: str) -> None: + def click_sub_item(self, node_root_locator, item_name: str) -> None: """Кликает по вложенному элементу с указанным текстом. Args: - locator: Локатор родительского элемента. - sublevel_number: Уровень вложенности (1 или 2). + node_root_locator: Локатор для поиска корневых элементов дерева (Локатор элемента или строка с CSS/XPath). item_name: Текст элемента для клика. - - Raises: - ValueError: Если уровень вложенности не 1 или 2. """ - root_locator = self.get_locator(NavigationPanelLocators.NODE_ROOT) - children_locator = self.get_locator(NavigationPanelLocators.NODE_CHILDREN) + def find_and_click_item(page, root_locator, item_name: str) -> bool: + # Находим все локаторы корневых узлов на текущем уровне + root_node = root_locator.locator('>div.v-treeview-node') + # Получаем список текстов + root_node_texts = root_node.all_inner_texts() - loc = self.get_locator(locator) + # Если искомый элемент находится на данном уровне, вычисляем локатор и делаем клик + for index, node_text in enumerate(root_node_texts): + node_text = node_text.replace("expand_more\n", "") + if item_name == node_text: + root_node.nth(index).click() + return True - if sublevel_number == 1: - loc.locator(root_locator).get_by_text(item_name).click() - elif sublevel_number == 2: - loc.locator(children_locator).locator(root_locator).get_by_text(item_name).click() - else: - raise ValueError("the navigation panel has two levels of nesting only") + # Если элемента нет, рекурсивно ищем дальше + 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_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.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 + + # Рекурсивный вызов для дочерних элементов + # Ищем дочерние элементы *внутри* текущего узла + if has_children and is_expanded: + child_nodes_locator = root_locator.locator(f">div:nth-child({index + 1})").locator('>div.v-treeview-node__children') + is_found = find_and_click_item(page, child_nodes_locator, item_name) + if is_found: + return True + + # закрываем узел, если в нем ничего не нашли + if is_expanded: + toggle_button = node.locator(NavigationPanelLocators.TOGGLE_BUTTON) + toggle_button.click() + + # элемент с заданным именем не найден + return False + + root_locator = self.get_locator(node_root_locator) + found = find_and_click_item(self.page, root_locator, item_name) + assert found, f"Navigation panel item {item_name} is missing" def traverse_panel_tree(self, node_root_locator, level=0, debug=False): """ diff --git a/pages/main_page.py b/pages/main_page.py index bd199bb..a2b093f 100644 --- a/pages/main_page.py +++ b/pages/main_page.py @@ -49,23 +49,14 @@ class MainPage(BasePage): self.navigation_panel.click_item(NavigationPanelLocators.PANEL_MAIN, item_name) - def click_configuration_navigation_panel_item(self, item_name: str) -> None: - """Кликает по элементу подраздела 'Конфигурация'. + def click_subpanel_item(self, item_name: str) -> None: + """Выполняет рекурсивный поиск по панели навигации заданного элемента и делает клик по нему.""" - Args: - item_name: Название элемента для клика. - """ + active_item_locator = self.page.locator(NavigationPanelLocators.PANEL_MAIN).locator(NavigationPanelLocators.ACTIVE_CONTAINER) + node_locator = active_item_locator.locator(NavigationPanelLocators.SUB_PANEL_MAIN).locator(NavigationPanelLocators.TREEVIEW).first - self.navigation_panel.click_sub_item(NavigationPanelLocators.PANEL_MAIN, 1, item_name) - - def click_maintenance_navigation_panel_item(self, item_name: str) -> None: - """Кликает по элементу подраздела 'Обслуживание'. - - Args: - item_name: Название элемента для клика. - """ - - self.navigation_panel.click_sub_item(NavigationPanelLocators.PANEL_MAIN, 2, item_name) + # Рекурсивный поиск в дереве v-treeview заданного элемента и клик по нему + self.navigation_panel.click_sub_item(node_locator, item_name) def click_user_button(self) -> None: """Кликает по кнопке пользователя.""" diff --git a/tests/components/test_json_container.py b/tests/components/test_json_container.py index 048cf48..ed1fb8d 100644 --- a/tests/components/test_json_container.py +++ b/tests/components/test_json_container.py @@ -28,8 +28,7 @@ class TestJsonContainer: mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") - mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") - mp.click_configuration_navigation_panel_item("Лицензии") + mp.click_subpanel_item("Лицензии") def test_verticall_scrolling(self, browser: Page) -> None: """Проверяет вертикальную прокрутку в контейнере с JSON-данными. diff --git a/tests/components/test_services_table.py b/tests/components/test_services_table.py index 0cc8e6a..9724220 100644 --- a/tests/components/test_services_table.py +++ b/tests/components/test_services_table.py @@ -27,8 +27,8 @@ class TestServiceStatusTable: mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") - mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") - mp.click_maintenance_navigation_panel_item("Статус обслуживания") + mp.click_subpanel_item("Обслуживание и диагностика") + mp.click_subpanel_item("Статус обслуживания") def test_scrolling(self, browser: Page) -> None: """Проверяет прокрутку таблицы статусов сервисов. diff --git a/tests/components/test_user_modal_window.py b/tests/components/test_user_modal_window.py index 9fe905b..570725d 100644 --- a/tests/components/test_user_modal_window.py +++ b/tests/components/test_user_modal_window.py @@ -27,7 +27,7 @@ class TestUsersModalWindow: mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") @pytest.mark.develop def test_edit_user_window_scrolling(self, browser: Page) -> None: diff --git a/tests/e2e/test_expand_navigation_panel.py b/tests/e2e/test_expand_navigation_panel.py index 963b624..e31475e 100644 --- a/tests/e2e/test_expand_navigation_panel.py +++ b/tests/e2e/test_expand_navigation_panel.py @@ -45,3 +45,33 @@ class TestNavigationPanel: mp.wait_for_timeout(300) mp.expand_navigation_subpanel() + + def test_sub_panel_item_click(self, browser: Page): + """Проверяет возможность клика заданного элемента в подпанели навигации. + + Args: + browser: Фикстура для работы с браузером. + + """ + + # Действия: + lp = LoginPage(browser) + lp.do_login() + + # Мы на главной странице + mp = MainPage(browser) + + # Проверки: + # Проверяем наличие панели навигации + mp.should_be_navigation_panel() + + # Открываем все пункты панели + mp.click_main_navigation_panel_item("Настройки") + + mp.click_subpanel_item("Обслуживание и диагностика") + mp.click_subpanel_item("Статус обслуживания") + + mp.wait_for_timeout(500) + + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") diff --git a/tests/e2e/test_license_tab.py b/tests/e2e/test_license_tab.py index b50de61..c9b95a7 100644 --- a/tests/e2e/test_license_tab.py +++ b/tests/e2e/test_license_tab.py @@ -29,8 +29,7 @@ class TestLicenseTab: mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") - mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") - mp.click_configuration_navigation_panel_item("Лицензии") + mp.click_subpanel_item("Лицензии") def test_lisence_tab_content(self, browser: Page) -> None: """Проверяет содержимое вкладки 'Лицензии'. diff --git a/tests/e2e/test_service_status_tab.py b/tests/e2e/test_service_status_tab.py index 6d9773b..3f71af2 100644 --- a/tests/e2e/test_service_status_tab.py +++ b/tests/e2e/test_service_status_tab.py @@ -36,10 +36,10 @@ class TestServiceStatusTab: mp.click_main_navigation_panel_item("Настройки") # Клик по пункту 'Обслуживание и диагностика' в панели навигации настроек - mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") + mp.click_subpanel_item("Обслуживание и диагностика") # Клик по пункту 'Статус обслуживания' в панели навигации обслуживания - mp.click_maintenance_navigation_panel_item("Статус обслуживания") + mp.click_subpanel_item("Статус обслуживания") def test_service_status_tab_content(self, browser: Page): """Проверяет содержимое вкладки 'Статус обслуживания'. diff --git a/tests/e2e/test_sessions_tab.py b/tests/e2e/test_sessions_tab.py index daff057..2759be4 100644 --- a/tests/e2e/test_sessions_tab.py +++ b/tests/e2e/test_sessions_tab.py @@ -1,3 +1,10 @@ +"""Модуль тестов вкладки 'Сеансы'. + +Содержит тесты для проверки функциональности +работы с пользовательтскими сессиями. +""" +from typing import Dict +from playwright.sync_api import Page from pages.login_page import LoginPage from pages.main_page import MainPage from pages.session_tab import SessionsTab @@ -23,16 +30,34 @@ class TestSessionsTab: # Авторизация в системе login_page = LoginPage(browser) login_page.do_login() - + # Инициализация главной страницы main_page = MainPage(browser) - + # Проверка и взаимодействие с элементами навигации main_page.should_be_navigation_panel() main_page.click_main_navigation_panel_item("Настройки") - main_page.click_configuration_navigation_panel_item("Обслуживание и диагностика") - main_page.click_maintenance_navigation_panel_item("Сеансы") - + main_page.click_subpanel_item("Обслуживание и диагностика") + main_page.click_subpanel_item("Сеансы") + + @pytest.fixture(scope="function") + def cleanup_user(self, browser: Page) -> None: + """Фикстура для очистки пользователя NewUser после теста.""" + + yield + + mp = MainPage(browser) + mp.click_subpanel_item("Пользователи") + ut = UsersTab(browser) + + # Удаляем тестового пользователя после выполнения теста + user_data: Dict[str, str] = {"name": "NewUser", "role": "Администратор"} + + # Проверяем существует ли пользователь и удаляем его + if ut.find_user_in_table(user_data["name"], user_data["role"]) != -1: + ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) + ut.delete_user(user_data["name"]) + def test_sessions_tab_content(self, browser): """Тест содержимого вкладки 'Сеансы'. @@ -47,7 +72,7 @@ class TestSessionsTab: # Проверка элементов интерфейса sessions_tab.should_be_toolbar() sessions_tab.should_be_sessions_table() - + # Проверка содержимого таблицы с верификацией данных из БД sessions_tab.check_sessions_table_content(verify=True) @@ -66,10 +91,10 @@ class TestSessionsTab: # Получение количества строк в таблице без учета заголовка rows_count = sessions_tab.get_rows_count() - + # Проверка подсветки первой строки sessions_tab.check_sessions_table_row_highlighting(0) - + # Проверка подсветки последней строки строки (если в таблице более одной строки) if rows_count > 1: sessions_tab.check_sessions_table_row_highlighting(rows_count - 1) @@ -81,18 +106,18 @@ class TestSessionsTab: 1. Наличие таблицы сеансов 2. Проверка контента и возможности горизонтального скроллинга окна подтверждения удаления сессии """ - + # Инициализация страницы сеансов sessions_tab = SessionsTab(browser) # Проверка элементов интерфейса sessions_tab.should_be_sessions_table() - + # Проверка контента и скроллинга окна подтверждения удаления сессии - sessions_tab.check_delete_session_confirm_window() - - #@pytest.mark.develop - def test_session_for_new_user(self, browser): + sessions_tab.check_delete_session_confirm_window() + + @pytest.mark.develop + def test_session_for_new_user(self, browser, cleanup_user): """Тест содержимого вкладки 'Сеансы'. Проверяет: @@ -108,64 +133,64 @@ class TestSessionsTab: mp = MainPage(browser) ut = UsersTab(browser) - + # Создание нового пользователя - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") ut.open_add_user_window() ut.add_new_user(user_data) - + # Обновление списка пользователей (двойной клик - возможно баг?) - mp.click_configuration_navigation_panel_item("Пользователи") - mp.click_configuration_navigation_panel_item("Пользователи") - + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + # Проверка наличия пользователя в таблице ut.should_be_user_in_table(user_data["name"], user_data["role"]) - + # Вход в систему для нового пользователя new_lp = LoginPage(browser) new_lp.do_login(username=user_data["name"], password=user_data["password"]) - + # Инициализация главной страницы new_mp = MainPage(browser) - + # Открыть вкладку Сессии new_mp.should_be_navigation_panel() new_mp.click_main_navigation_panel_item("Настройки") - new_mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") - new_mp.click_maintenance_navigation_panel_item("Сеансы") - + new_mp.click_subpanel_item("Обслуживание и диагностика") + new_mp.click_subpanel_item("Сеансы") + # Инициализация страницы сеансов st = SessionsTab(browser) # Проверка элементов интерфейса st.should_be_sessions_table() - + # Проверка наличия записи о сессии текущего пользователя session_token = st.get_session_token() st.should_be_session_in_table(session_token) - + # logout new_mp.do_logout() - + # Авторизация в системе предыдущего пользователя prev_lp = LoginPage(browser) prev_lp.do_login() - + # Инициализация главной страницы prev_mp = MainPage(browser) - + # Открыть вкладку Сессии prev_mp.should_be_navigation_panel() prev_mp.click_main_navigation_panel_item("Настройки") - prev_mp.click_configuration_navigation_panel_item("Обслуживание и диагностика") - prev_mp.click_maintenance_navigation_panel_item("Сеансы") - + prev_mp.click_subpanel_item("Обслуживание и диагностика") + prev_mp.click_subpanel_item("Сеансы") + # Проверка элементов интерфейса st.should_be_sessions_table() - + # Проверка отсутствия записи о сессии созданного пользователя после выхода из системы st.should_not_be_session_in_table(session_token) - + # Удаление созданного пользователя prev_ut = UsersTab(browser) prev_ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) diff --git a/tests/e2e/test_users_tab.py b/tests/e2e/test_users_tab.py index 0485712..50d0a28 100644 --- a/tests/e2e/test_users_tab.py +++ b/tests/e2e/test_users_tab.py @@ -28,7 +28,7 @@ class TestUsersTab: mp = MainPage(browser) mp.should_be_navigation_panel() mp.click_main_navigation_panel_item("Настройки") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") @pytest.fixture(scope="function") def cleanup_user(self, browser: Page) -> None: @@ -40,8 +40,8 @@ class TestUsersTab: # Проверяем существует ли пользователь и удаляем его if ut.find_user_in_table(user_data["name"], user_data["role"]) != -1: - ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) - ut.delete_user(user_data["name"]) + ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) + ut.delete_user(user_data["name"]) @pytest.fixture(scope="function") def cleanup_autoadmin(self, browser: Page) -> None: @@ -129,9 +129,9 @@ class TestUsersTab: """ ut = UsersTab(browser) - user_name, role = ut.open_edit_user_page_by_index(0) + user_name, _ = ut.open_edit_user_page_by_index(0) ut.close_edit_user_window_by_toolbar_button(user_name) - user_name, role = ut.open_edit_user_page_by_index(0) + user_name, _ = ut.open_edit_user_page_by_index(0) ut.close_edit_user_window(user_name) def test_add_and_delete_user(self, browser: Page, cleanup_user) -> None: @@ -177,8 +177,8 @@ class TestUsersTab: ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) ut.delete_user(user_data["name"]) - mp.click_configuration_navigation_panel_item("Пользователи") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") ut.should_not_be_user_in_table(user_data["name"], user_data["role"]) def test_reset_password(self, browser: Page, cleanup_autoadmin) -> None: @@ -196,8 +196,8 @@ class TestUsersTab: ut.open_add_user_window() ut.add_new_user(user_data) - mp.click_configuration_navigation_panel_item("Пользователи") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) new_password = ut.reset_password(user_data["name"]) @@ -213,12 +213,12 @@ class TestUsersTab: mp_1 = MainPage(browser) mp_1.should_be_navigation_panel() mp_1.click_main_navigation_panel_item("Настройки") - mp_1.click_configuration_navigation_panel_item("Пользователи") + mp_1.click_subpanel_item("Пользователи") ut_1 = UsersTab(browser) ut_1.open_edit_user_page_by_user(user_data["name"], user_data["role"]) ut_1.delete_user(user_data["name"]) - mp_1.click_configuration_navigation_panel_item("Пользователи") - mp_1.click_configuration_navigation_panel_item("Пользователи") + mp_1.click_subpanel_item("Пользователи") + mp_1.click_subpanel_item("Пользователи") ut_1.should_not_be_user_in_table(user_data["name"], user_data["role"]) def test_edit_user_role(self, browser: Page, cleanup_autooperator) -> None: @@ -236,16 +236,16 @@ class TestUsersTab: ut.open_add_user_window() ut.add_new_user(user_data) - mp.click_configuration_navigation_panel_item("Пользователи") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) new_user_data = {"role": "Контактное лицо"} ut.edit_user(user_data["name"], new_user_data) - mp.click_configuration_navigation_panel_item("Пользователи") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") ut.should_be_user_in_table(user_data["name"], new_user_data["role"]) ut.open_edit_user_page_by_user(user_data["name"], new_user_data["role"]) ut.delete_user(user_data["name"]) - mp.click_configuration_navigation_panel_item("Пользователи") - mp.click_configuration_navigation_panel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"])