From b2d73a3a0571bc19d888368026b4fdd32cbb4c1e Mon Sep 17 00:00:00 2001 From: Radislav Date: Sat, 1 Nov 2025 10:24:21 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B8=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=B9=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/e2e/rack/test_rack_tab.py: Тесты функциональности стойки - pages/rack_pages/rack_tab.py: Page Object для работы со стойкой - locators/rack_locators.py: Локаторы элементов стойки --- locators/rack_locators.py | 87 ++++++ pages/rack_pages/rack_tab.py | 466 ++++++++++++++++++++++++++++++++ tests/e2e/rack/test_rack_tab.py | 114 ++++++++ 3 files changed, 667 insertions(+) create mode 100644 locators/rack_locators.py create mode 100644 pages/rack_pages/rack_tab.py 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/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/tests/e2e/rack/test_rack_tab.py b/tests/e2e/rack/test_rack_tab.py new file mode 100644 index 0000000..9a45969 --- /dev/null +++ b/tests/e2e/rack/test_rack_tab.py @@ -0,0 +1,114 @@ +"""Модуль тестов вкладки 'Стойка' в модуле Объекты. + +Содержит тесты для проверки функциональности +работы со стойкой оборудования. +""" +import pytest +from playwright.sync_api import Page +from pages.rack_pages.rack_tab import RackTab +from pages.login_page import LoginPage +from pages.main_page import MainPage + + +# @pytest.mark.smoke +class TestRackTab: + """Набор тестов для вкладки 'Стойка' в модуле Объекты. + + Проверяет корректность отображения, функциональность элементов интерфейса + и переключение между вкладками стойки оборудования. + + Тесты покрывают следующие функциональные области: + 1. test_rack_tab_content - Базовая структура и содержимое вкладки стойки + 2. test_rack_tab_switching - Функциональность переключения между вкладками стойки + """ + + @pytest.fixture(scope="function", autouse=True) + def setup(self, browser: Page) -> None: + """Фикстура для подготовки тестового окружения. + + Выполняет: + 1. Авторизацию в системе + 2. Переход к стойке оборудования через панель навигации: + - Объекты → Физические устройства с опросом → Здание ЦОД 4 → Стойка КСПД + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + # Авторизация в системе + lp = LoginPage(browser) + lp.do_login() + + # Мы на главной странице + mp = MainPage(browser) + mp.should_be_navigation_panel() + mp.wait_for_timeout(3000) + + # Переходим к Объектам + mp.click_main_navigation_panel_item("Объекты") + mp.wait_for_timeout(3000) + + mp.click_subpanel_item("Физические устройства с опросом") + mp.wait_for_timeout(3000) + + # Переходим Здание ЦОД 4 + mp.click_subpanel_item("Здание ЦОД 4") + mp.wait_for_timeout(3000) + + # Переходим к Стойка КСПД с указанием родителя + mp.click_subpanel_item("Стойка КСПД", parent="Здание ЦОД 4") + mp.wait_for_timeout(10000) + + @pytest.mark.develop + def test_rack_tab_content(self, browser: Page) -> None: + """Тест содержимого вкладки 'Стойка'. + + Проверяет: + 1. Наличие и корректность заголовка панели с навигационной цепочкой + 2. Отображение и структуру обеих сторон стойки (лицевой и обратной) + 3. Наличие и функциональность кнопок панели инструментов + 4. Корректность отображения юнитов и устройств на стойке + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + expected_toolbar_subtitles = [ + "Мониторинг и инвентаризация", + 'chevron_right', + "Физические устройства с опросом", + 'chevron_right', + "Здание ЦОД 4", + 'chevron_right', + "Стойка КСПД" + ] + + rt = RackTab(browser) + rt.should_be_header_panel(expected_toolbar_subtitles) + + # Комплексная проверка отображения обеих сторон стойки с детальной информацией + rt.should_be_rack_sides_displayed() + + # Переход в режим редактирования + rt.should_be_toolbar_buttons() + rt.wait_for_timeout(2000) + + def test_rack_tab_switching(self, browser: Page) -> None: + """Тест переключения между вкладками стойки оборудования. + + Проверяет функциональность переключения на все доступные вкладки: + 1. Общая информация + 2. Обслуживание + 3. События + 4. Сервисы + + Проверяет: + 1. Наличие и доступность всех вкладок + 2. Корректность активации вкладок после переключения + 3. Отсутствие ошибок при последовательном переключении + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + rt = RackTab(browser) + + # Проверяем переключение между всеми вкладками стойки + rt.check_tab_switching()