534 lines
24 KiB
Python
534 lines
24 KiB
Python
"""Модуль вкладки 'Стойка'."""
|
||
|
||
import re
|
||
from playwright.sync_api import Page
|
||
from tools.logger import get_logger
|
||
from locators.rack_locators import RackLocators
|
||
from locators.toolbar_locators import ToolbarLocators
|
||
from components.toolbar_component import ToolbarComponent
|
||
from components.table_component import TableComponent
|
||
from pages.base_page import BasePage
|
||
|
||
logger = get_logger("RackGeneralInfo")
|
||
|
||
|
||
class RackGeneralInfo(BasePage):
|
||
"""Класс для работы с вкладкой 'Стойка'."""
|
||
|
||
def __init__(self, page: Page) -> None:
|
||
"""Инициализирует компоненты вкладки 'Стойка/'Общая информация."""
|
||
|
||
super().__init__(page)
|
||
|
||
# Инициализируем тулбар с правильными локаторами
|
||
self.toolbar = ToolbarComponent(page, "Стойка систем питания")
|
||
|
||
# Добавляем кнопки тулбара
|
||
toolbar_locator = self.page.locator(ToolbarLocators.ITEMS)
|
||
buttons = toolbar_locator.get_by_role("button")
|
||
|
||
if buttons.count() >= 2:
|
||
self.toolbar.add_tooltip_button(buttons.nth(0), "edit")
|
||
self.toolbar.add_tooltip_button(buttons.nth(1), "close")
|
||
else:
|
||
# Альтернативный поиск кнопок
|
||
all_buttons = self.page.get_by_role("button")
|
||
if all_buttons.count() >= 2:
|
||
self.toolbar.add_tooltip_button(all_buttons.nth(0), "edit")
|
||
self.toolbar.add_tooltip_button(all_buttons.nth(1), "close")
|
||
|
||
self.rack_info_table = TableComponent(page)
|
||
|
||
def switch_to_tab(self, tab_name: str) -> None:
|
||
"""Переключается на указанную вкладку."""
|
||
logger.info(f"Switching to '{tab_name}' tab...")
|
||
|
||
# Используем универсальный локатор
|
||
tab_selector = RackLocators.TAB_BY_NAME.format(tab_name)
|
||
tab = self.page.locator(tab_selector)
|
||
|
||
if tab.count() == 0:
|
||
# Альтернативный поиск - ищем по тексту
|
||
tab = self.page.get_by_text(tab_name, exact=True)
|
||
|
||
if tab.count() == 0:
|
||
# Пробуем не точное совпадение
|
||
tab = self.page.get_by_text(tab_name)
|
||
|
||
if tab.count() == 0:
|
||
raise AssertionError(f"Tab '{tab_name}' not found")
|
||
|
||
# Ищем видимую и кликабельную вкладку
|
||
clickable_tab = None
|
||
for i in range(tab.count()):
|
||
element = tab.nth(i)
|
||
if element.is_visible():
|
||
clickable_tab = element
|
||
break
|
||
|
||
if not clickable_tab:
|
||
raise AssertionError(f"Tab '{tab_name}' found but not visible or clickable")
|
||
|
||
# Проверяем активность
|
||
try:
|
||
tab_class = clickable_tab.get_attribute("class") or ""
|
||
if any(active_class in tab_class for active_class in RackLocators.ACTIVE_TAB_CLASSES):
|
||
logger.info(f"Tab '{tab_name}' is already active")
|
||
return
|
||
except:
|
||
pass
|
||
|
||
# Кликаем
|
||
logger.info(f"Clicking on tab '{tab_name}'...")
|
||
try:
|
||
clickable_tab.click()
|
||
logger.info(f"Successfully clicked on '{tab_name}' tab")
|
||
except Exception as e:
|
||
logger.warning(f"Click failed: {e}, trying force click")
|
||
clickable_tab.click(force=True)
|
||
logger.info(f"Successfully force-clicked on '{tab_name}' tab")
|
||
|
||
# Ждем
|
||
self.page.wait_for_timeout(1000)
|
||
|
||
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:
|
||
"""Проверяет, активна ли указанная вкладка."""
|
||
try:
|
||
tab_selector = RackLocators.TAB_BY_NAME.format(tab_name)
|
||
tab = self.page.locator(tab_selector)
|
||
|
||
for i in range(tab.count()):
|
||
element = tab.nth(i)
|
||
if element.is_visible():
|
||
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 '{tab_name}' is active")
|
||
return True
|
||
except Exception as e:
|
||
logger.warning(f"Error checking tab activity for '{tab_name}': {e}")
|
||
|
||
logger.info(f"Tab '{tab_name}' is not active")
|
||
return False
|
||
|
||
def get_available_tabs(self) -> list:
|
||
"""Возвращает список доступных вкладок."""
|
||
tabs = []
|
||
tab_elements = self.page.locator(RackLocators.ALL_TABS)
|
||
|
||
for i in range(tab_elements.count()):
|
||
try:
|
||
tab_text = tab_elements.nth(i).text_content().strip()
|
||
# Фильтруем только основные вкладки (игнорируем chevron_right и т.д.)
|
||
if tab_text and len(tab_text) > 3 and 'chevron_right' not in tab_text:
|
||
# Извлекаем чистые названия вкладок
|
||
clean_text = tab_text.replace('chevron_right', '').strip()
|
||
if clean_text and clean_text not in tabs:
|
||
tabs.append(clean_text)
|
||
except:
|
||
continue
|
||
|
||
# Убираем дубликаты и пустые значения
|
||
tabs = list(set([t for t in tabs if t]))
|
||
logger.info(f"Found available tabs: {tabs}")
|
||
return tabs
|
||
|
||
def debug_tabs_clickability(self) -> None:
|
||
"""Отладочная информация о кликабельности вкладок."""
|
||
print("=== DEBUG: TABS CLICKABILITY ===")
|
||
|
||
# Сначала покажем все доступные вкладки
|
||
available_tabs = self.get_available_tabs()
|
||
print(f"Available tabs: {available_tabs}")
|
||
|
||
tabs_to_check = ["Общая информация", "Обслуживание", "События", "Сервисы"]
|
||
|
||
for tab_name in tabs_to_check:
|
||
print(f"\n--- Checking tab: '{tab_name}' ---")
|
||
|
||
# Пробуем разные способы поиска
|
||
search_methods = [
|
||
("Locator", self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name))),
|
||
("Exact text", self.page.get_by_text(tab_name, exact=True)),
|
||
("Partial text", self.page.get_by_text(tab_name))
|
||
]
|
||
|
||
for method_name, elements in search_methods:
|
||
count = elements.count()
|
||
print(f"{method_name}: found {count} elements")
|
||
|
||
if count > 0:
|
||
for j in range(min(count, 3)):
|
||
try:
|
||
element = elements.nth(j)
|
||
text = element.text_content().strip()
|
||
is_visible = element.is_visible()
|
||
is_enabled = element.is_enabled()
|
||
element_class = element.get_attribute("class") or ""
|
||
|
||
print(f" Element {j}:")
|
||
print(f" Text: '{text}'")
|
||
print(f" Visible: {is_visible}")
|
||
print(f" Enabled: {is_enabled}")
|
||
print(f" Class: '{element_class}'")
|
||
|
||
if tab_name in text:
|
||
print(f" ✓ Contains target text")
|
||
else:
|
||
print(f" ✗ Different text")
|
||
|
||
except Exception as e:
|
||
print(f" Element {j}: error - {e}")
|
||
|
||
def debug_tabs_structure(self) -> None:
|
||
"""Отладочная информация о структуре вкладок."""
|
||
print("=== DEBUG: TABS STRUCTURE ===")
|
||
|
||
# Показываем все элементы с классами вкладок
|
||
containers = [
|
||
("v-tabs", self.page.locator(".v-tabs")),
|
||
("v-tabs__bar", self.page.locator(".v-tabs__bar")),
|
||
("v-tabs__wrapper", self.page.locator(".v-tabs__wrapper")),
|
||
("v-tabs__div", self.page.locator(".v-tabs__div")),
|
||
("v-tabs__item", self.page.locator(".v-tabs__item"))
|
||
]
|
||
|
||
for name, locator in containers:
|
||
count = locator.count()
|
||
print(f"\n{name}: found {count} elements")
|
||
|
||
for i in range(min(count, 5)):
|
||
try:
|
||
element = locator.nth(i)
|
||
text = element.text_content().strip()[:50] # Первые 50 символов
|
||
is_visible = element.is_visible()
|
||
element_class = element.get_attribute("class") or ""
|
||
print(f" {i}: '{text}' (visible: {is_visible}, class: '{element_class}')")
|
||
except Exception as e:
|
||
print(f" {i}: error - {e}")
|
||
|
||
def _wait_for_general_info_content(self, timeout: int = 10000) -> None:
|
||
"""Ожидает загрузки контента вкладки 'Общая информация'."""
|
||
logger.info("Waiting for general info content to load...")
|
||
|
||
# Ждем появления хотя бы одного из основных полей
|
||
fields_to_wait = ["Имя", "Серийный номер", "Состояние"]
|
||
|
||
for field in fields_to_wait:
|
||
try:
|
||
field_locator = self.page.get_by_text(field)
|
||
field_locator.first.wait_for(state="visible", timeout=timeout)
|
||
logger.info(f"Field '{field}' became visible")
|
||
return
|
||
except:
|
||
continue
|
||
|
||
logger.warning("None of the expected fields became visible within timeout")
|
||
|
||
def check_rack_general_info_content(self) -> None:
|
||
"""Проверяет содержимое таблицы общей информации стойки.
|
||
|
||
Использует прямое чтение input полей по индексам из отладочного вывода.
|
||
"""
|
||
logger.info("Checking rack general information content...")
|
||
|
||
# Сначала переключаемся на правильную вкладку
|
||
self.switch_to_general_info_tab()
|
||
|
||
# Ожидаемые данные
|
||
expected_data = {
|
||
"Имя": "Стойка систем питания",
|
||
"Серийный номер": "321321",
|
||
"Инвентарный номер": "321321",
|
||
"Ввод кабеля": "снизу",
|
||
"Состояние": "Введен в эксплуатацию",
|
||
"Высота в юнитах": "47",
|
||
"Владелец": "Компания 1",
|
||
"Обслуживающая организация": "Компания 1",
|
||
"Проект/Титул": "Проект обслуживания 123456"
|
||
}
|
||
|
||
logger.info("Checking rack general information content...")
|
||
|
||
# Получаем все input поля
|
||
inputs = self.page.locator("input")
|
||
input_count = inputs.count()
|
||
logger.info(f"Found {input_count} input fields")
|
||
|
||
# Проверяем конкретные поля по их индексам из отладочного вывода
|
||
field_mapping = {
|
||
"Имя": 41, # Input 41: value='Стойка систем питания'
|
||
"Серийный номер": 43, # Input 43: value='321321'
|
||
"Инвентарный номер": 45, # Input 45: value='321321'
|
||
"Ввод кабеля": 47, # Input 47: value='снизу'
|
||
"Состояние": 49, # Input 49: value='Введен в эксплуатацию'
|
||
"Высота в юнитах": 51, # Input 51: value='47'
|
||
"Владелец": 53, # Input 53: value='Компания 1'
|
||
"Обслуживающая организация": 55, # Input 55: value='Компания 1'
|
||
"Проект/Титул": 57 # Input 57: value='Проект обслуживания 123456'
|
||
}
|
||
|
||
found_fields = 0
|
||
for field_name, field_index in field_mapping.items():
|
||
try:
|
||
if field_index < input_count:
|
||
input_field = inputs.nth(field_index)
|
||
if input_field.is_visible():
|
||
actual_value = input_field.input_value()
|
||
expected_value = expected_data[field_name]
|
||
|
||
if actual_value == expected_value:
|
||
logger.info(f"✓ Field '{field_name}': '{actual_value}'")
|
||
found_fields += 1
|
||
else:
|
||
logger.warning(f"✗ Field '{field_name}' value mismatch. Expected: '{expected_value}', Actual: '{actual_value}'")
|
||
else:
|
||
logger.warning(f"✗ Field '{field_name}' at index {field_index} is not visible")
|
||
else:
|
||
logger.warning(f"✗ Field '{field_name}' index {field_index} out of range (max: {input_count})")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error checking field '{field_name}': {e}")
|
||
|
||
# Если найдено меньше половины полей, считаем это ошибкой
|
||
required_fields = 3 # Минимальное количество полей для успешной проверки
|
||
if found_fields < required_fields:
|
||
raise AssertionError(f"Too many fields missing. Found only {found_fields} out of {len(expected_data)} expected fields (minimum required: {required_fields})")
|
||
|
||
logger.info(f"Successfully verified {found_fields} out of {len(expected_data)} fields")
|
||
|
||
def check_tab_switching(self) -> None:
|
||
"""Проверяет переключение между всеми вкладками стойки.
|
||
|
||
Raises:
|
||
AssertionError: Если какая-либо вкладка не работает корректно.
|
||
"""
|
||
logger.info("Testing tab switching functionality...")
|
||
|
||
tabs_to_test = [
|
||
"Общая информация",
|
||
"Обслуживание",
|
||
"События",
|
||
"Сервисы"
|
||
]
|
||
|
||
successful_tabs = []
|
||
failed_tabs = []
|
||
|
||
for tab_name in tabs_to_test:
|
||
try:
|
||
self.switch_to_tab(tab_name)
|
||
successful_tabs.append(tab_name)
|
||
logger.info(f"✓ Tab '{tab_name}' switched successfully")
|
||
except Exception as e:
|
||
failed_tabs.append((tab_name, str(e)))
|
||
logger.error(f"✗ Failed to switch to tab '{tab_name}': {e}")
|
||
|
||
# Формируем отчет
|
||
if failed_tabs:
|
||
error_details = "; ".join([f"'{tab}': {error}" for tab, error in failed_tabs])
|
||
raise AssertionError(
|
||
f"Tab switching test failed. "
|
||
f"Successful: {len(successful_tabs)}/{len(tabs_to_test)}, "
|
||
f"Failed: {len(failed_tabs)}/{len(tabs_to_test)}. "
|
||
f"Errors: {error_details}"
|
||
)
|
||
|
||
logger.info(f"✓ All {len(successful_tabs)} tabs switched successfully")
|
||
|
||
def should_be_toolbar(self) -> None:
|
||
"""Проверяет наличие тулбара на вкладке.
|
||
|
||
Raises:
|
||
AssertionError: Если тулбар отсутствует.
|
||
"""
|
||
|
||
# Проверяем наличие тулбара через компонент
|
||
try:
|
||
self.toolbar.check_toolbar_presence("Toolbar is missing")
|
||
logger.info("Toolbar is present")
|
||
except AssertionError:
|
||
# Если стандартный тулбар не найден, проверяем альтернативные элементы
|
||
logger.warning("Standard toolbar not found, checking alternative elements")
|
||
|
||
# Проверяем наличие каких-либо элементов управления
|
||
control_elements = [
|
||
self.page.locator(ToolbarLocators.ITEMS),
|
||
self.page.get_by_role("toolbar"),
|
||
self.page.locator(".v-toolbar"),
|
||
self.page.locator("nav")
|
||
]
|
||
|
||
for element in control_elements:
|
||
if element.count() > 0 and element.first.is_visible():
|
||
logger.info("Alternative control elements found")
|
||
return
|
||
|
||
# Если ничего не найдено, проверяем наличие основного контента
|
||
if self._has_main_content():
|
||
logger.info("Main content is present, continuing test")
|
||
return
|
||
|
||
raise AssertionError("Toolbar and main content are missing")
|
||
|
||
def _has_main_content(self):
|
||
"""Проверяет наличие основного контента страницы."""
|
||
content_indicators = [
|
||
"//*[contains(text(), 'Стойка')]",
|
||
"//*[contains(text(), 'Общая информация')]",
|
||
".v-card",
|
||
".v-sheet"
|
||
]
|
||
|
||
for indicator in content_indicators:
|
||
if self.page.locator(indicator).count() > 0:
|
||
return True
|
||
return False
|
||
|
||
def should_be_toolbar_buttons(self) -> None:
|
||
"""Проверяет наличие и функциональность кнопок тулбара.
|
||
|
||
Raises:
|
||
AssertionError: Если кнопки недоступны или подсказки неверны.
|
||
"""
|
||
|
||
logger.info("Checking toolbar buttons...")
|
||
|
||
# Проверяем кнопку редактирования
|
||
try:
|
||
if self.toolbar.is_button_present("edit"):
|
||
logger.info("Edit button is present")
|
||
|
||
# Проверяем видимость (без hover из-за проблем с перекрытием)
|
||
self.toolbar.check_button_visibility("edit")
|
||
logger.info("Edit button is visible")
|
||
else:
|
||
logger.warning("Edit button is not present")
|
||
|
||
except Exception as e:
|
||
logger.warning(f"Could not check edit button: {e}")
|
||
|
||
# Проверяем кнопку закрытия если есть
|
||
try:
|
||
if self.toolbar.is_button_present("close"):
|
||
logger.info("Close button is present")
|
||
else:
|
||
logger.info("Close button is not present")
|
||
except:
|
||
logger.info("Close button check skipped")
|
||
|
||
def should_be_rack_info_table(self) -> None:
|
||
"""Проверяет наличие информации о стойке.
|
||
|
||
Raises:
|
||
AssertionError: Если информация отсутствует.
|
||
"""
|
||
|
||
logger.info("Checking rack information presence...")
|
||
|
||
# Сначала переключаемся на вкладку "Общая информация"
|
||
self.switch_to_general_info_tab()
|
||
|
||
# Проверяем наличие основных полей информации
|
||
required_fields = [
|
||
"Имя",
|
||
"Серийный номер",
|
||
"Состояние"
|
||
]
|
||
|
||
found_fields = 0
|
||
for field_name in required_fields:
|
||
field_locator = self.page.get_by_text(field_name)
|
||
if field_locator.count() > 0 and field_locator.first.is_visible():
|
||
logger.info(f"Field '{field_name}' found and visible")
|
||
found_fields += 1
|
||
else:
|
||
logger.warning(f"Field '{field_name}' not found or not visible")
|
||
|
||
if found_fields >= 2: # Требуем хотя бы 2 из 3 обязательных полей
|
||
logger.info(f"Rack information found ({found_fields} out of {len(required_fields)} required fields)")
|
||
else:
|
||
raise AssertionError(f"Rack information is missing. Found only {found_fields} out of {len(required_fields)} required fields")
|
||
|
||
def debug_page_content(self) -> None:
|
||
"""Выводит отладочную информацию о содержимом страницы."""
|
||
print("=== DEBUG: PAGE CONTENT ===")
|
||
print(f"URL: {self.page.url}")
|
||
print(f"Title: {self.page.title()}")
|
||
|
||
# Показываем доступные вкладки
|
||
available_tabs = self.get_available_tabs()
|
||
print(f"Available tabs: {available_tabs}")
|
||
|
||
# Показываем активную вкладку
|
||
for tab_name in available_tabs:
|
||
if self.is_tab_active(tab_name):
|
||
print(f"Active tab: '{tab_name}'")
|
||
break
|
||
|
||
# Поиск конкретных элементов
|
||
print("=== DEBUG: SPECIFIC ELEMENTS ===")
|
||
specific_selectors = {
|
||
"Имя": "//*[contains(text(), 'Имя')]",
|
||
"Серийный номер": "//*[contains(text(), 'Серийный номер')]",
|
||
"Стойка систем питания": "//*[contains(text(), 'Стойка систем питания')]",
|
||
"Ввод кабеля": "//*[contains(text(), 'Ввод кабеля')]",
|
||
"Состояние": "//*[contains(text(), 'Состояние')]",
|
||
"Общая информация": "//*[contains(text(), 'Общая информация')]",
|
||
"Обслуживание": "//*[contains(text(), 'Обслуживание')]",
|
||
"События": "//*[contains(text(), 'События')]",
|
||
"Сервисы": "//*[contains(text(), 'Сервисы')]"
|
||
}
|
||
|
||
for name, selector in specific_selectors.items():
|
||
elements = self.page.locator(selector)
|
||
count = elements.count()
|
||
if count > 0:
|
||
print(f"{name}: found {count} elements")
|
||
for j in range(min(count, 2)):
|
||
try:
|
||
element = elements.nth(j)
|
||
element_text = element.text_content().strip()
|
||
is_visible = element.is_visible()
|
||
print(f" {j}: '{element_text}' (visible: {is_visible})")
|
||
|
||
# Показываем родительский элемент для контекста
|
||
parent = element.locator("xpath=..")
|
||
parent_text = parent.text_content().strip()[:100] # Первые 100 символов
|
||
print(f" Parent: '{parent_text}...'")
|
||
except:
|
||
print(f" {j}: [cannot read]")
|
||
else:
|
||
print(f"{name}: not found")
|
||
|
||
# Вкладки
|
||
print("=== DEBUG: TABS ===")
|
||
tabs = self.page.locator(RackLocators.ALL_TABS)
|
||
tab_count = tabs.count()
|
||
print(f"Tabs found: {tab_count}")
|
||
for i in range(tab_count):
|
||
try:
|
||
tab = tabs.nth(i)
|
||
tab_text = tab.text_content().strip()
|
||
is_visible = tab.is_visible()
|
||
is_active = self.is_tab_active(tab_text)
|
||
print(f"Tab {i}: '{tab_text}' (visible: {is_visible}, active: {is_active})")
|
||
except:
|
||
print(f"Tab {i}: [cannot read]")
|