e-nms_qa_automation/components_derived/modal_rack_edit.py

443 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""Модуль для работы с модальным окном редактирования стойки."""
from dataclasses import dataclass, field
from typing import Optional, Dict
from playwright.sync_api import Page, Locator
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 components.base_component import BaseComponent
logger = get_logger("MODAL_EDIT_RACK")
#logger.setLevel("INFO")
@dataclass
class RackEditData:
"""Класс для хранения данных редактирования стойки."""
# Основные поля (редактируемые)
name: str = ""
serial: str = ""
inventory: str = ""
comment: str = ""
# Combobox поля (редактируемые)
cable_entry: str = ""
state: str = ""
owner: str = ""
service_org: str = ""
project: str = ""
# Дополнительные поля (редактируемые)
power: str = ""
# Checkbox поля (редактируемые) - если есть
ventilation_panel: Optional[bool] = None
# Правила доступа (если есть)
read_access_rules: str = ""
write_access_rules: str = ""
sms_access_rules: str = ""
email_access_rules: str = ""
push_access_rules: str = ""
class ModalEditRack(BaseComponent):
"""Компонент для работы с модальным окном редактирования стойки."""
def __init__(self, page: Page) -> None:
"""
Инициализирует компонент редактирования стойки.
Args:
page (Page): Экземпляр страницы Playwright
"""
super().__init__(page)
self._form_container = None
self._fields_cache = {}
self.toolbar = ToolbarComponent(page, "")
# Кнопка "Переместить" (data-testid)
replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON)
self.replace_button = TooltipButton(page, replace_button_locator, "replace")
# Кнопка "Сохранить" (data-testid)
done_button_locator = page.locator(RackLocators.TOOLBAR_DONE_BUTTON)
self.done_button = TooltipButton(page, done_button_locator, "done")
# Кнопка "Отменить" (data-testid)
close_button_locator = page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON)
self.close_button = TooltipButton(page, close_button_locator, "close")
# Кнопка "Удалить" (data-testid)
remove_button_locator = page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON)
self.remove_button = TooltipButton(page, remove_button_locator, "remove")
# Добавляем кнопки в тулбар
self.toolbar.add_tooltip_button(replace_button_locator, "replace")
self.toolbar.add_tooltip_button(done_button_locator, "done")
self.toolbar.add_tooltip_button(close_button_locator, "close")
self.toolbar.add_tooltip_button(remove_button_locator, "remove")
def click_remove_button(self) -> None:
"""
Кликает на кнопку 'Удалить' и обрабатывает диалог подтверждения.
"""
logger.debug("Clicking on 'Remove' button...")
# Проверяем видимость кнопки
self.toolbar.check_button_visibility("remove")
self.toolbar.check_button_tooltip("remove", "Удалить")
# Кликаем на кнопку удаления
self.toolbar.get_button_by_name("remove").click()
self.wait_for_timeout(1000)
# Ожидаем появления диалога подтверждения
self._handle_remove_confirmation_dialog()
def click_done_button(self) -> None:
"""
Кликает на кнопку 'Сохранить' и обрабатывает диалог подтверждения.
"""
logger.debug("Clicking on 'Done' button...")
# Проверяем видимость кнопки
self.toolbar.check_button_visibility("done")
self.toolbar.check_button_tooltip("done", "Сохранить")
# Кликаем на кнопку сохранения
self.toolbar.get_button_by_name("done").click()
self.wait_for_timeout(1000)
def confirm_remove_dialog(self, confirm: bool = True) -> None:
"""
Подтверждает или отклоняет удаление в диалоговом окне.
Args:
confirm (bool): Если True - подтвердить удаление, если False - отменить
"""
logger.debug(f"Confirming remove dialog with: {'Да' if confirm else 'Нет'}")
# Ждем немного перед поиском диалога
self.wait_for_timeout(1500)
# Ищем активный диалог
dialog = self.page.locator("div.v-dialog--active")
# Проверяем, что диалог найден и содержит нужный текст
assert dialog.count() > 0, "No active dialog found"
# Проверяем текст диалога
dialog_text = dialog.first.text_content()
logger.debug("Dialog text: %s", dialog_text)
# Должен содержать "Запрос подтверждения" и "Удалить"
assert "Запрос подтверждения" in dialog_text, "Not a confirmation dialog"
# Ищем кнопку по data-testid
if confirm:
button = self.page.locator(RackLocators.CONFIRM_REMOVE_YES_BUTTON)
else:
button = self.page.locator(RackLocators.CONFIRM_REMOVE_NO_BUTTON)
# Проверяем, что кнопка найдена
assert button.count() > 0, "Button not found with selector"
# Кликаем на кнопку
button_text = button.first.text_content()
logger.debug("Clicking button with text: %s", button_text)
button.first.click()
self.wait_for_timeout(2000)
# Проверяем, что диалог закрылся
self.wait_for_timeout(1000)
logger.debug("Remove confirmation completed")
def _handle_remove_confirmation_dialog(self) -> None:
"""Обрабатывает диалог подтверждения удаления."""
logger.debug("Handling remove confirmation dialog...")
self.confirm_remove_dialog(confirm=True)
# Остальные существующие методы остаются без изменений
def _get_form_container(self) -> Locator:
"""
Получает контейнер формы редактирования.
"""
if self._form_container is None:
form_container = self.page.locator("[data-testid='cabinet-bar__cabinet-form']")
try:
form_container.wait_for(state="visible", timeout=10000)
self._form_container = form_container
except:
raise ValueError("Cabinet form container not found")
return self._form_container
def get_available_fields(self) -> list:
"""
Получает список доступных полей.
"""
fields_locators = self._get_form_fields()
return list(fields_locators.keys()) if fields_locators else []
def _get_form_fields(self) -> dict:
"""
Получает все поля формы редактирования стойки.
"""
if self._fields_cache:
return self._fields_cache
form_container = self._get_form_container()
fields_locators = self.get_input_fields_locators(form_container)
self._fields_cache = fields_locators
return fields_locators
def _fill_text_field(self, field_name: str, value: str) -> bool:
"""
Заполняет текстовое поле по полному совпадению названия.
"""
fields_locators = self._get_form_fields()
# Ищем точное совпадение
if field_name not in fields_locators:
logger.debug(f"Text field '{field_name}' not found. Available fields: {list(fields_locators.keys())}")
return False
field_container = fields_locators[field_name]
try:
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(300)
# Ищем input поле
input_field = field_container.locator("input, textarea").first
if input_field.count() == 0:
logger.debug(f"Field '{field_name}' doesn't have input element")
return False
# Очищаем и заполняем
input_field.click()
self.wait_for_timeout(200)
input_field.fill("")
self.wait_for_timeout(200)
input_field.fill(value)
self.wait_for_timeout(500)
# Проверяем что значение установлено
actual_value = input_field.input_value()
if actual_value == value:
logger.debug(f"✓ Text field '{field_name}' filled with: '{value}'")
return True
else:
logger.warning(f"Field '{field_name}' value mismatch: expected '{value}', got '{actual_value}'")
return False
except Exception as e:
logger.error(f"Error filling text field '{field_name}': {e}")
return False
def _fill_combobox_field(self, field_name: str, value: str) -> bool:
"""
Заполняет combobox поле по полному совпадению названия.
"""
fields_locators = self._get_form_fields()
# Ищем точное совпадение
if field_name not in fields_locators:
logger.debug(f"Combobox field '{field_name}' not found. Available fields: {list(fields_locators.keys())}")
return False
field_container = fields_locators[field_name]
try:
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(300)
# Ищем кнопку открытия dropdown
dropdown_button = field_container.locator(".v-input__append-inner, [role='button']").first
if dropdown_button.count() == 0:
# Может быть поле уже открыто
input_field = field_container.locator("input").first
input_field.click()
self.wait_for_timeout(1000)
else:
dropdown_button.click()
self.wait_for_timeout(1000)
# Ищем выпадающий список
active_menu = None
menu_selectors = [
".v-menu__content.menuable__content__active",
".v-select__menu",
".v-autocomplete__content",
".v-menu__content"
]
for selector in menu_selectors:
menu = self.page.locator(selector).first
if menu.count() > 0 and menu.is_visible():
active_menu = menu
break
if not active_menu:
logger.debug(f"No dropdown menu found for '{field_name}'")
return False
# Ищем нужный элемент
dropdown_item = active_menu.locator(f"div[role='listitem'], .v-list-item").filter(
has_text=value
).first
if dropdown_item.count() == 0:
logger.debug(f"Value '{value}' not found in dropdown for '{field_name}'")
self.page.keyboard.press("Escape")
return False
# Выбираем значение
dropdown_item.click()
logger.debug(f"✓ Combobox '{field_name}' set to: '{value}'")
self.wait_for_timeout(1000)
return True
except Exception as e:
logger.error(f"Error filling combobox '{field_name}': {e}")
self.page.keyboard.press("Escape")
return False
def _set_checkbox_field(self, checkbox_label: str, value: bool) -> bool:
"""
Устанавливает состояние checkbox используя input[type="checkbox"].
"""
try:
logger.debug(f"Setting checkbox '{checkbox_label}' to {value}")
# Ищем все checkbox элементы в форме
form_container = self._get_form_container()
checkboxes = form_container.locator("input[type='checkbox']")
if checkboxes.count() == 0:
logger.warning("No checkbox elements found in form")
return False
logger.debug(f"Found {checkboxes.count()} checkbox(es) in form")
# Если несколько чекбоксов, ищем нужный
target_checkbox = None
# Вариант 1: По data-testid
for i in range(checkboxes.count()):
checkbox = checkboxes.nth(i)
testid = checkbox.get_attribute("data-testid")
if testid == "cabinet-bar__main__checkbox__available_ventilation_panel":
target_checkbox = checkbox
logger.debug(f"Found checkbox by data-testid: {testid}")
break
# Вариант 2: По role="checkbox" и aria-checked
if target_checkbox is None:
for i in range(checkboxes.count()):
checkbox = checkboxes.nth(i)
role = checkbox.get_attribute("role")
if role == "checkbox":
target_checkbox = checkbox
logger.debug(f"Found checkbox by role='checkbox'")
break
# Вариант 3: Первый найденный checkbox
if target_checkbox is None:
target_checkbox = checkboxes.first
logger.debug("Using first found checkbox")
# Проверяем состояние
current_aria_checked = target_checkbox.get_attribute("aria-checked")
is_currently_checked = current_aria_checked == "true"
logger.debug(f"Checkbox current state: {is_currently_checked}")
# Если уже в нужном состоянии
if is_currently_checked == value:
logger.debug(f"Checkbox already in desired state ({value})")
return True
# Кликаем на чекбокс
target_checkbox.click(force=True)
self.wait_for_timeout(800)
# Проверяем результат
new_aria_checked = target_checkbox.get_attribute("aria-checked")
is_now_checked = new_aria_checked == "true"
if is_now_checked == value:
logger.info(f"✓ Checkbox '{checkbox_label}' set to {value}")
return True
else:
logger.warning(f"Checkbox state didn't change. Still: {is_now_checked}")
return False
except Exception as e:
logger.error(f"Error setting checkbox '{checkbox_label}': {e}")
return False
def fill_rack_data(self, rack_data: RackEditData) -> Dict[str, int]:
"""
Заполняет все доступные поля в форме редактирования.
"""
logger.debug("Filling rack edit form...")
results = {
"text_fields_filled": 0,
"combobox_fields_filled": 0,
"checkboxes_set": 0
}
# Получаем доступные поля
available_fields = self.get_available_fields()
logger.debug(f"Available fields in form: {available_fields}")
# 1. Заполняем текстовые поля (только если поле существует)
text_fields_mapping = {
"Имя": rack_data.name,
"Серийный номер": rack_data.serial,
"Инвентарный номер": rack_data.inventory,
"Комментарий": rack_data.comment,
"Выделенная мощность": rack_data.power,
"Правила доступа для чтения по умолчанию": rack_data.read_access_rules,
"Правила доступа для записи по умолчанию": rack_data.write_access_rules,
"Правила доступа по умолчанию для получения СМС": rack_data.sms_access_rules,
"Правила доступа по умолчанию для получения email сообщения": rack_data.email_access_rules,
"Правила доступа по умолчанию для получения push уведомлений": rack_data.push_access_rules
}
for field_name, value in text_fields_mapping.items():
if value and value.strip() and field_name in available_fields:
if self._fill_text_field(field_name, value):
results["text_fields_filled"] += 1
# 2. Заполняем combobox поля (только если поле существует)
combobox_fields_mapping = {
"Ввод кабеля": rack_data.cable_entry,
"Состояние": rack_data.state,
"Владелец": rack_data.owner,
"Обслуживающая организация": rack_data.service_org,
"Проект/Титул": rack_data.project
}
for field_name, value in combobox_fields_mapping.items():
if value and value.strip() and field_name in available_fields:
if self._fill_combobox_field(field_name, value):
results["combobox_fields_filled"] += 1
# 3. Устанавливаем checkbox (если есть)
if rack_data.ventilation_panel is not None:
if self._set_checkbox_field("Вентиляционная панель", rack_data.ventilation_panel):
results["checkboxes_set"] += 1
logger.debug(f"Fill results: {results}")
return results