From d874700abec0f39c500dd0d6a771c48ea9b4d47c Mon Sep 17 00:00:00 2001 From: Radislav Date: Sat, 1 Nov 2025 13:55:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=BA=D1=82=D1=83=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locators/rack_locators.py | 87 + mkdocs.yml | 12 +- pages/rack_pages/rack_tab.py | 466 ++ site/404.html | 196 +- site/components/alert_component/index.html | 196 +- site/components/base_component/index.html | 196 +- site/components/card_component/index.html | 196 +- site/components/confirm_component/index.html | 196 +- .../dropdown_list_component/index.html | 196 +- site/components/eventbar_component/index.html | 196 +- .../events_container_component/index.html | 644 ++- .../json_container_component/index.html | 196 +- .../modal_window_component/index.html | 196 +- site/components/navbar_component/index.html | 460 +- site/components/table_component/index.html | 817 ++- site/components/toolbar_component/index.html | 691 ++- .../container_system_log_events/index.html | 198 +- .../dialog_user_settings/index.html | 3903 ------------- .../modal_add_AD_user/index.html | 292 +- .../modal_add_local_user/index.html | 474 +- .../modal_change_password/index.html | 242 +- .../modal_edit_user/index.html | 268 +- .../modal_view_template/index.html | 196 +- .../modal_view_ztp_template/index.html | 196 +- site/components_derived/user_card/index.html | 460 +- site/config/add_docstring/index.html | 196 +- .../code_development_process/index.html | 196 +- site/config/mkdocs_guide/index.html | 196 +- site/data/constants/index.html | 196 +- site/data/environment/index.html | 196 +- site/data/roles_dict/index.html | 196 +- site/elements/base_element/index.html | 220 +- site/elements/button_element/index.html | 196 +- site/elements/checkbox_element/index.html | 196 +- site/elements/icon_element/index.html | 196 +- site/elements/tab_button_element/index.html | 196 +- site/elements/text_element/index.html | 196 +- site/elements/text_input_element/index.html | 705 ++- .../tooltip_button_element/index.html | 196 +- site/fixtures/pages/index.html | 196 +- site/index.html | 196 +- site/locators/button_locators/index.html | 196 +- site/locators/confirm_locators/index.html | 196 +- site/locators/event_panel_locators/index.html | 196 +- site/locators/input_locators/index.html | 196 +- .../json_container_locators/index.html | 196 +- .../locators/modal_window_locators/index.html | 204 +- .../navigation_panel_locators/index.html | 198 +- site/locators/rack_locators/index.html | 2927 ++++++++++ .../settings_form_locators/index.html | 2782 ++++++++++ site/locators/table_locators/index.html | 198 +- site/locators/text_input_locators/index.html | 196 +- site/locators/text_locators/index.html | 196 +- site/locators/toolbar_locators/index.html | 196 +- site/locators/user_card_locators/index.html | 196 +- site/objects.inv | Bin 5571 -> 5819 bytes site/pages/base_page/index.html | 196 +- .../index.html | 418 +- site/pages/license_tab/index.html | 196 +- site/pages/login_page/index.html | 196 +- site/pages/main_page/index.html | 196 +- site/pages/service_status_tab/index.html | 198 +- site/pages/session_settings_tab/index.html | 4909 +++++++++++++++++ site/pages/templates_tab/index.html | 198 +- site/pages/users_tab/index.html | 320 +- site/pages/ztp_config_tab/index.html | 196 +- site/pages/ztp_templates_tab/index.html | 198 +- site/search/search_index.json | 2 +- site/sitemap.xml.gz | Bin 127 -> 127 bytes .../components/test_json_container/index.html | 196 +- .../test_navigation_panel/index.html | 196 +- .../components/test_services_table/index.html | 196 +- .../test_user_modal_window/index.html | 196 +- site/tests/e2e/rack/test_rack_tab/index.html | 3285 +++++++++++ .../test_current_sessions_tab}/index.html | 1020 ++-- .../test_session_settings_tab/index.html | 3388 ++++++++++++ site/tests/e2e/test_event_panel/index.html | 196 +- .../test_expand_navigation_panel/index.html | 572 +- site/tests/e2e/test_license_tab/index.html | 196 +- site/tests/e2e/test_login/index.html | 212 +- .../e2e/test_service_status_tab/index.html | 198 +- .../index.html | 282 +- site/tests/e2e/test_templates_tab/index.html | 196 +- site/tests/e2e/test_ztp_config_tab/index.html | 232 +- .../e2e/test_ztp_templates_tab/index.html | 196 +- site/tests/e2e/users/test_add_user/index.html | 276 +- .../tests/e2e/users/test_edit_user/index.html | 300 +- .../tests/e2e/users/test_user_card/index.html | 552 +- .../tests/e2e/users/test_users_tab/index.html | 202 +- site/tools/fix_python_project/index.html | 196 +- site/tools/logger/index.html | 196 +- tests/e2e/rack/test_rack_tab.py | 114 + 92 files changed, 33151 insertions(+), 9577 deletions(-) create mode 100644 locators/rack_locators.py create mode 100644 pages/rack_pages/rack_tab.py delete mode 100644 site/components_derived/dialog_user_settings/index.html create mode 100644 site/locators/rack_locators/index.html create mode 100644 site/locators/settings_form_locators/index.html rename site/pages/{session_tab => current_session_tab}/index.html (94%) create mode 100644 site/pages/session_settings_tab/index.html create mode 100644 site/tests/e2e/rack/test_rack_tab/index.html rename site/tests/e2e/{test_sessions_tab => sessions/test_current_sessions_tab}/index.html (88%) create mode 100644 site/tests/e2e/sessions/test_session_settings_tab/index.html create mode 100644 tests/e2e/rack/test_rack_tab.py diff --git a/locators/rack_locators.py b/locators/rack_locators.py new file mode 100644 index 0000000..17d4ccd --- /dev/null +++ b/locators/rack_locators.py @@ -0,0 +1,87 @@ +"""Модуль rack_locators содержит локаторы элементов страницы стойки. + +Класс RackLocators хранит XPath локаторы для взаимодействия +с элементами интерфейса стойки оборудования в тестах. +""" + +class RackLocators: + """Класс для хранения локаторов элементов страницы стойки. + + Содержит локаторы в формате XPath для поиска элементов: + - Вкладки стойки (верхние вкладки) + - Секции лицевой и обратной сторон стойки + - Юниты и устройства на стойке + - Поля формы редактирования стойки + - Контейнеры и структурные элементы + """ + + # Основной контейнер вкладок стойки (верхние вкладки) + TABS_CONTAINER = "//div[@class='v-tabs__container']" + + # Все элементы верхних вкладок стойки + ALL_TABS = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item')]" + + # Универсальный локатор для любой вкладки по имени + TAB_BY_NAME = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., '{}')]" + + # Конкретные вкладки по тексту + GENERAL_INFO_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'Общая информация')]" + MAINTENANCE_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'Обслуживание')]" + EVENTS_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'События')]" + SERVICES_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'Сервисы')]" + + # Классы для проверки активности + ACTIVE_TAB_CLASSES = ["accent--text", "v-tabs__item--active"] + + # Локатор для активной вкладки + ACTIVE_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item--active')]" + + # Контейнер формы + FORM_CONTAINER = "//div[contains(@class, 'container')]" + + # Локаторы полей + NAME_FIELD = "//input[@aria-label='Имя']" + SERIAL_NUMBER_FIELD = "//input[@aria-label='Серийный номер']" + INVENTORY_NUMBER_FIELD = "//input[@aria-label='Инвентарный номер']" + CABLE_ENTRY_FIELD = "//input[@aria-label='Ввод кабеля']" + STATUS_FIELD = "//input[@aria-label='Состояние']" + HEIGHT_FIELD = "//input[@aria-label='Высота в юнитах']" + OWNER_FIELD = "//input[@aria-label='Владелец']" + SERVICE_ORG_FIELD = "//input[@aria-label='Обслуживающая организация']" + PROJECT_FIELD = "//input[@aria-label='Проект/Титул']" + + # Локаторы для отображения сторон стойки + FRONT_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and not(contains(@class, 'back'))]" + BACK_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and contains(@class, 'back')]" + + FRONT_SIDE_TITLE = "//span[contains(@class, 'subheading') and contains(text(), 'Лицевая сторона')]" + BACK_SIDE_TITLE = "//span[contains(@class, 'subheading') and contains(text(), 'Обратная сторона')]" + + # Юниты на лицевой стороне + FRONT_SIDE_UNITS = ".cabinet:not(.back) .unit, .unit:not(.back)" + + # Юниты на обратной стороне + BACK_SIDE_UNITS = ".cabinet.back .unit" + + # Локатор для всех юнитов + ALL_UNITS = ".unit" + + # Устройства на лицевой стороне + FRONT_SIDE_DEVICES = "//*[contains(@class, 'parent-class')]" + + # Устройства на обратной стороне + BACK_SIDE_DEVICES = "//*[contains(@class, 'parent-class')]" + + # Позиции юнитов + UNIT_POSITIONS = "//div[contains(@class, 'unit-positions')]" + + # Контейнер с обеими сторонами + SIDES_CONTAINER = "//div[contains(@class, 'layout row shrink fill-height ma-0 pa-0')]" + + # ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОРОН + # Основные секции сторон + FRONT_SIDE_SECTION = "//span[contains(text(), 'Лицевая сторона')]//ancestor::div[contains(@class, 'flex shrink')]" + BACK_SIDE_SECTION = "//span[contains(text(), 'Обратная сторона')]//ancestor::div[contains(@class, 'flex shrink')]" + + # Основной контейнер стойки + MAIN_CONTAINER = "//div[contains(@class, 'layout row')]" diff --git a/mkdocs.yml b/mkdocs.yml index 7c1de62..7e35ab7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,13 +44,12 @@ nav: - ToolbarComponent: components/toolbar_component.md - Компоненты производные UI: - SystemLogEventsContainer: components_derived/container_system_log_events.md - - UserSettingsDialogWindow: components_derived/dialog_user_settings.md - AddADUserModalWindow: components_derived/modal_add_AD_user.md - AddLocalUserModalWindow: components_derived/modal_add_local_user.md - ChangePasswordModalWindow: components_derived/modal_change_password.md - EditUserModalWindow: components_derived/modal_edit_user.md - ViewTemplateModalWindow: components_derived/modal_view_template.md - - ViewZTPTemplateModalWindow: components_derived/modal_view_ztp_template.md #new + - ViewZTPTemplateModalWindow: components_derived/modal_view_ztp_template.md - UserCard: components_derived/user_card.md - Локаторы: - ButtonLocators: locators/button_locators.md @@ -60,6 +59,8 @@ nav: - JsonContainerLocators: locators/json_container_locators.md - ModalWindowLocators: locators/modal_window_locators.md - NavigationPanelLocators: locators/navigation_panel_locators.md + - RackLocators: locators/rack_locators.md # new + - SettingsFormLocators: locators/settings_form_locators.md # new - TableLocators: locators/table_locators.md - TextInputLocators: locators/text_input_locators.md - TextLocators: locators/text_locators.md @@ -71,13 +72,17 @@ nav: - LoginPage: pages/login_page.md - MainPage: pages/main_page.md - ServiceStatusTab: pages/service_status_tab.md - - SessionTab: pages/session_tab.md + - CurrentSessionsTab: pages/current_session_tab.md # new + - SessionSettingsTab: pages/session_settings_tab.md # new - TemplatesTab: pages/templates_tab.md - UsersTab: pages/users_tab.md - ZTPConfigTab: pages/ztp_config_tab.md - ZTPTemplatesTab: pages/ztp_templates_tab.md - Тесты: - End-to-End: + - Sessions: + - TestCurrentSessionsTab: tests/e2e/sessions/test_current_sessions_tab.md # new + - TestCurrentSettingsTab: tests/e2e/sessions/test_session_settings_tab.md # new - Users: - TestUsersTabAddUser: tests/e2e/users/test_add_user.md - TestUsersTabEditUser: tests/e2e/users/test_edit_user.md @@ -88,7 +93,6 @@ nav: - TestLicenseTab: tests/e2e/test_license_tab.md - TestLogin: tests/e2e/test_login.md - TestServiceStatusTab: tests/e2e/test_service_status_tab.md - - TestSessionTab: tests/e2e/test_sessions_tab.md - TestSystemLogEventsContainer: tests/e2e/test_system_log_events_container.md - TestTemplatesTab: tests/e2e/test_templates_tab.md - TestZTPConfigTab: tests/e2e/test_ztp_config_tab.md diff --git a/pages/rack_pages/rack_tab.py b/pages/rack_pages/rack_tab.py new file mode 100644 index 0000000..01f722a --- /dev/null +++ b/pages/rack_pages/rack_tab.py @@ -0,0 +1,466 @@ +"""Модуль тестов вкладки 'Стойка'. + +Содержит тесты для проверки функциональности +работы со стойкой оборудования. +""" + +from playwright.sync_api import Page, expect +from elements.tooltip_button_element import TooltipButton +from components.toolbar_component import ToolbarComponent +from pages.base_page import BasePage +from locators.rack_locators import RackLocators +from tools.logger import get_logger + +logger = get_logger("RACK_TAB") + +# Специфичные локаторы оставленые в основном коде +PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i" +TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']" +EDIT_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button" +PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]" + + +class RackTab(BasePage): + """Класс для работы с вкладкой стойки оборудования.""" + + def __init__(self, page: Page) -> None: + """ + Инициализирует объект вкладки стойки. + + Args: + page: Экземпляр страницы Playwright + """ + super().__init__(page) + + locator_button = self.page.locator(PANEL_HEADER).\ + locator(EDIT_BUTTON_ANCESTOR_DIV3).nth(0) + self.edit_button = TooltipButton(page, locator_button, "edit") + + self.toolbar = ToolbarComponent(page, "") + self.toolbar.add_tooltip_button(locator_button, "edit") + + def wait_for_rack_loading(self, timeout: int = 15000) -> None: + """ + Ожидает загрузки интерфейса стойки. + + Args: + timeout: Время ожидания в миллисекундах (по умолчанию 15000) + + Raises: + TimeoutError: Если загрузка не завершилась в указанное время + """ + logger.info("Ожидание загрузки интерфейса стойки...") + + # Ждем появления основного контейнера + main_container = self.page.locator(RackLocators.MAIN_CONTAINER) + expect(main_container).to_be_visible(timeout=timeout) + + # Ждем появления юнитов + units = self.page.locator(RackLocators.ALL_UNITS) + expect(units).to_have_count(20, timeout=timeout) + + logger.info("Интерфейс стойки загружен") + + def get_toolbar_title(self) -> list[str]: + """ + Получает заголовок панели инструментов. + + Returns: + list[str]: Список элементов заголовка панели инструментов + """ + toolbar_title_locator = self.page.locator(PANEL_HEADER).\ + locator(PANEL_HEADER_ANCESTOR_DIV2).get_by_role("navigation").\ + locator(TOOLBAR_CONTENT) + + return self.toolbar.get_toolbar_composite_title_text(toolbar_title_locator) + + def switch_to_tab(self, tab_name: str) -> None: + """ + Переключается на указанную вкладку. + + Args: + tab_name: Название вкладки для переключения + + Raises: + AssertionError: Если вкладка не найдена или недоступна + """ + logger.info(f"Переключение на вкладку '{tab_name}'...") + + tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) + + if tab.count() == 0: + raise AssertionError(f"Вкладка '{tab_name}' не найдена") + + # Проверяем активность ДО клика + if self.is_tab_active(tab_name): + logger.info(f"Вкладка '{tab_name}' уже активна") + return + + # Находим первую видимую вкладку с нужным именем + target_tab = None + for i in range(tab.count()): + element = tab.nth(i) + if element.is_visible() and element.is_enabled(): + target_tab = element + break + + if not target_tab: + raise AssertionError(f"Не найдена видимая/доступная вкладка '{tab_name}'") + + # Кликаем на вкладку + logger.info(f"Клик на вкладку '{tab_name}'...") + target_tab.click() + + # Ждем изменения активной вкладки + self._wait_for_tab_activation(tab_name) + + # Ждем загрузки контента + self.page.wait_for_timeout(500) + + def switch_to_general_info_tab(self) -> None: + """Переключается на вкладку 'Общая информация'.""" + self.switch_to_tab("Общая информация") + + def switch_to_maintenance_tab(self) -> None: + """Переключается на вкладку 'Обслуживание'.""" + self.switch_to_tab("Обслуживание") + + def switch_to_events_tab(self) -> None: + """Переключается на вкладку 'События'.""" + self.switch_to_tab("События") + + def switch_to_services_tab(self) -> None: + """Переключается на вкладку 'Сервисы'.""" + self.switch_to_tab("Сервисы") + + def is_tab_active(self, tab_name: str) -> bool: + """ + Проверяет, активна ли указанная вкладка. + + Args: + tab_name: Название вкладки для проверки + + Returns: + bool: True если вкладка активна, False в противном случае + """ + # Метод 1: Проверяем по активному классу и тексту, метод быстый, если надо универсальный оставояем метод 2 - медленный + active_tab = self.page.locator(RackLocators.ACTIVE_TAB) + + if active_tab.count() > 0 and active_tab.first.is_visible(): + active_text = active_tab.first.text_content() + if active_text and active_text.strip() == tab_name: + logger.info(f"Вкладка '{tab_name}' активна (через класс активной вкладки)") + return True + + # Метод 2: Проверяем по классам у конкретной вкладки + tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) + + if tab.count() > 0: + for i in range(tab.count()): + element = tab.nth(i) + if element.is_visible() and element.is_enabled(): + element_class = element.get_attribute("class") or "" + is_active = any( + active_class in element_class + for active_class in RackLocators.ACTIVE_TAB_CLASSES + ) + + if is_active: + logger.info(f"Вкладка '{tab_name}' активна (классы: {element_class})") + return True + + logger.info(f"Вкладка '{tab_name}' не активна") + return False + + def get_available_tabs(self) -> list[str]: + """ + Возвращает список доступных вкладок используя DOM структуру. + + Returns: + list[str]: Список названий доступных вкладок + """ + tabs = [] + + # Используем локатор для верхних вкладок + tab_elements = self.page.locator(RackLocators.ALL_TABS) + + # Ждем появления элементов + tab_elements.first.wait_for(state="visible", timeout=5000) + + total_count = tab_elements.count() + logger.info(f"Всего найдено элементов верхних вкладок: {total_count}") + + for i in range(total_count): + element = tab_elements.nth(i) + + # Проверяем видимость и доступность элемента + if element.is_visible() and element.is_enabled(): + tab_text = element.text_content() + if tab_text: + tab_text = tab_text.strip() + if tab_text and tab_text not in tabs: + tabs.append(tab_text) + logger.info(f"Найдена верхняя вкладка: '{tab_text}'") + + logger.info(f"Найдены доступные верхние вкладки: {tabs}") + return tabs + + def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None: + """ + Ожидает активации вкладки. + + Args: + tab_name: Название вкладки для ожидания + timeout: Время ожидания в миллисекундах + + Raises: + AssertionError: Если вкладка не активирована в течение таймаута + """ + logger.info(f"Ожидание активации вкладки '{tab_name}'...") + + start_time = self.page.evaluate("Date.now()") + while self.page.evaluate("Date.now()") - start_time < timeout: + if self.is_tab_active(tab_name): + logger.info(f"Вкладка '{tab_name}' успешно активирована") + return + self.page.wait_for_timeout(100) + + raise AssertionError(f"Вкладка '{tab_name}' не активирована в течение {timeout}мс") + + def should_be_toolbar_buttons(self) -> None: + """ + Проверяет наличие и функциональность кнопок тулбара. + + Raises: + AssertionError: Если кнопки недоступны или подсказки неверны. + """ + logger.info("Проверка кнопок панели инструментов...") + + self.toolbar.check_button_visibility("edit") + self.toolbar.check_button_tooltip("edit", "Изменить") + self.toolbar.get_button_by_name("edit").click() + + def should_be_rack_sides_displayed(self) -> None: + """ + Проверка отображения и структуры сторон стойки. + + Raises: + AssertionError: Если стороны стойки не отображаются корректно + """ + logger.info("Проверка отображения и структуры сторон стойки...") + + # Ожидаем загрузки + self.wait_for_rack_loading() + + # БАЗОВАЯ ПРОВЕРКА: обе стороны отображаются + logger.info("--- Базовая проверка отображения сторон ---") + + front_side_section = self.page.locator(RackLocators.FRONT_SIDE_SECTION).first + expect(front_side_section).to_be_visible(timeout=10000) + logger.info("Секция лицевой стороны найдена") + + back_side_section = self.page.locator(RackLocators.BACK_SIDE_SECTION).first + expect(back_side_section).to_be_visible(timeout=10000) + logger.info("Секция обратной стороны найдена") + + # Проверяем заголовки + front_side_title = front_side_section.locator(RackLocators.FRONT_SIDE_TITLE) + expect(front_side_title).to_be_visible(timeout=5000), "Заголовок 'Лицевая сторона' не отображается" + logger.info("Заголовок 'Лицевая сторона' отображается") + + back_side_title = back_side_section.locator(RackLocators.BACK_SIDE_TITLE) + expect(back_side_title).to_be_visible(timeout=5000), "Заголовок 'Обратная сторона' не отображается" + logger.info("Заголовок 'Обратная сторона' отображается") + + # Проверяем позиции юнитов + unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) + total_positions = unit_positions.count() + logger.info(f"Всего позиций юнитов: {total_positions}") + assert total_positions > 0, "Не найдено позиций юнитов" + + # Детальная проверка лицевой стороны + logger.info("--- Детальная проверка лицевой стороны ---") + self._check_front_side_details(front_side_section) + + # Детальная проверка обратной стороны + logger.info("--- Детальная проверка обратной стороны ---") + self._check_back_side_details(back_side_section) + + logger.info("Все проверки сторон стойки пройдены успешно") + + def _check_front_side_details(self, front_side_section) -> None: + """ + Проверка структуры лицевой стороны стойки. + + Args: + front_side_section: Локатор секции лицевой стороны + + Raises: + AssertionError: Если структура лицевой стороны некорректна + """ + # Проверяем юниты в секции лицевой стороны + front_side_units = front_side_section.locator(RackLocators.FRONT_SIDE_UNITS) + unit_count = front_side_units.count() + logger.info(f"Найдено юнитов на лицевой стороне: {unit_count}") + assert unit_count >= 1, f"Не найдено юнитов на лицевой стороне. Ожидалось минимум 1, найдено {unit_count}" + + # Проверяем наличие устройств на лицевой стороне + front_side_devices = front_side_section.locator(RackLocators.FRONT_SIDE_DEVICES) + device_count = front_side_devices.count() + logger.info(f"Найдено физических устройств на лицевой стороне: {device_count}") + + if device_count > 0: + for i in range(device_count): + device = front_side_devices.nth(i) + device_title = device.get_attribute("title") + device_classes = device.get_attribute("class") or "" + logger.info(f" Устройство {i}: title='{device_title}', classes='{device_classes}'") + + def _check_back_side_details(self, back_side_section) -> None: + """ + Проверка структуры обратной стороны стойки. + + Args: + back_side_section: Локатор секции обратной стороны + + Raises: + AssertionError: Если структура обратной стороны некорректна + """ + # Проверяем юниты в секции обратной стороны + back_side_units = back_side_section.locator(RackLocators.BACK_SIDE_UNITS) + unit_count = back_side_units.count() + logger.info(f"Найдено юнитов на обратной стороне: {unit_count}") + assert unit_count >= 1, f"Не найдено юнитов на обратной стороне. Ожидалось минимум 1, найдено {unit_count}" + + # Проверяем наличие устройств на обратной стороне + back_side_devices = back_side_section.locator(RackLocators.BACK_SIDE_DEVICES) + device_count = back_side_devices.count() + logger.info(f"Найдено физических устройств на обратной стороне: {device_count}") + + if device_count > 0: + for i in range(device_count): + device = back_side_devices.nth(i) + device_title = device.get_attribute("title") + device_classes = device.get_attribute("class") or "" + logger.info(f" Устройство {i}: title='{device_title}', classes='{device_classes}'") + + def should_be_header_panel(self, expected_toolbar_title_items: list[str]) -> None: + """ + Проверяет наличие и корректность заголовка панели. + + Args: + expected_toolbar_title_items: Ожидаемые элементы заголовка + + Raises: + AssertionError: Если заголовок панели не соответствует ожиданиям + """ + panel_header_locator = self.page.locator(PANEL_HEADER) + expect(panel_header_locator).to_be_visible(), "Panel header 'Объекты'" + + if panel_header_locator.inner_text() != 'chevron_right': + assert False, "No separator 'chevron_right' after header 'Объекты'" + + actual_toolbar_title_items = self.get_toolbar_title() + + self.check_lists_equals(actual_toolbar_title_items, + expected_toolbar_title_items, + f"Miscomparison actual {actual_toolbar_title_items} and expected {expected_toolbar_title_items}") + + self.toolbar.check_button_visibility("edit") + + + def check_tab_switching(self) -> None: + """ + Проверяет переключение между вкладками стойки в соответствии с локаторами. + + Raises: + AssertionError: Если переключение на одну или более вкладок не удалось + """ + logger.info("Тестирование функциональности переключения вкладок стойки...") + + # Вкладки + defined_tabs = [ + "Общая информация", + "Обслуживание", + "События", + "Сервисы" + ] + + logger.info(f"Тестируемые определенные вкладки: {defined_tabs}") + + successful_switches = 0 + failed_switches = [] + + # Тестируем переключение на каждую определенную вкладку + for tab_name in defined_tabs: + logger.info(f"Тестирование переключения на вкладку '{tab_name}'...") + + # Проверяем существование локатора для этой вкладки + tab_locator = RackLocators.TAB_BY_NAME.format(tab_name) + tab_elements = self.page.locator(tab_locator) + + # Проверяем наличие элементов через count() + if tab_elements.count() == 0: + logger.warning(f"Вкладка '{tab_name}' не найдена на странице") + failed_switches.append(f"Вкладка '{tab_name}' не найдена") + continue + + # Находим видимую и доступную вкладку + target_tab = None + for i in range(tab_elements.count()): + element = tab_elements.nth(i) + # Проверки видимости и доступности + if element.is_visible() and element.is_enabled(): + target_tab = element + break + + if not target_tab: + logger.warning(f"Не найдена видимая/доступная вкладка '{tab_name}'") + failed_switches.append(f"Вкладка '{tab_name}' не видима/не доступна") + continue + + # Переключаемся на вкладку + logger.info(f"Переключение на вкладку '{tab_name}'...") + + # Проверяем активность ДО клика + if self.is_tab_active(tab_name): + logger.info(f"Вкладка '{tab_name}' уже активна") + successful_switches += 1 + continue + + # Кликаем на вкладку с таймаутом + target_tab.click(timeout=5000) + + # Ждем изменения активной вкладки + self._wait_for_tab_activation(tab_name) + + # Проверяем, что вкладка активна + if not self.is_tab_active(tab_name): + logger.warning(f"Вкладка '{tab_name}' не активна после переключения") + failed_switches.append(f"Вкладка '{tab_name}' не активна после клика") + continue + + logger.info(f"Успешно переключено на вкладку '{tab_name}'") + successful_switches += 1 + + # Небольшая пауза между переключениями для стабильности + self.page.wait_for_timeout(1000) + + # Формируем итоговый отчет + logger.info("=== РЕЗУЛЬТАТЫ ПЕРЕКЛЮЧЕНИЯ ВКЛАДОК ===") + logger.info(f"Успешных переключений: {successful_switches}/{len(defined_tabs)}") + + if failed_switches: + logger.info("Неудачные переключения:") + for failure in failed_switches: + logger.info(f" - {failure}") + + # Требуем успешного переключения на все определенные вкладки + if successful_switches < len(defined_tabs): + raise AssertionError( + f"Тест переключения вкладок не пройден. " + f"Только {successful_switches} из {len(defined_tabs)} определенных вкладок переключены успешно. " + f"Ошибки: {', '.join(failed_switches)}" + ) + + logger.info(f"Все {successful_switches} определенных вкладок успешно переключены!") diff --git a/site/404.html b/site/404.html index f79137d..e907570 100644 --- a/site/404.html +++ b/site/404.html @@ -931,27 +931,6 @@ -
  • - - - - - UserSettingsDialogWindow - - - - -
  • - - - - - - - - - -
  • @@ -1288,6 +1267,48 @@ +
  • + + + + + RackLocators + + + + +
  • + + + + + + + + + + +
  • + + + + + SettingsFormLocators + + + + +
  • + + + + + + + + + +
  • @@ -1541,11 +1562,32 @@
  • - + - SessionTab + CurrentSessionsTab + + + + +
  • + + + + + + + + + + +
  • + + + + + SessionSettingsTab @@ -1737,7 +1779,7 @@ - Users + Sessions @@ -1746,6 +1788,91 @@ + +
  • + + + + + + + + + + + + + + +
  • + + + + + + + + +