152 lines
6.1 KiB
Python
152 lines
6.1 KiB
Python
# components/dynamic_form_component.py
|
||
"""Универсальный компонент для работы с динамическими формами."""
|
||
|
||
from typing import Optional, Dict, List, Any
|
||
from playwright.sync_api import Page, Locator
|
||
from tools.logger import get_logger
|
||
|
||
logger = get_logger("DYNAMIC_FORM_COMPONENT")
|
||
|
||
|
||
class DynamicFormComponent:
|
||
"""Компонент для работы с формами, находит поля по меткам."""
|
||
|
||
def __init__(self, page: Page, form_selector: str = "form, .v-form"):
|
||
self.page = page
|
||
self.form_selector = form_selector
|
||
self._form_container = None
|
||
self._field_labels_cache = {}
|
||
|
||
def _get_form_container(self) -> Locator:
|
||
"""Получает контейнер формы."""
|
||
if self._form_container is None:
|
||
container = self.page.locator(self.form_selector)
|
||
try:
|
||
container.wait_for(state="visible", timeout=5000)
|
||
self._form_container = container
|
||
except:
|
||
raise ValueError(f"Form container not found: {self.form_selector}")
|
||
return self._form_container
|
||
|
||
def get_all_field_labels(self) -> List[str]:
|
||
"""Получает все метки полей в форме."""
|
||
self._load_field_labels()
|
||
return list(self._field_labels_cache.keys())
|
||
|
||
def _load_field_labels(self) -> None:
|
||
"""Загружает метки полей формы."""
|
||
if self._field_labels_cache:
|
||
return
|
||
|
||
form = self._get_form_container()
|
||
labels = {}
|
||
|
||
# Ищем все элементы с текстом, которые могут быть метками
|
||
# Адаптируйте селекторы под вашу структуру DOM
|
||
label_elements = form.locator(
|
||
".v-label, label, .field-label, [class*='label']"
|
||
)
|
||
|
||
for i in range(label_elements.count()):
|
||
elem = label_elements.nth(i)
|
||
label_text = elem.text_content().strip()
|
||
if label_text and len(label_text) < 100: # Исключаем большие тексты
|
||
labels[label_text] = elem
|
||
|
||
self._field_labels_cache = labels
|
||
|
||
def get_field_by_label(self, label_text: str) -> Optional[Locator]:
|
||
"""Находит поле по метке."""
|
||
self._load_field_labels()
|
||
|
||
# Прямое совпадение
|
||
if label_text in self._field_labels_cache:
|
||
return self._get_field_input(self._field_labels_cache[label_text])
|
||
|
||
# Частичное совпадение
|
||
for label, element in self._field_labels_cache.items():
|
||
if label_text in label or label in label_text:
|
||
return self._get_field_input(element)
|
||
|
||
logger.warning(f"Поле с меткой '{label_text}' не найдено")
|
||
return None
|
||
|
||
def _get_field_input(self, label_element: Locator) -> Optional[Locator]:
|
||
"""Получает элемент ввода рядом с меткой."""
|
||
# Разные стратегии поиска input элемента
|
||
strategies = [
|
||
lambda: label_element.locator("+ input, + textarea").first,
|
||
lambda: label_element.locator("../..").locator("input, textarea").first,
|
||
lambda: self.page.locator(f"input[aria-label*='{label_element.text_content()}']"),
|
||
lambda: self.page.locator(f"input[placeholder*='{label_element.text_content()}']"),
|
||
]
|
||
|
||
for strategy in strategies:
|
||
try:
|
||
input_elem = strategy()
|
||
if input_elem.count() > 0:
|
||
return input_elem
|
||
except:
|
||
continue
|
||
|
||
return None
|
||
|
||
def get_field_type_by_label(self, label_text: str) -> str:
|
||
"""Определяет тип поля по метке."""
|
||
field_element = self.get_field_by_label(label_text)
|
||
if not field_element:
|
||
return "unknown"
|
||
|
||
# Определяем тип по атрибутам
|
||
input_type = field_element.get_attribute("type")
|
||
role = field_element.get_attribute("role")
|
||
|
||
if input_type == "checkbox" or role == "checkbox":
|
||
return "checkbox"
|
||
elif role == "combobox" or field_element.get_attribute("aria-haspopup") == "listbox":
|
||
return "combobox"
|
||
else:
|
||
return "text"
|
||
|
||
def fill_field_by_label(self, label_text: str, value: Any) -> bool:
|
||
"""Заполняет поле по метке."""
|
||
field_type = self.get_field_type_by_label(label_text)
|
||
|
||
if field_type == "text":
|
||
return self._fill_text_field_by_label(label_text, str(value))
|
||
elif field_type == "combobox":
|
||
return self._fill_combobox_by_label(label_text, str(value))
|
||
elif field_type == "checkbox":
|
||
return self._set_checkbox_by_label(label_text, bool(value))
|
||
else:
|
||
logger.warning(f"Неизвестный тип поля для '{label_text}'")
|
||
return False
|
||
|
||
def _fill_text_field_by_label(self, label_text: str, value: str) -> bool:
|
||
"""Заполняет текстовое поле по метке."""
|
||
field = self.get_field_by_label(label_text)
|
||
if not field:
|
||
return False
|
||
|
||
try:
|
||
field.click()
|
||
field.fill("")
|
||
field.fill(value)
|
||
|
||
# Проверяем результат
|
||
actual_value = field.input_value()
|
||
if actual_value == value:
|
||
logger.debug(f"✓ Заполнено поле '{label_text}': '{value}'")
|
||
return True
|
||
else:
|
||
logger.warning(f"Несоответствие значения для '{label_text}'")
|
||
return False
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при заполнении поля '{label_text}': {e}")
|
||
return False
|
||
|
||
def _fill_combobox_by_label(self, label_text: str, value: str) -> bool:
|
||
"""Заполняет combobox по метке."""
|
||
# Реализация аналогичная modal_rack_edit.py
|
||
# ...
|