Перенос изменений из radislav/element_rack в main:
- components/toolbar_component.py - locators/rack_locators.py - pages/rack_page.py - tests/e2e/elements/test_element_rack.pyra2/create_element_rack
parent
9a5308bf7d
commit
453d6a5ec2
|
|
@ -27,6 +27,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
|
|
||||||
def __init__(self, page: Page, title: str) -> None:
|
def __init__(self, page: Page, title: str) -> None:
|
||||||
"""Инициализирует компонент тулбара с указанным заголовком."""
|
"""Инициализирует компонент тулбара с указанным заголовком."""
|
||||||
|
|
||||||
super().__init__(page)
|
super().__init__(page)
|
||||||
self.title = title
|
self.title = title
|
||||||
self.buttons = []
|
self.buttons = []
|
||||||
|
|
@ -38,6 +39,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Args:
|
Args:
|
||||||
title (str): Новый заголовок
|
title (str): Новый заголовок
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
def add_tooltip_button(self, locator: Locator, name: str) -> None:
|
def add_tooltip_button(self, locator: Locator, name: str) -> None:
|
||||||
|
|
@ -47,6 +49,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
locator (Locator): Локатор кнопки
|
locator (Locator): Локатор кнопки
|
||||||
name (str): Уникальное имя кнопки
|
name (str): Уникальное имя кнопки
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.buttons.append(TooltipButton(self.page, locator, name))
|
self.buttons.append(TooltipButton(self.page, locator, name))
|
||||||
|
|
||||||
def add_tab_button(self, locator: Locator, name: str) -> None:
|
def add_tab_button(self, locator: Locator, name: str) -> None:
|
||||||
|
|
@ -56,6 +59,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
locator (Locator): Локатор кнопки
|
locator (Locator): Локатор кнопки
|
||||||
name (str): Уникальное имя кнопки
|
name (str): Уникальное имя кнопки
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.buttons.append(TabButton(self.page, locator, name))
|
self.buttons.append(TabButton(self.page, locator, name))
|
||||||
|
|
||||||
def add_button(self, locator: Locator, name: str) -> None:
|
def add_button(self, locator: Locator, name: str) -> None:
|
||||||
|
|
@ -65,6 +69,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
locator (Locator): Локатор кнопки
|
locator (Locator): Локатор кнопки
|
||||||
name (str): Уникальное имя кнопки
|
name (str): Уникальное имя кнопки
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.buttons.append(Button(self.page, locator, name))
|
self.buttons.append(Button(self.page, locator, name))
|
||||||
|
|
||||||
def get_button_by_name(self, name: str
|
def get_button_by_name(self, name: str
|
||||||
|
|
@ -77,6 +82,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Returns:
|
Returns:
|
||||||
TooltipButton | TabButton | Button | None: Найденная кнопка или None
|
TooltipButton | TabButton | Button | None: Найденная кнопка или None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for button in self.buttons:
|
for button in self.buttons:
|
||||||
if button.name == name:
|
if button.name == name:
|
||||||
return button
|
return button
|
||||||
|
|
@ -91,6 +97,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: Если кнопка не найдена
|
AssertionError: Если кнопка не найдена
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button = self.get_button_by_name(name)
|
button = self.get_button_by_name(name)
|
||||||
if button is None:
|
if button is None:
|
||||||
raise AssertionError(f"Unsupported button name {name}")
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
|
@ -112,6 +119,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Raises:
|
Raises:
|
||||||
Exception: Если не удалось получить заголовок
|
Exception: Если не удалось получить заголовок
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Получаем локатор заголовка
|
# Получаем локатор заголовка
|
||||||
title_locator = self.get_locator(locator)
|
title_locator = self.get_locator(locator)
|
||||||
|
|
||||||
|
|
@ -128,6 +136,40 @@ class ToolbarComponent(BaseComponent):
|
||||||
|
|
||||||
return title_text
|
return title_text
|
||||||
|
|
||||||
|
def get_toolbar_composite_title_text(self, locator: str|Locator ,
|
||||||
|
timeout: int = 5000) -> []:
|
||||||
|
"""Получает составной заголовок тулбара окна в виде списка подзаголовков.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор для заголовка тулбара
|
||||||
|
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:
|
def is_button_present(self, name: str) -> bool:
|
||||||
"""Проверяет наличие кнопки.
|
"""Проверяет наличие кнопки.
|
||||||
|
|
@ -141,6 +183,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: Если имя кнопки не поддерживается
|
AssertionError: Если имя кнопки не поддерживается
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button = self.get_button_by_name(name)
|
button = self.get_button_by_name(name)
|
||||||
if button is None:
|
if button is None:
|
||||||
raise AssertionError(f"Unsupported button name {name}")
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
|
@ -158,6 +201,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: Если имя кнопки не поддерживается
|
AssertionError: Если имя кнопки не поддерживается
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button = self.get_button_by_name(name)
|
button = self.get_button_by_name(name)
|
||||||
if button is None:
|
if button is None:
|
||||||
raise AssertionError(f"Unsupported button name {name}")
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
|
@ -169,6 +213,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Args:
|
Args:
|
||||||
message (str): Сообщение об ошибке если тулбар не виден
|
message (str): Сообщение об ошибке если тулбар не виден
|
||||||
"""
|
"""
|
||||||
|
|
||||||
locator = self.get_locator(ToolbarLocators.TITLE).filter(
|
locator = self.get_locator(ToolbarLocators.TITLE).filter(
|
||||||
has_text=self.title
|
has_text=self.title
|
||||||
)
|
)
|
||||||
|
|
@ -182,6 +227,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
locator: Локатор тулбара
|
locator: Локатор тулбара
|
||||||
message (str): Сообщение об ошибке если тулбар не виден
|
message (str): Сообщение об ошибке если тулбар не виден
|
||||||
"""
|
"""
|
||||||
|
|
||||||
locator = self.get_locator(locator)
|
locator = self.get_locator(locator)
|
||||||
expect(locator).to_be_visible(), message
|
expect(locator).to_be_visible(), message
|
||||||
|
|
||||||
|
|
@ -193,6 +239,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
locator: Локатор тулбара
|
locator: Локатор тулбара
|
||||||
message (str): Сообщение об ошибке если тулбар не виден
|
message (str): Сообщение об ошибке если тулбар не виден
|
||||||
"""
|
"""
|
||||||
|
|
||||||
locator = self.get_locator(locator).filter(has_text=self.title)
|
locator = self.get_locator(locator).filter(has_text=self.title)
|
||||||
expect(locator).to_be_visible(), message
|
expect(locator).to_be_visible(), message
|
||||||
|
|
||||||
|
|
@ -205,6 +252,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: Если кнопка не найдена или не видна
|
AssertionError: Если кнопка не найдена или не видна
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button = self.get_button_by_name(name)
|
button = self.get_button_by_name(name)
|
||||||
|
|
||||||
if button is None:
|
if button is None:
|
||||||
|
|
@ -223,6 +271,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: Если текст подсказки не совпадает
|
AssertionError: Если текст подсказки не совпадает
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button = self.get_button_by_name(name)
|
button = self.get_button_by_name(name)
|
||||||
if button is None:
|
if button is None:
|
||||||
raise AssertionError(f"Unsupported button name {name}")
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
Класс RackLocators хранит XPath локаторы для взаимодействия
|
Класс RackLocators хранит XPath локаторы для взаимодействия
|
||||||
с элементами интерфейса стойки оборудования в тестах.
|
с элементами интерфейса стойки оборудования в тестах.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class RackLocators:
|
class RackLocators:
|
||||||
|
|
@ -15,26 +16,37 @@ class RackLocators:
|
||||||
- Контейнеры и структурные элементы
|
- Контейнеры и структурные элементы
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# Основной контейнер вкладок стойки (верхние вкладки)
|
# Основной контейнер вкладок стойки (верхние вкладки)
|
||||||
TABS_CONTAINER = "//div[@class='v-tabs__container']"
|
TABS_CONTAINER = "//div[@data-testid='CABINET_SHOW__tabs' and contains(@class, 'v-tabs')]"
|
||||||
|
|
||||||
# Все элементы верхних вкладок стойки
|
# Все элементы верхних вкладок стойки
|
||||||
ALL_TABS = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item')]"
|
ALL_TABS = "//div[@data-testid='CABINET_SHOW__tabs']//a[contains(@class, 'v-tabs__item')]"
|
||||||
|
|
||||||
|
# Кнопка редактирования свойств стойки
|
||||||
|
EDIT_BUTTON ="//button[@data-testid='CABINET_SHOW__btn__edit']"
|
||||||
|
|
||||||
|
# Кнопка "Скрыть стойку"
|
||||||
|
HIDE_RACK_BUTTON = "//div[@data-testid='CABINET_SHOW__div__hideCabinet' and contains(@class, 'cabinet_hide_button_trigger_show')]"
|
||||||
|
|
||||||
|
# Кнопка "Показать стойку"
|
||||||
|
SHOW_RACK_BUTTON = "//div[@data-testid='CABINET_SHOW__div__hideCabinet' and contains(@class, 'cabinet_hide_button_trigger_hide')]"
|
||||||
|
|
||||||
# Универсальный локатор для любой вкладки по имени
|
# Универсальный локатор для любой вкладки по имени
|
||||||
TAB_BY_NAME = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., '{}')]"
|
TAB_BY_NAME = "//div[starts-with(@data-testid, 'CABINET_SHOW__') and contains(@class, 'v-tabs__div')]//a[contains(@class, 'v-tabs__item') and .//*[contains(., '{}')]]"
|
||||||
|
|
||||||
# Конкретные вкладки по тексту
|
# Конкретные вкладки по тексту
|
||||||
GENERAL_INFO_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'Общая информация')]"
|
COMPOSITION_TAB = "//div[@data-testid='CABINET_SHOW__composition_tab']//a[contains(@class, 'v-tabs__item')]"
|
||||||
MAINTENANCE_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'Обслуживание')]"
|
GENERAL_INFO_TAB = "//div[@data-testid='CABINET_SHOW__main_tab']//a[contains(@class, 'v-tabs__item')]"
|
||||||
EVENTS_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'События')]"
|
MAINTENANCE_TAB = "//div[@data-testid='CABINET_SHOW__service_tab']//a[contains(@class, 'v-tabs__item')]"
|
||||||
SERVICES_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item') and contains(., 'Сервисы')]"
|
EVENTS_TAB = "//div[@data-testid='CABINET_SHOW__events_tab']//a[contains(@class, 'v-tabs__item')]"
|
||||||
|
SERVICES_TAB = "//div[@data-testid='CABINET_SHOW__services_tab']//a[contains(@class, 'v-tabs__item')]"
|
||||||
|
|
||||||
# Классы для проверки активности
|
# Классы для проверки активности
|
||||||
ACTIVE_TAB_CLASSES = ["accent--text", "v-tabs__item--active"]
|
ACTIVE_TAB_CLASSES = ["accent--text", "v-tabs__item--active"]
|
||||||
|
|
||||||
# Локатор для активной вкладки
|
# Локатор для активной вкладки
|
||||||
ACTIVE_TAB = "//div[@class='v-tabs__container']//a[contains(@class, 'v-tabs__item--active')]"
|
ACTIVE_TAB = "//div[@data-testid='CABINET_SHOW__tabs']//a[contains(@class, 'v-tabs__item--active')]"
|
||||||
|
|
||||||
# Контейнер формы
|
# Контейнер формы
|
||||||
FORM_CONTAINER = "//div[contains(@class, 'container')]"
|
FORM_CONTAINER = "//div[contains(@class, 'container')]"
|
||||||
|
|
@ -69,38 +81,37 @@ class RackLocators:
|
||||||
# CSS селекторы для ошибок валидации
|
# CSS селекторы для ошибок валидации
|
||||||
ERROR_CSS_SELECTORS = ".error--text, .v-input--error"
|
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(), 'Лицевая сторона')]"
|
# Общий контейнер стойки (включает кнопки переключения сторон и MAIN_CONTAINER)
|
||||||
BACK_SIDE_TITLE = "//span[contains(@class, 'subheading') and contains(text(), 'Обратная сторона')]"
|
RACK_CONTAINER = "//div[contains(@class, 'layout active') and contains(@class, 'row') and contains(@class, 'shrink')]"
|
||||||
|
|
||||||
# Юниты на лицевой стороне
|
# Основной контейнер стойки (изображение стойки)
|
||||||
FRONT_SIDE_UNITS = ".cabinet:not(.back) .unit, .unit:not(.back)"
|
MAIN_CONTAINER = "//div[contains(@class, 'layout cabinet')]"
|
||||||
|
|
||||||
# Юниты на обратной стороне
|
# Кнопки переключения сторон
|
||||||
BACK_SIDE_UNITS = ".cabinet.back .unit"
|
FRONT_SIDE_BUTTON = "//button[@data-testid='page-cabinet__btn__mount_front']"
|
||||||
|
BACK_SIDE_BUTTON = "//button[@data-testid='page-cabinet__btn__mount_rear']"
|
||||||
|
|
||||||
# Локатор для всех юнитов
|
# Локаторы для определения активной стороны
|
||||||
ALL_UNITS = ".unit"
|
ACTIVE_SIDE_BUTTON = "//button[contains(@class, 'primary--text')]"
|
||||||
|
INACTIVE_SIDE_BUTTON = "//button[contains(@class, 'secondary--text')]"
|
||||||
|
|
||||||
# Устройства на лицевой стороне
|
# Для получения текста активной стороны
|
||||||
FRONT_SIDE_DEVICES = "//*[contains(@class, 'parent-class')]"
|
ACTIVE_SIDE_BUTTON_TEXT = "//button[contains(@class, 'primary--text')]//div[contains(@class, 'v-btn__content')]"
|
||||||
|
|
||||||
# Устройства на обратной стороне
|
# Кнопка добавления (add_circle)
|
||||||
BACK_SIDE_DEVICES = "//*[contains(@class, 'parent-class')]"
|
ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]"
|
||||||
|
|
||||||
|
# Все юниты на стойке
|
||||||
|
ALL_UNITS = "//div[contains(@class, 'unit')]"
|
||||||
|
|
||||||
# Позиции юнитов
|
# Позиции юнитов
|
||||||
UNIT_POSITIONS = "//div[contains(@class, 'unit-positions')]"
|
UNIT_POSITIONS = "//div[contains(@class, 'headline') and contains(@class, 'test-xs-center') and contains(@class, 'unit-positions')]"
|
||||||
|
|
||||||
# Контейнер с обеими сторонами
|
# Локатор для устройств
|
||||||
SIDES_CONTAINER = "//div[contains(@class, 'layout row shrink fill-height ma-0 pa-0')]"
|
DEVICE_ELEMENTS = "//div[contains(@class, 'parent-class')]"
|
||||||
|
|
||||||
# ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОРОН
|
# Локатор для слотов в устройствах
|
||||||
# Основные секции сторон
|
DEVICE_SLOTS = "//div[contains(@class, 'slot')]"
|
||||||
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')]"
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,596 @@
|
||||||
|
"""Модуль тестов вкладки 'Стойка'.
|
||||||
|
|
||||||
|
Содержит тесты для проверки функциональности
|
||||||
|
работы со стойкой оборудования.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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"
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
|
||||||
|
|
||||||
|
Содержит тесты для проверки функциональности
|
||||||
|
работы со стойкой оборудования.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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_panel_header(expected_toolbar_subtitles)
|
||||||
|
|
||||||
|
# Комплексная проверка отображения обеих сторон стойки с детальной информацией
|
||||||
|
rt.should_be_rack_sides_displayed()
|
||||||
|
|
||||||
|
# Проверка кнопки "Скрыть стойку"
|
||||||
|
rt.should_have_hide_rack_button()
|
||||||
|
|
||||||
|
# Проверка кнопки "Показать стойку"
|
||||||
|
rt.should_have_show_rack_button()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Переход в режим редактирования
|
||||||
|
rt.should_be_toolbar_buttons()
|
||||||
|
rt.wait_for_timeout(2000)
|
||||||
Loading…
Reference in New Issue