e-nms_qa_automation/pages/rack_page.py

597 lines
25 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 typing import Optional
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")
logger.setLevel("INFO")
# Специфичные локаторы оставленые в основном коде
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 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.debug(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.debug(f"Top tab found: '{tab_text}'")
logger.debug(f"Available top tabs found: {tabs}")
return tabs
def get_current_active_side(self) -> Optional[str]:
"""
Возвращает текущую активную сторону стойки.
Returns:
Optional[str]: "Лицевая сторона", "Обратная сторона" или None если ни одна не активна
"""
# Проверяем конкретно кнопку "Лицевая сторона"
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 None
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.debug(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.debug(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.debug(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.debug("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.debug("Side toggle buttons loaded")
# Проверяем, что есть активная кнопка
active_button = self.page.locator(RackLocators.ACTIVE_SIDE_BUTTON)
if active_button.count() > 0:
logger.debug("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.debug(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.debug(f"Unit positions found: {unit_positions.count()}")
if unit_positions.first.text_content():
content = unit_positions.first.text_content().strip()
logger.debug(f"First position: {content}")
# Проверяем наличие кнопок добавления
open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON)
if open_buttons.count() > 0:
logger.debug(f"'add_circle' buttons found: {open_buttons.count()}")
logger.debug("Rack interface loaded")
# Проверки
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.debug(
f"Devices found: {device_count} "
f"(first: ID={device_id}, Title={device_title})"
)
else:
logger.debug("No devices detected")
def check_tab_switching(self) -> None:
"""
Проверяет переключение между вкладками стойки.
"""
logger.debug("Testing rack tab switching functionality...")
# Вкладки в правильном порядке
defined_tabs = [
"Состав",
"Общая информация",
"Обслуживание",
"События",
"Сервисы"
]
logger.debug(f"Defined tabs to test: {defined_tabs}")
successful_switches = 0
failed_switches = []
# Тестируем переключение на каждую определенную вкладку
for tab_name in defined_tabs:
logger.debug(f"Testing switch to tab '{tab_name}'...")
try:
# Переключаемся на вкладку
self.switch_to_tab(tab_name)
# Проверяем, что вкладка активна
if self.is_tab_active(tab_name):
logger.debug(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 (AssertionError, TimeoutError) 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.debug("=== TAB SWITCHING RESULTS ===")
logger.debug(f"Successful switches: {successful_switches}/{len(defined_tabs)}")
if failed_switches:
logger.debug("Failed switches:")
for failure in failed_switches:
logger.debug(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.debug(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.debug(f"Tab '{tab_name}' is active (via active tab class)")
return True
logger.debug(f"Tab '{tab_name}' is not active")
return False
def should_be_panel_header(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.debug("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.debug("Side toggle buttons found")
# Проверяем, какая сторона активна по умолчанию
current_active = self.get_current_active_side()
logger.debug(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.debug(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.debug(f"Final active side: {final_active}")
logger.debug("All rack sides checks passed successfully")
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если кнопки недоступны или подсказки неверны.
"""
logger.debug("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.debug("Checking 'Hide rack' button...")
# Проверяем видимость кнопки
self.toolbar.check_button_visibility("hide_rack")
self.toolbar.check_button_tooltip("hide_rack", "Скрыть стойку")
# Получаем общий контейнер стойки до клика
rack_container = self.page.locator(RackLocators.RACK_CONTAINER)
# Проверяем, что контейнер существует
expect(rack_container).to_be_visible(timeout=5000)
# Кликаем на кнопку "Скрыть стойку"
self.toolbar.get_button_by_name("hide_rack").click()
self.wait_for_timeout(2000)
# Проверяем, что общий контейнер стойки теперь скрыт (имеет display: none)
expect(rack_container).to_have_css("display", "none", timeout=5000)
logger.debug("Rack container successfully hidden (display: none)")
logger.debug("'Hide rack' button test completed successfully")
def should_have_show_rack_button(self) -> None:
"""
Проверка кнопки "Показать стойку".
Проверяет наличие, тултип, кликабельность и эффект показа стойки.
"""
logger.debug("Checking 'Show rack' button...")
# Проверяем видимость кнопки
self.toolbar.check_button_visibility("show_rack")
self.toolbar.check_button_tooltip("show_rack", "Показать стойку")
# Получаем общий контейнер стойки
rack_container = self.page.locator(RackLocators.RACK_CONTAINER)
# Проверяем, что контейнер существует
expect(rack_container).to_be_attached(timeout=5000)
# Кликаем на кнопку "Показать стойку"
self.toolbar.get_button_by_name("show_rack").click()
self.wait_for_timeout(2000) # Даем время для применения стилей
# Проверяем, что общий контейнер стойки теперь видим (display не равен "none")
expect(rack_container).not_to_have_css("display", "none", timeout=5000)
logger.debug("Rack container successfully shown (display is not 'none')")
logger.debug("'Show rack' button test completed successfully")
# Вспомогательные методы
def _check_side_details(self, side_name: str, side_button) -> None:
"""
Проверка структуры конкретной стороны стойки.
Args:
side_name: Название стороны для логов
side_button: Локатор кнопки стороны
Raises:
AssertionError: Если структура стороны некорректна
"""
logger.debug(f"Checking {side_name}...")
# Логируем текущее состояние кнопки перед кликом
button_classes = side_button.get_attribute("class") or ""
logger.debug(f"Button classes before click: {button_classes}")
# Проверяем, активна ли уже эта сторона
current_active = self.get_current_active_side()
logger.debug(f"Current active side (before click): '{current_active}'")
if current_active == side_name:
logger.debug(f"{side_name} is already active")
else:
# Если не активна, кликаем для переключения
logger.debug(f"Switching to {side_name}...")
side_button.click()
# Даем время на перерисовку классов (увеличиваем время)
self.wait_for_timeout(2500)
# Проверяем классы после клика
button_classes_after = side_button.get_attribute("class") or ""
logger.debug(f"Button classes after click: {button_classes_after}")
# Проверяем, что нужная сторона стала активной
active_side = self.get_current_active_side()
logger.debug(f"Active side after switching: '{active_side}'")
assert active_side == side_name, \
f"Wrong side is active: '{active_side}', expected: '{side_name}'"
logger.debug(f"{side_name} successfully activated")
# Проверяем позиции юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
total_positions = unit_positions.count()
logger.debug(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.debug(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.debug(f"Devices found: {device_count} (showing first)")
logger.debug(f" Device: ID={device_id}")
logger.debug(f" Title: {device_title}")
logger.debug(f" Classes: {device_classes}")
logger.debug(f" Slots: {slot_count}")
else:
logger.debug("No devices detected")
logger.debug(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.debug(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.debug(f"Tab '{tab_name}' successfully activated")
return
self.wait_for_timeout(100)
assert False, f"Tab '{tab_name}' not activated within {timeout}ms"