e-nms_qa_automation/pages/rack_page.py

570 lines
23 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, expect
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from elements.tooltip_button_element import TooltipButton
from components.toolbar_component import ToolbarComponent
from pages.base_page import BasePage
logger = get_logger("RACK_PAGE")
# Специфичные локаторы оставленые в основном коде
PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i"
TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']"
PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]"
class RackPage(BasePage):
"""Класс для работы с вкладкой стойки оборудования."""
def __init__(self, page: Page) -> None:
"""
Инициализирует объект вкладки стойки.
Args:
page: Экземпляр страницы Playwright
"""
super().__init__(page)
# Кнопка "Изменить"
locator_button = self.page.locator(RackLocators.EDIT_BUTTON)
self.edit_button = TooltipButton(page, locator_button, "edit")
# Кнопка "Скрыть стойку"
hide_button_locator = self.page.locator(RackLocators.HIDE_RACK_BUTTON)
self.hide_button = TooltipButton(page, hide_button_locator, "hide_rack")
# Кнопка "Показать стойку"
show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON)
self.show_button = TooltipButton(page, show_button_locator, "show_rack")
self.toolbar = ToolbarComponent(page, "")
self.toolbar.add_tooltip_button(locator_button, "edit")
self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack")
self.toolbar.add_tooltip_button(show_button_locator, "show_rack")
# Действия
def check_physical_devices_presence(self) -> None:
"""Проверяет наличие физических устройств на стойке."""
# Поиск устройств по классу parent-class
devices = self.page.locator(RackLocators.DEVICE_ELEMENTS)
device_count = devices.count()
if device_count > 0:
# Выводим информацию только о первом устройстве
first_device = devices.first
device_id = first_device.get_attribute("id") or "No id"
device_title = first_device.get_attribute("title") or "No title"
logger.info(
f"Devices found: {device_count} "
f"(first: ID={device_id}, Title={device_title})"
)
else:
logger.info("No devices detected")
def get_available_tabs(self) -> list[str]:
"""
Возвращает список доступных вкладок.
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 top tab elements found: {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"Top tab found: '{tab_text}'")
logger.info(f"Available top tabs found: {tabs}")
return tabs
def get_current_active_side(self) -> str:
"""
Возвращает текущую активную сторону стойки.
Проверяет конкретные кнопки стойки напрямую.
"""
# Проверяем конкретно кнопку "Лицевая сторона"
front_btn = self.page.locator(RackLocators.FRONT_SIDE_BUTTON)
if front_btn.count() > 0:
classes = front_btn.first.get_attribute("class") or ""
if "primary--text" in classes.split():
return "Лицевая сторона"
# Проверяем конкретно кнопку "Обратная сторона"
back_btn = self.page.locator(RackLocators.BACK_SIDE_BUTTON)
if back_btn.count() > 0:
classes = back_btn.first.get_attribute("class") or ""
if "primary--text" in classes.split():
return "Обратная сторона"
return ""
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_composition_tab(self) -> None:
"""Переключается на вкладку 'Состав'."""
self.switch_to_tab("Состав")
def switch_to_events_tab(self) -> None:
"""Переключается на вкладку 'События'."""
self.switch_to_tab("События")
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_services_tab(self) -> None:
"""Переключается на вкладку 'Сервисы'."""
self.switch_to_tab("Сервисы")
def switch_to_tab(self, tab_name: str) -> None:
"""
Переключается на указанную вкладку.
Args:
tab_name: Название вкладки для переключения
Raises:
AssertionError: Если вкладка не найдена или недоступна
"""
logger.info(f"Switching to tab '{tab_name}'...")
tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name))
assert tab.count() > 0, f"Tab '{tab_name}' not found"
# Проверяем активность ДО клика
if self.is_tab_active(tab_name):
logger.info(f"Tab '{tab_name}' is already active")
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
assert target_tab is not None, f"No visible/available tab '{tab_name}' found"
# Кликаем на вкладку
logger.info(f"Clicking on tab '{tab_name}'...")
target_tab.click()
# Ждем изменения активной вкладки
self._wait_for_tab_activation(tab_name)
# Ждем загрузки контента
self.wait_for_timeout(500)
def wait_for_rack_loading(self) -> None:
"""Ожидает загрузки интерфейса стойки."""
logger.info("Waiting for rack interface to load...")
# Проверяем наличие кнопок переключения сторон
front_button = self.page.locator(RackLocators.FRONT_SIDE_BUTTON)
back_button = self.page.locator(RackLocators.BACK_SIDE_BUTTON)
expect(front_button).to_be_visible(timeout=5000)
expect(back_button).to_be_visible(timeout=5000)
logger.info("Side toggle buttons loaded")
# Проверяем, что есть активная кнопка
active_button = self.page.locator(RackLocators.ACTIVE_SIDE_BUTTON)
if active_button.count() > 0:
logger.info("Active side button found")
else:
logger.warning("No active side button found")
# Проверяем наличие основного контейнера
main_container = self.page.locator(RackLocators.MAIN_CONTAINER)
if main_container.count() == 0:
logger.warning("Main rack container not found")
else:
logger.info(f"Main rack container found (count: {main_container.count()})")
expect(main_container.first).to_be_attached()
# Проверяем наличие позиций юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
if unit_positions.count() > 0:
logger.info(f"Unit positions found: {unit_positions.count()}")
if unit_positions.first.text_content():
content = unit_positions.first.text_content().strip()
logger.info(f"First position: {content}")
# Проверяем наличие кнопок добавления
open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON)
if open_buttons.count() > 0:
logger.info(f"'add_circle' buttons found: {open_buttons.count()}")
logger.info("Rack interface loaded")
# Проверки
def check_tab_switching(self) -> None:
"""
Проверяет переключение между вкладками стойки.
"""
logger.info("Testing rack tab switching functionality...")
# Вкладки в правильном порядке
defined_tabs = [
"Состав",
"Общая информация",
"Обслуживание",
"События",
"Сервисы"
]
logger.info(f"Defined tabs to test: {defined_tabs}")
successful_switches = 0
failed_switches = []
# Тестируем переключение на каждую определенную вкладку
for tab_name in defined_tabs:
logger.info(f"Testing switch to tab '{tab_name}'...")
try:
# Переключаемся на вкладку
self.switch_to_tab(tab_name)
# Проверяем, что вкладка активна
if self.is_tab_active(tab_name):
logger.info(f"Successfully switched to tab '{tab_name}'")
successful_switches += 1
else:
logger.warning(f"Tab '{tab_name}' not active after switching")
failed_switches.append(f"Tab '{tab_name}' is not active after click")
except Exception as e:
logger.error(f"Error switching to tab '{tab_name}': {e}")
failed_switches.append(f"Tab '{tab_name}' error: {str(e)}")
# Небольшая пауза между переключениями
self.wait_for_timeout(1000)
# Формируем итоговый отчет
logger.info("=== TAB SWITCHING RESULTS ===")
logger.info(f"Successful switches: {successful_switches}/{len(defined_tabs)}")
if failed_switches:
logger.info("Failed switches:")
for failure in failed_switches:
logger.info(f" - {failure}")
# Требуем успешного переключения на все определенные вкладки
assert successful_switches == len(defined_tabs), (
f"Tab switching test failed. "
f"Only {successful_switches} out of {len(defined_tabs)} defined tabs "
f"were successfully switched. "
f"Errors: {', '.join(failed_switches)}"
)
logger.info(f"All {successful_switches} defined tabs successfully switched!")
def is_tab_active(self, tab_name: str) -> bool:
"""
Проверяет, активна ли указанная вкладка.
Args:
tab_name: Название вкладки для проверки
Returns:
bool: True если вкладка активна, False в противном случае
"""
# Проверяем по активному классу и тексту
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 '{tab_name}' is active (via active tab class)")
return True
logger.info(f"Tab '{tab_name}' is not active")
return False
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} "
f"and expected {expected_toolbar_title_items}"
)
def should_be_rack_sides_displayed(self) -> None:
"""Проверка отображения и структуры сторон стойки."""
logger.info("Checking rack sides display and structure...")
# Ожидаем загрузки
self.wait_for_rack_loading()
# БАЗОВАЯ ПРОВЕРКА: наличие кнопок переключения сторон
front_side_button = self.page.locator(RackLocators.FRONT_SIDE_BUTTON)
back_side_button = self.page.locator(RackLocators.BACK_SIDE_BUTTON)
# Проверяем наличие кнопок
expect(front_side_button).to_be_visible(timeout=5000), \
"Front side button not found"
expect(back_side_button).to_be_visible(timeout=5000), \
"Back side button not found"
logger.info("Side toggle buttons found")
# Проверяем, какая сторона активна по умолчанию
current_active = self.get_current_active_side()
logger.info(f"Current active side: {current_active}")
# Дополнительная проверка устройств
self.check_physical_devices_presence()
# ПРОВЕРКА ЛИЦЕВОЙ СТОРОНЫ
self._check_side_details("Лицевая сторона", front_side_button)
# ПРОВЕРКА ОБРАТНОЙ СТОРОНЫ
self._check_side_details("Обратная сторона", back_side_button)
# Возвращаемся на исходную активную сторону
if current_active:
logger.info(f"Returning to original active side: {current_active}")
if current_active == "Лицевая сторона":
front_side_button.click()
else:
back_side_button.click()
self.wait_for_timeout(1000)
final_active = self.get_current_active_side()
logger.info(f"Final active side: {final_active}")
logger.info("All rack sides checks passed successfully")
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если кнопки недоступны или подсказки неверны.
"""
logger.info("Checking toolbar buttons...")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Изменить")
self.toolbar.get_button_by_name("edit").click()
def should_have_hide_rack_button(self) -> None:
"""
Упрощенная проверка кнопки "Скрыть стойку".
Проверяет только кликабельность и наличие элемента.
"""
logger.info("Checking 'Hide rack' button...")
# Проверяем видимость
self.toolbar.check_button_visibility("hide_rack")
self.toolbar.check_button_tooltip("hide_rack", "Скрыть стойку")
# Проверяем, что кнопка кликабельна
self.toolbar.get_button_by_name("hide_rack").click()
self.wait_for_timeout(1000)
def should_have_show_rack_button(self) -> None:
"""
Проверка кнопки "Показать стойку".
Проверяет наличие, тултип и кликабельность.
"""
logger.info("Checking 'Show rack' button...")
# Проверяем видимость
self.toolbar.check_button_visibility("show_rack")
self.toolbar.check_button_tooltip("show_rack", "Показать стойку")
# Проверяем кликабельность
self.toolbar.get_button_by_name("show_rack").click()
self.wait_for_timeout(1000)
# Вспомогательные методы
def _check_side_details(self, side_name: str, side_button) -> None:
"""
Проверка структуры конкретной стороны стойки.
Args:
side_name: Название стороны для логов
side_button: Локатор кнопки стороны
Raises:
AssertionError: Если структура стороны некорректна
"""
logger.info(f"Checking {side_name}...")
# Логируем текущее состояние кнопки перед кликом
button_classes = side_button.get_attribute("class") or ""
logger.info(f"Button classes before click: {button_classes}")
# Проверяем, активна ли уже эта сторона
current_active = self.get_current_active_side()
logger.info(f"Current active side (before click): '{current_active}'")
if current_active == side_name:
logger.info(f"{side_name} is already active")
else:
# Если не активна, кликаем для переключения
logger.info(f"Switching to {side_name}...")
side_button.click()
# Даем время на перерисовку классов (увеличиваем время)
self.wait_for_timeout(2500)
# Проверяем классы после клика
button_classes_after = side_button.get_attribute("class") or ""
logger.info(f"Button classes after click: {button_classes_after}")
# Проверяем, что нужная сторона стала активной
active_side = self.get_current_active_side()
logger.info(f"Active side after switching: '{active_side}'")
assert active_side == side_name, \
f"Wrong side is active: '{active_side}', expected: '{side_name}'"
logger.info(f"{side_name} successfully activated")
# Проверяем позиции юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
total_positions = unit_positions.count()
logger.info(f"Total unit positions: {total_positions}")
assert total_positions > 0, f"No unit positions found on {side_name}"
# Проверяем юниты
all_units = self.page.locator(RackLocators.ALL_UNITS)
all_units_count = all_units.count()
units_per_side = all_units_count // 2
logger.info(f"Units on {side_name}: {units_per_side}")
# Проверяем устройства
devices = self.page.locator(RackLocators.DEVICE_ELEMENTS)
device_count = devices.count()
if device_count > 0:
# Выводим информацию только о первом устройстве
first_device = devices.first
device_id = first_device.get_attribute("id") or "No id"
device_title = first_device.get_attribute("title") or "No title"
device_classes = first_device.get_attribute("class") or "No classes"
# Ищем слоты внутри устройства
slots = first_device.locator(RackLocators.DEVICE_SLOTS)
slot_count = slots.count()
logger.info(f"Devices found: {device_count} (showing first)")
logger.info(f" Device: ID={device_id}")
logger.info(f" Title: {device_title}")
logger.info(f" Classes: {device_classes}")
logger.info(f" Slots: {slot_count}")
else:
logger.info("No devices detected")
logger.info(f"{side_name} check completed successfully")
def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None:
"""
Ожидает активации вкладки.
Args:
tab_name: Название вкладки для ожидания
timeout: Время ожидания в миллисекундах
Raises:
AssertionError: Если вкладка не активирована в течение таймаута
"""
logger.info(f"Waiting for tab '{tab_name}' activation...")
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 '{tab_name}' successfully activated")
return
self.wait_for_timeout(100)
assert False, f"Tab '{tab_name}' not activated within {timeout}ms"