Добавлены тесты для вкладки элемента 'Стойка'

>>
>> - Создан tests/e2e/elements/test_element_rack.py
>> - Создан pages/rack_page.py
>> - Изменен модуль locators/rack_locators.py, добавлены с локаторами элементов стойки
radislav/element_rack
Radislav 2025-12-15 08:46:11 +03:00
parent 47d5306c5d
commit 348530fe37
5 changed files with 776 additions and 499 deletions

View File

@ -128,6 +128,41 @@ class ToolbarComponent(BaseComponent):
return title_text
def get_toolbar_composite_title_text(self, locator: str|Locator ,
timeout: int = 5000) -> []:
"""Получает составной заголовок тулбара окна в виде списка подзаголовков.
Args:
locator: Локатор для заголовка тулбара
separator: Разделитель подзаголовков, по умолчанию галочка вправо (chevron_right)
timeout: Таймаут ожидания в миллисекундах
Returns:
str: Текст заголовка тулбара
Raises:
Exception: Если не удалось получить заголовок
"""
toolbar_title_items = []
# Получаем локатор заголовка
title_locator = self.get_locator(locator)
# Ждем появления заголовка с помощью expect
expect(title_locator).to_be_visible(timeout=timeout)
# Найти все элементы <li> внутри <ul>
list_items = title_locator.locator("ul > li")
# Перебрать элементы и получить текст
for i in range(list_items.count()):
item_text = list_items.nth(i).inner_text()
toolbar_title_items.append(item_text)
return toolbar_title_items
# Проверки:
def is_button_present(self, name: str) -> bool:
"""Проверяет наличие кнопки.

View File

@ -2,6 +2,7 @@
Класс RackLocators хранит XPath локаторы для взаимодействия
с элементами интерфейса стойки оборудования в тестах.
"""
class RackLocators:
@ -21,6 +22,15 @@ class RackLocators:
# Все элементы верхних вкладок стойки
ALL_TABS = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item')]"
# Кнопка редактирования свойств стойки
EDIT_BUTTON = "//button[@data-v-5f34ceac and contains(@class, 'font-weight-medium')]"
# Кнопка "Скрыть стойку"
HIDE_RACK_BUTTON = "//div[contains(@class, 'cabinet_hide_button')]//i[contains(@class, 'material-icons') and text()='navigate_before']"
# Кнопка "Показать стойку"
SHOW_RACK_BUTTON = "//div[contains(@class, 'cabinet_hide_button')]//i[contains(@class, 'material-icons') and text()='navigate_next']"
# Универсальный локатор для любой вкладки по имени
TAB_BY_NAME = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., '{}')]"
@ -69,38 +79,34 @@ class RackLocators:
# CSS селекторы для ошибок валидации
ERROR_CSS_SELECTORS = ".error--text, .v-input--error"
# Локаторы для отображения сторон стойки
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')]"
MAIN_CONTAINER = "//div[contains(@class, 'layout cabinet')]"
# Кнопки переключения сторон
FRONT_SIDE_BUTTON = "//button[contains(@class, 'v-btn')]//div[contains(@class, 'v-btn__content') and normalize-space(text())='Лицевая сторона']"
BACK_SIDE_BUTTON = "//button[contains(@class, 'v-btn')]//div[contains(@class, 'v-btn__content') and normalize-space(text())='Обратная сторона']"
# Активная кнопка (имеет класс primary, но НЕ имеет outline)
ACTIVE_SIDE_BUTTON = "//button[contains(@class, 'v-btn') and contains(@class, 'primary') and not(contains(@class, 'v-btn--outline'))]"
ACTIVE_SIDE_BUTTON_TEXT = "//button[contains(@class, 'v-btn') and contains(@class, 'primary') and not(contains(@class, 'v-btn--outline'))]//div[contains(@class, 'v-btn__content')]"
# Неактивная кнопка (имеет outline или не имеет primary)
INACTIVE_SIDE_BUTTON = "//button[contains(@class, 'v-btn') and (contains(@class, 'v-btn--outline') or not(contains(@class, 'primary')))]"
# Кнопка добавления (add_circle)
ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]"
# Все юниты на стойке
ALL_UNITS = "//div[contains(@class, 'unit')]"
# Позиции юнитов
UNIT_POSITIONS = "//div[contains(@class, 'headline') and contains(@class, 'test-xs-center') and contains(@class, 'unit-positions')]"
# Локатор для устройств
DEVICE_ELEMENTS = "//div[contains(@class, 'parent-class')]"
# Локатор для слотов в устройствах
DEVICE_SLOTS = "//div[contains(@class, 'slot')]"

585
pages/rack_page.py Normal file
View File

@ -0,0 +1,585 @@
"""Модуль тестов вкладки 'Стойка'.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
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:
"""
Возвращает текущую активную сторону стойки.
Returns:
str: Название активной стороны ('Лицевая сторона' или 'Обратная сторона')
"""
active_button = self.page.locator(RackLocators.ACTIVE_SIDE_BUTTON_TEXT)
if active_button.count() > 0:
text = active_button.first.text_content()
if text:
return text.strip()
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_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:
"""
Проверяет переключение между вкладками стойки в соответствии с локаторами.
Raises:
AssertionError: Если переключение на одну или более вкладок не удалось
"""
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}'...")
# Проверяем существование локатора для этой вкладки
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 '{tab_name}' not found on page")
failed_switches.append(f"Tab '{tab_name}' not found")
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"No visible/available tab '{tab_name}' found")
failed_switches.append(f"Tab '{tab_name}' is not visible/available")
continue
# Переключаемся на вкладку
logger.info(f"Switching to tab '{tab_name}'...")
# Проверяем активность ДО клика
if self.is_tab_active(tab_name):
logger.info(f"Tab '{tab_name}' is already active")
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 '{tab_name}' not active after switching")
failed_switches.append(f"Tab '{tab_name}' is not active after click")
continue
logger.info(f"Successfully switched to tab '{tab_name}'")
successful_switches += 1
# Небольшая пауза между переключениями
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}"
)
self.toolbar.check_button_visibility("edit")
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}...")
# Проверяем, активна ли уже эта сторона
current_active = self.get_current_active_side()
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(1500)
# Проверяем, что сторона активирована
active_button_after = self.page.locator(RackLocators.ACTIVE_SIDE_BUTTON_TEXT)
assert active_button_after.count() > 0, \
f"No active button found after clicking on {side_name}"
active_text_after = active_button_after.first.text_content().strip()
assert active_text_after == side_name, \
f"Wrong side is active: '{active_text_after}', 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"

View File

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

View File

@ -0,0 +1,117 @@
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
import pytest
from playwright.sync_api import Page
from pages.rack_page import RackPage
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("test-zone")
mp.wait_for_timeout(3000)
# Переходим к Стойка КСПД с указанием родителя
mp.click_subpanel_item("Test-Rack-01", parent="test-zone")
mp.wait_for_timeout(3000)
@pytest.mark.develop
def test_rack_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки 'Стойка'.
Проверяет:
1. Наличие и корректность заголовка панели с навигационной цепочкой
2. Отображение и структуру обеих сторон стойки (лицевой и обратной)
3. Наличие и функциональность кнопок панели инструментов
4. Корректность отображения юнитов и устройств на стойке
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
expected_toolbar_subtitles = [
"test-zone",
'chevron_right',
'Test-Rack-01'
]
rt = RackPage(browser)
rt.should_be_header_panel(expected_toolbar_subtitles)
# Комплексная проверка отображения обеих сторон стойки с детальной информацией
rt.should_be_rack_sides_displayed()
# Проверка кнопки "Скрыть стойку"
rt.should_have_hide_rack_button()
# Проверка кнопки "Показать стойку"
rt.should_have_show_rack_button()
# Переход в режим редактирования
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 = RackPage(browser)
# Проверяем переключение между всеми вкладками стойки
rt.check_tab_switching()