"""Модуль тестов вкладки 'Стойка'. Содержит тесты для проверки функциональности работы со стойкой оборудования. """ 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} определенных вкладок успешно переключены!")