"""Модуль вкладки 'Стойка'.""" 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]")