From 0295852986cde64b7a614959861656e0a69e3e04 Mon Sep 17 00:00:00 2001 From: Radislav Date: Wed, 11 Mar 2026 14:14:15 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D1=83=D0=BD=D0=B8=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=D1=86=D0=B8=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B=20=D1=81=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D0=BC=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=81=D1=82=D0=BE=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен базовый класс BaseRackForm с общей логикой для работы с формами - Вынесена общая функциональность по заполнению полей, очистке и проверке ошибок - Упрощены классы CreateRackForm и EditRackForm за счет наследования от BaseRackForm - Обновлены зависимые компоненты (create_element_frame, edit_rack_maker) - Исправлены тесты создания стойки с учетом новой архитектуры --- forms/create_rack_form.py | 467 +------------------ forms/edit_rack_form.py | 605 ++----------------------- frames/create_element_frame.py | 103 +++-- makers/edit_rack_maker.py | 185 ++++---- tests/e2e/elements/test_create_rack.py | 18 +- 5 files changed, 212 insertions(+), 1166 deletions(-) diff --git a/forms/create_rack_form.py b/forms/create_rack_form.py index d1a3fef..7fb6395 100644 --- a/forms/create_rack_form.py +++ b/forms/create_rack_form.py @@ -1,43 +1,23 @@ +# forms/rack_create_form.py """Модуль для работы с формой создания стойки.""" -import time from dataclasses import dataclass -from typing import List, Dict, Any +from typing import Dict from playwright.sync_api import Page from tools.logger import get_logger from locators.rack_locators import RackLocators -from elements.text_input_element import TextInput -from components.base_component import BaseComponent -from components.dropdown_list_component import DropdownList - +from forms.base_rack_form import BaseRackForm, BaseRackData logger = get_logger("CREATE_RACK_FORM") -logger.setLevel("INFO") @dataclass -class CreateRackData: +class CreateRackData(BaseRackData): """Класс для хранения данных создаваемой стойки.""" - - # Основные поля - name: str = "" - serial: str = "" - inventory: str = "" - comment: str = "" - - # Combobox поля - cable_entry: str = "" - state: str = "" - depth: str = "" - usize: str = "" - - # Combobox поля (не заполняемые) - owner: str = "" - service_org: str = "" - project: str = "" + pass # Используем все поля из базового класса -class CreateRackForm(BaseComponent): +class CreateRackForm(BaseRackForm): """Компонент для работы с формой создания стойки.""" # Маппинг текстовых полей @@ -48,7 +28,7 @@ class CreateRackForm(BaseComponent): "Инвентарный номер": ("inventory", "inventory_input"), } - # Маппинг полей для заполнения combobox полей + # Маппинг combobox полей COMBOBOX_FIELDS_MAPPING = { "Ввод кабеля": ("cable_entry", "cable_entry_input", "cable_entry_list"), "Состояние": ("state", "state_input", "state_list"), @@ -59,7 +39,7 @@ class CreateRackForm(BaseComponent): "Проект/Титул": ("project", "project_input", "project_list") } - # Локаторы для текстовых полей (из RackLocators) + # Локаторы для текстовых полей TEXT_FIELDS_LOCATORS = { "Имя": RackLocators.CREATE_RACK_FORM_FIELD_NAME, "Комментарий": RackLocators.CREATE_RACK_FORM_FIELD_COMMENT, @@ -67,7 +47,7 @@ class CreateRackForm(BaseComponent): "Инвентарный номер": RackLocators.CREATE_RACK_FORM_FIELD_INVENTORY, } - # Локаторы для combobox полей (из RackLocators) + # Локаторы для combobox полей COMBOBOX_FIELDS_LOCATORS = { "Высота в юнитах": RackLocators.CREATE_RACK_FORM_SELECT_USIZE, "Глубина (мм)": RackLocators.CREATE_RACK_FORM_SELECT_DEPTH, @@ -79,230 +59,11 @@ class CreateRackForm(BaseComponent): } def __init__(self, page: Page) -> None: - """Инициализирует компонент формы создания стойки. - - Args: - page: Экземпляр страницы Playwright - """ - - super().__init__(page) - self.page = page - self.content_items = {} - self.available_fields = None - - # Инициализация полей формы - self._init_form_fields() - - def _init_form_fields(self) -> None: - """Инициализирует все поля формы создания.""" - - # Получаем доступные поля формы - container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER).nth(1) - self.available_fields = self.get_input_fields_locators(container_locator) - - self._init_text_fields() - self._init_combobox_fields() - - def _init_text_fields(self) -> None: - """Инициализирует текстовые поля формы.""" - - for field_label, (attr_name, widget_name) in self.TEXT_FIELDS_MAPPING.items(): - locator = self.TEXT_FIELDS_LOCATORS.get(field_label) - if not locator: - continue - - self._init_single_text_field(field_label, locator, widget_name) - - def _init_single_text_field(self, field_label: str, locator: str, widget_name: str) -> None: - """Инициализирует одно текстовое поле. - - Args: - field_label: Метка поля - locator: Локатор поля - widget_name: Имя виджета - """ - - try: - element = self.page.locator(locator).first - if element.count() > 0 and element.is_visible(): - # Создаем TextInput для поля - field_input = TextInput(self.page, element, widget_name) - self.content_items[widget_name] = field_input - logger.debug(f"Initialized text field: '{field_label}'") - except Exception as e: - logger.error(f"Error initializing text field '{field_label}': {e}") - - def _init_combobox_fields(self) -> None: - """Инициализирует combobox поля формы.""" - - for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items(): - locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_label) - if not locator: - continue - - self._init_single_combobox_field(field_label, locator, input_name, list_name) - - def _init_single_combobox_field( - self, field_label: str, locator: str, input_name: str, list_name: str - ) -> None: - """Инициализирует одно combobox поле. - - Args: - field_label: Метка поля - locator: Локатор поля - input_name: Имя поля ввода - list_name: Имя списка - """ - - try: - element = self.page.locator(locator).first - if element.count() > 0 and element.is_visible(): - # Для combobox создаем TextInput для клика - field_input = TextInput(self.page, element, input_name) - self.content_items[input_name] = field_input - # Добавляем DropdownList для выбора значений - self.content_items[list_name] = DropdownList(self.page) - logger.debug(f"Initialized combobox field: '{field_label}'") - except Exception as e: - logger.error(f"Error initializing combobox field '{field_label}': {e}") - - - def clear_field(self, field_name: str) -> None: - """Очищает указанное поле. - - Args: - field_name: Название поля для очистки - """ - - logger.debug(f"Clearing field: '{field_name}'") - - # Получаем локатор поля - locator = None - if field_name in self.COMBOBOX_FIELDS_LOCATORS: - locator = self.COMBOBOX_FIELDS_LOCATORS[field_name] - elif field_name in self.TEXT_FIELDS_LOCATORS: - locator = self.TEXT_FIELDS_LOCATORS[field_name] - else: - logger.warning(f"Unknown field: {field_name}") - return - - field_element = self.page.locator(locator).first - - if field_element.count() == 0: - logger.debug(f"Field '{field_name}' not found") - return - - # Для текстовых полей - if field_name in self.TEXT_FIELDS_LOCATORS: - try: - field_element.click() - field_element.page.keyboard.press("Control+A") - field_element.page.keyboard.press("Backspace") - self.wait_for_timeout(200) - logger.debug(f"Text field '{field_name}' cleared") - except Exception as e: - logger.debug(f"Could not clear text field '{field_name}': {e}") - return - - # Для combobox полей - if field_name in self.COMBOBOX_FIELDS_LOCATORS: - # Поднимаемся до родительского контейнера - parent_container = field_element.locator( - "xpath=ancestor::div[contains(@class, 'v-input')]" - ).first - - if parent_container.count() == 0: - logger.debug(f"Parent container not found for field '{field_name}'") - return - - # Ищем кнопку очистки (крестик) - clear_button = parent_container.locator( - ".v-input__icon--clear button, .v-input__icon--append button, i.mdi-close-circle, i.mdi-close" - ).first - - if clear_button.count() > 0 and clear_button.is_visible(): - clear_button.click() - self.wait_for_timeout(300) - logger.debug(f"Combobox field '{field_name}' cleared") - else: - logger.debug(f"Clear button not found for field '{field_name}'") - - def get_content_item(self, item_name: str) -> Any: - """Возвращает элемент контента по имени. - - Args: - item_name: Имя элемента - - Returns: - Элемент или None если не найден - """ - return self.content_items.get(item_name) - - def _scroll_to_element_in_dropdown(self, value: str) -> bool: - """Скроллит выпадающий список до элемента с нужным текстом используя playwright. - - Args: - value: Текст для поиска - - Returns: - bool: True если элемент найден, False в противном случае - """ - - logger.debug(f"Scrolling to find element with text: '{value}'") - - # Получаем активное выпадающее меню - dropdown_menu = self.page.locator("div.menuable__content__active").first - - if dropdown_menu.count() == 0: - logger.error("Active dropdown menu not found") - return False - - max_attempts = 10 - attempts = 0 - last_item_text = "" - - while attempts < max_attempts: - # Получаем все видимые элементы списка - visible_items = dropdown_menu.locator("a.v-list__tile, div[role='listitem']").all() - - if visible_items: - # Проверяем каждый видимый элемент - for item in visible_items: - item_text = item.text_content() or "" - if value in item_text: - logger.debug(f"Found element with text '{value}'") - # Скроллим до элемента - item.scroll_into_view_if_needed() - self.wait_for_timeout(300) - return True - - # Если элемент не найден, скроллим до последнего видимого элемента - last_item = visible_items[-1] - last_item_text = last_item.text_content() or "" - logger.debug(f"Scrolling to last visible item: '{last_item_text}'") - last_item.scroll_into_view_if_needed() - self.wait_for_timeout(500) - else: - # Если нет видимых элементов, скроллим вниз - dropdown_menu.evaluate("(el) => el.scrollTop += 200") - self.wait_for_timeout(300) - - attempts += 1 - logger.debug(f"Scroll attempt {attempts}/{max_attempts}") - - logger.warning(f"Element with text '{value}' not found after {max_attempts} scroll attempts") - return False + """Инициализирует компонент формы создания стойки.""" + super().__init__(page, RackLocators.CREATE_RACK_FORM_CONTAINER) def fill_rack_data(self, rack_data: CreateRackData) -> Dict[str, int]: - """Заполняет поля формы создания стойки. - - Args: - rack_data: Данные для заполнения - - Returns: - Словарь с результатами заполнения - """ - + """Заполняет поля формы создания стойки.""" results = { "text_fields_filled": 0, "combobox_fields_filled": 0, @@ -314,207 +75,3 @@ class CreateRackForm(BaseComponent): logger.info(f"Filled {results['text_fields_filled']} text fields and " f"{results['combobox_fields_filled']} combobox fields") return results - - def _fill_text_fields(self, rack_data: CreateRackData, results: Dict[str, int]) -> None: - """Заполняет текстовые поля. - - Args: - rack_data: Данные для заполнения - results: Словарь с результатами - """ - - for field_label, (attr_name, field_name) in self.TEXT_FIELDS_MAPPING.items(): - value = getattr(rack_data, attr_name, "") - if not value or not str(value).strip(): - continue - - self._fill_single_text_field(field_label, field_name, value, results) - - def _fill_single_text_field( - self, field_label: str, field_name: str, value: str, results: Dict[str, int] - ) -> None: - """Заполняет одно текстовое поле. - - Args: - field_label: Метка поля - field_name: Имя поля - value: Значение для заполнения - results: Словарь с результатами - """ - - try: - input_field = self.get_content_item(field_name) - if input_field: - input_field.input_value(value) - results["text_fields_filled"] += 1 - logger.debug(f"Field '{field_label}' filled: '{value}'") - except Exception as e: - logger.error(f"Error filling field '{field_label}': {e}") - - def _fill_combobox_fields(self, rack_data: CreateRackData, results: Dict[str, int]) -> None: - """Заполняет combobox поля. - - Args: - rack_data: Данные для заполнения - results: Словарь с результатами - """ - - for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items(): - value = getattr(rack_data, attr_name, "") - if not value or not str(value).strip(): - continue - - self._fill_single_combobox_field( - field_label, input_name, list_name, value, results - ) - - def _fill_single_combobox_field( - self, field_label: str, input_name: str, list_name: str, value: str, results: Dict[str, int] - ) -> None: - """Заполняет одно combobox поле. - - Args: - field_label: Метка поля - input_name: Имя поля ввода - list_name: Имя списка - value: Значение для выбора - results: Словарь с результатами - """ - - try: - combobox_field = self.get_content_item(input_name) - if not combobox_field: - logger.warning(f"Field '{field_label}' input not found") - return - - # Кликаем для открытия выпадающего списка - combobox_field.click(force=True) - self.wait_for_timeout(1000) - - # Скроллим до нужного элемента - if not self._scroll_to_element_in_dropdown(value): - logger.error(f"Could not find element with text '{value}' after scrolling") - # Закрываем выпадающий список кликом вне - self.page.mouse.click(10, 10) - self.wait_for_timeout(300) - return - - # Получаем активное выпадающее меню - dropdown_menu = self.page.locator("div.menuable__content__active").first - - # Ищем элемент с нужным текстом - item_locator = dropdown_menu.locator(f"a.v-list__tile:has-text('{value}')").first - - if item_locator.count() == 0: - item_locator = dropdown_menu.locator(f"span:has-text('{value}')").first - - if item_locator.count() == 0: - item_locator = dropdown_menu.locator(f"div[role='listitem']:has-text('{value}')").first - - if item_locator.count() > 0: - # Убеждаемся что элемент видим и кликаем - item_locator.scroll_into_view_if_needed() - self.wait_for_timeout(300) - item_locator.click() - results["combobox_fields_filled"] += 1 - logger.debug(f"Field '{field_label}' set: '{value}'") - - # Небольшая пауза после выбора - self.wait_for_timeout(500) - else: - logger.error(f"Item with text '{value}' not found in dropdown for field '{field_label}'") - # Закрываем выпадающий список кликом вне - self.page.mouse.click(10, 10) - self.wait_for_timeout(300) - - except Exception as e: - logger.error(f"Error filling combobox '{field_label}': {e}") - self.page.mouse.click(10, 10) - - def is_field_highlighted_as_error(self, field_name: str) -> bool: - """Проверяет, подсвечено ли поле как ошибочное. - - Args: - field_name: Название поля для проверки - - Returns: - bool: True если поле подсвечено ошибкой, False в противном случае - """ - - # Проверяем в текстовых полях - if field_name in self.TEXT_FIELDS_LOCATORS: - locator = self.TEXT_FIELDS_LOCATORS[field_name] - field_element = self.page.locator(locator).first - - if field_element.count() == 0: - logger.debug(f"Field '{field_name}' not found") - return False - - # Поднимаемся до родительского контейнера с классом v-input - parent_input = field_element.locator("xpath=ancestor::div[contains(@class, 'v-input')]").first - - if parent_input.count() > 0: - # Проверяем наличие класса ошибки - class_attr = parent_input.get_attribute("class") or "" - is_error = "v-input--error" in class_attr or "error--text" in class_attr - logger.debug(f"Field '{field_name}' error state: {is_error}, classes: {class_attr}") - return is_error - - # Проверяем в combobox полях - elif field_name in self.COMBOBOX_FIELDS_LOCATORS: - locator = self.COMBOBOX_FIELDS_LOCATORS[field_name] - field_element = self.page.locator(locator).first - - if field_element.count() == 0: - logger.debug(f"Field '{field_name}' not found") - return False - - # Поднимаемся до родительского контейнера с классом v-input - parent_input = field_element.locator("xpath=ancestor::div[contains(@class, 'v-input')]").first - - if parent_input.count() > 0: - # Проверяем наличие класса ошибки - class_attr = parent_input.get_attribute("class") or "" - is_error = "v-input--error" in class_attr or "error--text" in class_attr - logger.debug(f"Field '{field_name}' error state: {is_error}, classes: {class_attr}") - return is_error - - return False - - def verify_required_fields_highlighted(self, field_names: List[str]) -> Dict[str, bool]: - """Проверяет, что указанные поля подсвечены как обязательные (с ошибкой). - - Args: - field_names: Список названий полей для проверки - - Returns: - Словарь с результатами проверки {field_name: is_highlighted} - """ - - results = {} - - for field_name in field_names: - results[field_name] = self.is_field_highlighted_as_error(field_name) - logger.debug(f"Field '{field_name}' highlighted: {results[field_name]}") - - return results - - def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool: - """Ожидает появления подсветки ошибки на поле. - - Args: - field_name: Название поля - timeout: Таймаут в миллисекундах - - Returns: - bool: True если ошибка появилась, False в противном случае - """ - - start_time = time.time() - - while (time.time() - start_time) * 1000 < timeout: - if self.is_field_highlighted_as_error(field_name): - return True - self.wait_for_timeout(200) - - return False diff --git a/forms/edit_rack_form.py b/forms/edit_rack_form.py index e0e03db..0f0ef96 100644 --- a/forms/edit_rack_form.py +++ b/forms/edit_rack_form.py @@ -1,48 +1,28 @@ -"""Модуль для работы с формой редактирования стойки в модальном окне.""" +# forms/rack_edit_form.py +"""Модуль для работы с формой редактирования стойки.""" -import time from dataclasses import dataclass -from typing import Optional, List, Dict, Any +from typing import Optional, Dict from playwright.sync_api import Page from tools.logger import get_logger from locators.rack_locators import RackLocators -from elements.text_input_element import TextInput -from components.base_component import BaseComponent -from components.dropdown_list_component import DropdownList +from forms.base_rack_form import BaseRackForm, BaseRackData logger = get_logger("EDIT_RACK_FORM") -logger.setLevel("INFO") @dataclass -class EditRackFormData: +class EditRackData(BaseRackData): """Класс для хранения данных редактируемой стойки.""" - # Основные поля - name: str = "" - serial: str = "" - inventory: str = "" - comment: str = "" + # Дополнительное поле для формы редактирования allocated_power: str = "" - - # Combobox поля - cable_entry: str = "" - state: str = "" - depth: str = "" - usize: str = "" - - # Combobox поля (не заполняемые) - owner: str = "" - service_org: str = "" - project: str = "" - - # Checkbox поле ventilation_panel: Optional[bool] = None -class EditRackForm(BaseComponent): - """Компонент для работы с формой редактирования стойки в модальном окне.""" +class EditRackForm(BaseRackForm): + """Компонент для работы с формой редактирования стойки.""" # Маппинг текстовых полей TEXT_FIELDS_MAPPING = { @@ -53,7 +33,7 @@ class EditRackForm(BaseComponent): "Выделенная мощность (Вт/ВА)": ("allocated_power", "power_input"), } - # Маппинг полей для заполнения combobox полей + # Маппинг combobox полей COMBOBOX_FIELDS_MAPPING = { "Ввод кабеля": ("cable_entry", "cable_entry_input", "cable_entry_list"), "Состояние": ("state", "state_input", "state_list"), @@ -64,7 +44,12 @@ class EditRackForm(BaseComponent): "Проект/Титул": ("project", "project_input", "project_list") } - # Локаторы для текстовых полей (из RackLocators) + # Маппинг checkbox полей + CHECKBOX_FIELDS_MAPPING = { + "Вентиляционная панель": ("ventilation_panel", "ventilation_checkbox"), + } + + # Локаторы для текстовых полей TEXT_FIELDS_LOCATORS = { "Имя": RackLocators.EDIT_RACK_FORM_FIELD_NAME, "Комментарий": RackLocators.EDIT_RACK_FORM_FIELD_COMMENT, @@ -73,7 +58,7 @@ class EditRackForm(BaseComponent): "Выделенная мощность (Вт/ВА)": RackLocators.EDIT_RACK_FORM_FIELD_POWER, } - # Локаторы для combobox полей (из RackLocators) + # Локаторы для combobox полей COMBOBOX_FIELDS_LOCATORS = { "Ввод кабеля": RackLocators.EDIT_RACK_FORM_SELECT_CABLE_INPUT, "Состояние": RackLocators.EDIT_RACK_FORM_SELECT_CONDITION_TYPE, @@ -84,262 +69,17 @@ class EditRackForm(BaseComponent): "Проект/Титул": RackLocators.EDIT_RACK_FORM_SELECT_PROJECT, } - # Локатор для чекбокса вентиляционной панели - CHECKBOX_VENTILATION = RackLocators.EDIT_RACK_FORM_CHECKBOX_VENTILATION + # Локаторы для checkbox полей + CHECKBOX_FIELDS_LOCATORS = { + "Вентиляционная панель": RackLocators.EDIT_RACK_FORM_CHECKBOX_VENTILATION, + } def __init__(self, page: Page) -> None: - """Инициализирует компонент формы редактирования стойки. + """Инициализирует компонент формы редактирования стойки.""" + super().__init__(page, RackLocators.EDIT_RACK_FORM) - Args: - page: Экземпляр страницы Playwright - """ - - super().__init__(page) - self.page = page - self.content_items = {} - self.available_fields = None - - # Инициализация полей формы - self._init_form_fields() - - def _init_form_fields(self) -> None: - """Инициализирует все поля формы редактирования.""" - - # Получаем доступные поля формы - container_locator = self.page.locator(RackLocators.EDIT_RACK_FORM) - self.available_fields = self.get_input_fields_locators(container_locator) - - self._init_text_fields() - self._init_combobox_fields() - self._init_checkbox_fields() - - def _init_text_fields(self) -> None: - """Инициализирует текстовые поля формы.""" - - for field_label, (attr_name, widget_name) in self.TEXT_FIELDS_MAPPING.items(): - locator = self.TEXT_FIELDS_LOCATORS.get(field_label) - if not locator: - continue - - self._init_single_text_field(field_label, locator, widget_name) - - def _init_single_text_field(self, field_label: str, locator: str, widget_name: str) -> None: - """Инициализирует одно текстовое поле. - - Args: - field_label: Метка поля - locator: Локатор поля - widget_name: Имя виджета - """ - - try: - element = self.page.locator(locator).first - if element.count() > 0 and element.is_visible(): - # Создаем TextInput для поля - field_input = TextInput(self.page, element, widget_name) - self.content_items[widget_name] = field_input - logger.debug(f"Initialized text field: '{field_label}'") - except Exception as e: - logger.error(f"Error initializing text field '{field_label}': {e}") - - def _init_combobox_fields(self) -> None: - """Инициализирует combobox поля формы.""" - - for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items(): - locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_label) - if not locator: - continue - - self._init_single_combobox_field(field_label, locator, input_name, list_name) - - def _init_single_combobox_field( - self, field_label: str, locator: str, input_name: str, list_name: str - ) -> None: - """Инициализирует одно combobox поле. - - Args: - field_label: Метка поля - locator: Локатор поля - input_name: Имя поля ввода - list_name: Имя списка - """ - - try: - element = self.page.locator(locator).first - if element.count() > 0 and element.is_visible(): - # Для combobox создаем TextInput для клика - field_input = TextInput(self.page, element, input_name) - self.content_items[input_name] = field_input - # Добавляем DropdownList для выбора значений - self.content_items[list_name] = DropdownList(self.page) - logger.debug(f"Initialized combobox field: '{field_label}'") - except Exception as e: - logger.error(f"Error initializing combobox field '{field_label}': {e}") - - def _init_checkbox_fields(self) -> None: - """Инициализирует checkbox поля формы.""" - - try: - self._init_ventilation_checkbox() - except Exception as e: - logger.error(f"Error initializing checkbox: {e}") - - def _init_ventilation_checkbox(self) -> None: - """Инициализирует чекбокс вентиляционной панели.""" - - checkbox_input = self.page.locator(self.CHECKBOX_VENTILATION).first - - if checkbox_input.count() == 0: - logger.debug("Ventilation panel checkbox not found") - return - - # Импортируем Checkbox только здесь чтобы избежать циклических импортов - from elements.checkbox_element import Checkbox - - checkbox = Checkbox(self.page, checkbox_input, "ventilation_panel") - self.content_items["ventilation_checkbox"] = checkbox - - logger.debug("Initialized ventilation panel checkbox") - - def clear_field(self, field_name: str) -> None: - """Очищает указанное поле. - - Args: - field_name: Название поля для очистки - """ - - logger.debug(f"Clearing field: '{field_name}'") - - # Проверяем, не является ли поле чекбоксом - if field_name == "Вентиляционная панель": - logger.debug(f"Field '{field_name}' is a checkbox, skipping clear operation") - return - - # Получаем локатор поля - locator = None - if field_name in self.COMBOBOX_FIELDS_LOCATORS: - locator = self.COMBOBOX_FIELDS_LOCATORS[field_name] - elif field_name in self.TEXT_FIELDS_LOCATORS: - locator = self.TEXT_FIELDS_LOCATORS[field_name] - else: - logger.warning(f"Unknown field: {field_name}") - return - - field_element = self.page.locator(locator).first - - if field_element.count() == 0: - logger.debug(f"Field '{field_name}' not found") - return - - # Для текстовых полей - if field_name in self.TEXT_FIELDS_LOCATORS: - try: - field_element.click() - field_element.page.keyboard.press("Control+A") - field_element.page.keyboard.press("Backspace") - self.wait_for_timeout(200) - logger.debug(f"Text field '{field_name}' cleared") - except Exception as e: - logger.debug(f"Could not clear text field '{field_name}': {e}") - return - - # Для combobox полей - if field_name in self.COMBOBOX_FIELDS_LOCATORS: - # Поднимаемся до родительского контейнера - parent_container = field_element.locator( - "xpath=ancestor::div[contains(@class, 'v-input')]" - ).first - - if parent_container.count() == 0: - logger.debug(f"Parent container not found for field '{field_name}'") - return - - # Ищем кнопку очистки (крестик) - clear_button = parent_container.locator( - ".v-input__icon--clear button, .v-input__icon--append button, i.mdi-close-circle, i.mdi-close" - ).first - - if clear_button.count() > 0 and clear_button.is_visible(): - clear_button.click() - self.wait_for_timeout(300) - logger.debug(f"Combobox field '{field_name}' cleared") - else: - logger.debug(f"Clear button not found for field '{field_name}'") - - def get_content_item(self, item_name: str) -> Any: - """Возвращает элемент контента по имени. - - Args: - item_name: Имя элемента - - Returns: - Элемент или None если не найден - """ - return self.content_items.get(item_name) - - def _scroll_to_element_in_dropdown(self, value: str) -> bool: - """Скроллит выпадающий список до элемента с нужным текстом используя playwright. - - Args: - value: Текст для поиска - - Returns: - bool: True если элемент найден, False в противном случае - """ - - logger.debug(f"Scrolling to find element with text: '{value}'") - - # Получаем активное выпадающее меню - dropdown_menu = self.page.locator("div.menuable__content__active").first - - if dropdown_menu.count() == 0: - logger.error("Active dropdown menu not found") - return False - - max_attempts = 10 - attempts = 0 - - while attempts < max_attempts: - # Получаем все видимые элементы списка - visible_items = dropdown_menu.locator("a.v-list__tile, div[role='listitem']").all() - - if visible_items: - # Проверяем каждый видимый элемент - for item in visible_items: - item_text = item.text_content() or "" - if value in item_text: - logger.debug(f"Found element with text '{value}'") - # Скроллим до элемента - item.scroll_into_view_if_needed() - self.wait_for_timeout(300) - return True - - # Если элемент не найден, скроллим до последнего видимого элемента - last_item = visible_items[-1] - last_item_text = last_item.text_content() or "" - logger.debug(f"Scrolling to last visible item: '{last_item_text}'") - last_item.scroll_into_view_if_needed() - self.wait_for_timeout(500) - else: - # Если нет видимых элементов, скроллим вниз - dropdown_menu.evaluate("(el) => el.scrollTop += 200") - self.wait_for_timeout(300) - - attempts += 1 - logger.debug(f"Scroll attempt {attempts}/{max_attempts}") - - logger.warning(f"Element with text '{value}' not found after {max_attempts} scroll attempts") - return False - - def fill_rack_data(self, rack_data: EditRackFormData) -> Dict[str, int]: - """Заполняет поля формы редактирования стойки. - - Args: - rack_data: Данные для заполнения - - Returns: - Словарь с результатами заполнения - """ + def fill_rack_data(self, rack_data: EditRackData) -> Dict[str, int]: + """Заполняет поля формы редактирования стойки.""" results = { "text_fields_filled": 0, "combobox_fields_filled": 0, @@ -348,302 +88,9 @@ class EditRackForm(BaseComponent): self._fill_text_fields(rack_data, results) self._fill_combobox_fields(rack_data, results) - self._set_checkbox(rack_data, results) + self._fill_checkbox_fields(rack_data, results) logger.info(f"Filled {results['text_fields_filled']} text fields, " f"{results['combobox_fields_filled']} combobox fields, " f"{results['checkboxes_set']} checkboxes") - return results - - def _fill_text_fields(self, rack_data: EditRackFormData, results: Dict[str, int]) -> None: - """Заполняет текстовые поля. - - Args: - rack_data: Данные для заполнения - results: Словарь с результатами - """ - - for field_label, (attr_name, field_name) in self.TEXT_FIELDS_MAPPING.items(): - value = getattr(rack_data, attr_name, "") - if not value or not str(value).strip(): - continue - - self._fill_single_text_field(field_label, field_name, value, results) - - def _fill_single_text_field( - self, field_label: str, field_name: str, value: str, results: Dict[str, int] - ) -> None: - """Заполняет одно текстовое поле. - - Args: - field_label: Метка поля - field_name: Имя поля - value: Значение для заполнения - results: Словарь с результатами - """ - - try: - input_field = self.get_content_item(field_name) - if input_field: - input_field.input_value(value) - results["text_fields_filled"] += 1 - logger.debug(f"Field '{field_label}' filled: '{value}'") - except Exception as e: - logger.error(f"Error filling field '{field_label}': {e}") - - def _fill_combobox_fields(self, rack_data: EditRackFormData, results: Dict[str, int]) -> None: - """Заполняет combobox поля. - - Args: - rack_data: Данные для заполнения - results: Словарь с результатами - """ - - for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items(): - value = getattr(rack_data, attr_name, "") - if not value or not str(value).strip(): - continue - - self._fill_single_combobox_field( - field_label, input_name, list_name, value, results - ) - - def _fill_single_combobox_field( - self, field_label: str, input_name: str, list_name: str, value: str, results: Dict[str, int] - ) -> None: - """Заполняет одно combobox поле. - - Args: - field_label: Метка поля - input_name: Имя поля ввода - list_name: Имя списка - value: Значение для выбора - results: Словарь с результатами - """ - - try: - combobox_field = self.get_content_item(input_name) - if not combobox_field: - logger.warning(f"Field '{field_label}' input not found") - return - - # Кликаем для открытия выпадающего списка - combobox_field.click(force=True) - self.wait_for_timeout(1000) - - # Скроллим до нужного элемента - if not self._scroll_to_element_in_dropdown(value): - logger.error(f"Could not find element with text '{value}' after scrolling") - # Закрываем выпадающий список кликом вне - self.page.mouse.click(10, 10) - self.wait_for_timeout(300) - return - - # Получаем активное выпадающее меню - dropdown_menu = self.page.locator("div.menuable__content__active").first - - # Ищем элемент с нужным текстом - item_locator = dropdown_menu.locator(f"a.v-list__tile:has-text('{value}')").first - - if item_locator.count() == 0: - item_locator = dropdown_menu.locator(f"span:has-text('{value}')").first - - if item_locator.count() == 0: - item_locator = dropdown_menu.locator(f"div[role='listitem']:has-text('{value}')").first - - if item_locator.count() > 0: - # Убеждаемся что элемент видим и кликаем - item_locator.scroll_into_view_if_needed() - self.wait_for_timeout(300) - item_locator.click() - results["combobox_fields_filled"] += 1 - logger.debug(f"Field '{field_label}' set: '{value}'") - - # Небольшая пауза после выбора - self.wait_for_timeout(500) - else: - logger.error(f"Item with text '{value}' not found in dropdown for field '{field_label}'") - # Закрываем выпадающий список кликом вне - self.page.mouse.click(10, 10) - self.wait_for_timeout(300) - - except Exception as e: - logger.error(f"Error filling combobox '{field_label}': {e}") - self.page.mouse.click(10, 10) - - def _set_checkbox(self, rack_data: EditRackFormData, results: Dict[str, int]) -> None: - """Устанавливает чекбокс. - - Args: - rack_data: Данные для заполнения - results: Словарь с результатами - """ - - if rack_data.ventilation_panel is None: - return - - try: - checkbox = self.get_content_item("ventilation_checkbox") - if not checkbox: - logger.warning("Ventilation panel checkbox not found") - return - - if rack_data.ventilation_panel: - checkbox.check(force=True) - logger.debug("Ventilation panel checkbox checked") - else: - checkbox.uncheck(force=True) - logger.debug("Ventilation panel checkbox unchecked") - - results["checkboxes_set"] += 1 - - except Exception as e: - logger.error(f"Error setting checkbox: {e}") - - def is_field_highlighted_as_error(self, field_name: str) -> bool: - """Проверяет, подсвечено ли поле как ошибочное. - - Args: - field_name: Название поля для проверки - - Returns: - bool: True если поле подсвечено ошибкой, False в противном случае - """ - - # Для чекбокса проверка ошибок не применяется - if field_name == "Вентиляционная панель": - return False - - # Проверяем в текстовых полях - if field_name in self.TEXT_FIELDS_LOCATORS: - locator = self.TEXT_FIELDS_LOCATORS[field_name] - field_element = self.page.locator(locator).first - - if field_element.count() == 0: - logger.debug(f"Field '{field_name}' not found") - return False - - # Поднимаемся до родительского контейнера с классом v-input - parent_input = field_element.locator("xpath=ancestor::div[contains(@class, 'v-input')]").first - - if parent_input.count() > 0: - # Проверяем наличие класса ошибки - class_attr = parent_input.get_attribute("class") or "" - is_error = "v-input--error" in class_attr or "error--text" in class_attr - logger.debug(f"Field '{field_name}' error state: {is_error}, classes: {class_attr}") - return is_error - - # Проверяем в combobox полях - elif field_name in self.COMBOBOX_FIELDS_LOCATORS: - locator = self.COMBOBOX_FIELDS_LOCATORS[field_name] - field_element = self.page.locator(locator).first - - if field_element.count() == 0: - logger.debug(f"Field '{field_name}' not found") - return False - - # Поднимаемся до родительского контейнера с классом v-input - parent_input = field_element.locator("xpath=ancestor::div[contains(@class, 'v-input')]").first - - if parent_input.count() > 0: - # Проверяем наличие класса ошибки - class_attr = parent_input.get_attribute("class") or "" - is_error = "v-input--error" in class_attr or "error--text" in class_attr - logger.debug(f"Field '{field_name}' error state: {is_error}, classes: {class_attr}") - return is_error - - return False - - def verify_required_fields_highlighted(self, field_names: List[str]) -> Dict[str, bool]: - """Проверяет, что указанные поля подсвечены как обязательные (с ошибкой). - - Args: - field_names: Список названий полей для проверки - - Returns: - Словарь с результатами проверки {field_name: is_highlighted} - """ - - results = {} - - for field_name in field_names: - results[field_name] = self.is_field_highlighted_as_error(field_name) - logger.debug(f"Field '{field_name}' highlighted: {results[field_name]}") - - return results - - def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool: - """Ожидает появления подсветки ошибки на поле. - - Args: - field_name: Название поля - timeout: Таймаут в миллисекундах - - Returns: - bool: True если ошибка появилась, False в противном случае - """ - - # Для чекбокса не ждем ошибок - if field_name == "Вентиляционная панель": - return False - - start_time = time.time() - - while (time.time() - start_time) * 1000 < timeout: - if self.is_field_highlighted_as_error(field_name): - return True - self.wait_for_timeout(200) - - return False - - def get_field_value(self, field_name: str) -> Optional[str]: - """Получает значение поля. - - Args: - field_name: Название поля - - Returns: - Значение поля или None если поле не найдено - """ - - # Для чекбокса - if field_name == "Вентиляционная панель": - checkbox = self.get_content_item("ventilation_checkbox") - if checkbox: - return str(checkbox.is_checked()) - return None - - # Для текстовых полей - if field_name in self.TEXT_FIELDS_LOCATORS: - for field_label, (attr_name, widget_name) in self.TEXT_FIELDS_MAPPING.items(): - if attr_name == field_name or field_label == field_name: - input_field = self.get_content_item(widget_name) - if input_field: - return input_field.get_input_value() - return None - - # Для combobox полей - if field_name in self.COMBOBOX_FIELDS_LOCATORS: - locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_name) - if not locator: - # Пробуем найти по атрибуту - for field_label, (attr_name, input_name, _) in self.COMBOBOX_FIELDS_MAPPING.items(): - if attr_name == field_name or field_label == field_name: - input_field = self.get_content_item(input_name) - if input_field: - # Получаем текст из поля - element = input_field.element - selections = element.locator("xpath=ancestor::div[contains(@class, 'v-select__selections')]").first - if selections.count() > 0: - value_span = selections.locator("span").first - return value_span.text_content() or "" - return None - - element = self.page.locator(locator).first - if element.count() > 0: - selections = element.locator("xpath=ancestor::div[contains(@class, 'v-select__selections')]").first - if selections.count() > 0: - value_span = selections.locator("span").first - return value_span.text_content() or "" - - return None + return results \ No newline at end of file diff --git a/frames/create_element_frame.py b/frames/create_element_frame.py index 733bbb0..ff5861e 100644 --- a/frames/create_element_frame.py +++ b/frames/create_element_frame.py @@ -1,6 +1,7 @@ """Модуль фрейма создания дочернего элемента.""" import re +from typing import Dict, Any, Optional from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.rack_locators import RackLocators @@ -9,6 +10,7 @@ from components.alert_component import AlertComponent from components.base_component import BaseComponent from components.toolbar_component import ToolbarComponent from components_derived.selection_bar_component import SelectionBarComponent +from forms.create_rack_form import CreateRackForm, CreateRackData logger = get_logger("CREATE_ELEMENT_FRAME") @@ -27,6 +29,9 @@ class CreateElementFrame(BaseComponent): """ super().__init__(page) + # Инициализация формы создания стойки + self.rack_form = CreateRackForm(page) + # Инициализация компонентов self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в") self.selection_bar = SelectionBarComponent(page, "Класс объекта учета") @@ -46,7 +51,67 @@ class CreateElementFrame(BaseComponent): self.toolbar.add_tooltip_button(add_button_locator, "add") self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") - # Действия: + # Делегирование методов форме создания стойки + + def fill_rack_data(self, rack_data: CreateRackData) -> Dict[str, int]: + """ + Заполняет поля формы создания стойки. + + Args: + rack_data: Данные для заполнения + + Returns: + Словарь с результатами заполнения + """ + return self.rack_form.fill_rack_data(rack_data) + + def clear_field(self, field_name: str) -> None: + """ + Очищает указанное поле формы. + + Args: + field_name: Название поля для очистки + """ + self.rack_form.clear_field(field_name) + + def get_field_value(self, field_name: str) -> Optional[str]: + """ + Получает значение поля формы. + + Args: + field_name: Название поля + + Returns: + Значение поля или None если поле не найдено + """ + return self.rack_form.get_field_value(field_name) + + def is_field_highlighted_as_error(self, field_name: str) -> bool: + """ + Проверяет, подсвечено ли поле как ошибочное. + + Args: + field_name: Название поля для проверки + + Returns: + bool: True если поле подсвечено ошибкой + """ + return self.rack_form.is_field_highlighted_as_error(field_name) + + def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool: + """ + Ожидает появления подсветки ошибки на поле. + + Args: + field_name: Название поля + timeout: Таймаут в миллисекундах + + Returns: + bool: True если ошибка появилась + """ + return self.rack_form.wait_for_field_error(field_name, timeout) + + # Оригинальные методы фрейма def clear_combobox_field(self, field_name: str) -> None: """ @@ -192,22 +257,9 @@ class CreateElementFrame(BaseComponent): AssertionError: Если поле не подсвечено ошибкой """ logger.debug(f"Checking field '{field_name}' for error highlighting...") - - container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER) - fields_locators = self.get_input_fields_locators(container_locator) - field_container = fields_locators.get(field_name) - - if not field_container: - raise ValueError(f"Field '{field_name}' not found in form") - - error_elements = field_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS) - has_error = error_elements.count() > 0 - - assert has_error, ( - f"Field '{field_name}' has no elements with error classes. " - f"Expected to find elements matching: {SelectionBarLocators.ERROR_CSS_SELECTORS}" + assert self.is_field_highlighted_as_error(field_name), ( + f"Field '{field_name}' is not highlighted as error" ) - logger.debug(f"Field '{field_name}' is correctly highlighted with error color") def check_field_error_not_highlighted(self, field_name: str) -> None: @@ -222,22 +274,9 @@ class CreateElementFrame(BaseComponent): AssertionError: Если поле подсвечено ошибкой """ logger.debug(f"Checking field '{field_name}' for absence of error highlighting...") - - container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER) - fields_locators = self.get_input_fields_locators(container_locator) - field_container = fields_locators.get(field_name) - - if not field_container: - raise ValueError(f"Field '{field_name}' not found in form") - - error_elements = field_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS) - has_error = error_elements.count() > 0 - - assert not has_error, ( - f"Field '{field_name}' has {error_elements.count()} elements with error classes. " - f"Expected no elements matching: {SelectionBarLocators.ERROR_CSS_SELECTORS}" + assert not self.is_field_highlighted_as_error(field_name), ( + f"Field '{field_name}' is incorrectly highlighted as error" ) - logger.debug(f"Field '{field_name}' correctly has no error highlighting") def check_object_class_selected(self, expected_class: str) -> None: @@ -296,4 +335,4 @@ class CreateElementFrame(BaseComponent): self.toolbar.check_button_visibility("cancel") self.toolbar.check_button_tooltip("cancel", "Отменить") self.toolbar.click_button("cancel") - self.wait_for_timeout(500) \ No newline at end of file + self.wait_for_timeout(500) diff --git a/makers/edit_rack_maker.py b/makers/edit_rack_maker.py index 2285057..6cc28f6 100644 --- a/makers/edit_rack_maker.py +++ b/makers/edit_rack_maker.py @@ -1,3 +1,4 @@ +# makers/edit_rack_maker.py """Модуль для работы с модальным окном редактирования стойки.""" import re @@ -9,17 +10,16 @@ from components.modal_window_component import ModalWindowComponent from components.dropdown_list_component import DropdownList from components.confirm_component import ConfirmComponent from elements.text_input_element import TextInput -from elements.text_element import Text -from forms.edit_rack_form import EditRackForm, EditRackFormData +from forms.edit_rack_form import EditRackForm, EditRackData logger = get_logger("EDIT_RACK_MAKER") logger.setLevel("INFO") -# Используем EditRackFormData -EditRackData = EditRackFormData - +# Re-export EditRackData for backward compatibility +EditRackData = EditRackData +__all__ = ['EditRackMaker', 'EditRackData'] class EditRackMaker(ModalWindowComponent): """Компонент для работы с модальным окном редактирования стойки. @@ -38,7 +38,7 @@ class EditRackMaker(ModalWindowComponent): TAB_IMAGE = "Изображение" TAB_SETTINGS = "Настройки" - # Маппинг полей для вкладки "Настройки" - оставляем только то, что специфично для модального окна + # Маппинг полей для вкладки "Настройки" ACCESS_RULES_MAPPING = { "Правила доступа для чтения": ( "read_access_rules", "rules_read_input", "rules_read_list" @@ -57,7 +57,7 @@ class EditRackMaker(ModalWindowComponent): ), } - # Локаторы для полей правил доступа (из RackLocators) + # Локаторы для полей правил доступа ACCESS_RULES_LOCATORS = { "Правила доступа для чтения": RackLocators.SETTINGS_READ_RULES, "Правила доступа для записи": RackLocators.SETTINGS_WRITE_RULES, @@ -210,6 +210,82 @@ class EditRackMaker(ModalWindowComponent): self.add_button(self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON), "cancel") self.add_button(self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON), "delete") + # Делегирование методов форме редактирования + + def fill_rack_data(self, rack_data: EditRackData) -> dict: + """Заполняет поля формы редактирования стойки. + + Args: + rack_data: Данные для заполнения. + + Returns: + Словарь с результатами заполнения. + """ + + if self.active_tab != self.TAB_GENERAL: + self.switch_to_tab(self.TAB_GENERAL) + + if not self.edit_form: + logger.error("Edit form not initialized") + return { + "text_fields_filled": 0, + "combobox_fields_filled": 0, + "checkboxes_set": 0 + } + + results = self.edit_form.fill_rack_data(rack_data) + logger.info(f"Filled rack data via EditRackForm: {results}") + return results + + def clear_field(self, field_name: str) -> None: + """Очищает указанное поле формы. + + Args: + field_name: Название поля для очистки. + """ + if self.edit_form: + self.edit_form.clear_field(field_name) + + def get_field_value(self, field_name: str) -> Optional[str]: + """Получает значение поля формы. + + Args: + field_name: Название поля. + + Returns: + Значение поля или None если поле не найдено. + """ + if self.edit_form: + return self.edit_form.get_field_value(field_name) + return None + + def is_field_highlighted_as_error(self, field_name: str) -> bool: + """Проверяет, подсвечено ли поле как ошибочное. + + Args: + field_name: Название поля для проверки. + + Returns: + bool: True если поле подсвечено ошибкой. + """ + if self.edit_form: + return self.edit_form.is_field_highlighted_as_error(field_name) + return False + + def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool: + """Ожидает появления подсветки ошибки на поле. + + Args: + field_name: Название поля. + timeout: Таймаут в миллисекундах. + + Returns: + bool: True если ошибка появилась. + """ + if self.edit_form: + return self.edit_form.wait_for_field_error(field_name, timeout) + return False + # Действия с вкладками def switch_to_tab(self, tab_name: str) -> None: """Переключается на указанную вкладку. @@ -348,11 +424,7 @@ class EditRackMaker(ModalWindowComponent): target_fields: Список целевых полей для заполнения. Returns: - Словарь с результатами заполнения: - - access_rules_filled: количество добавленных пользователей - - errors: список ошибок - - fields_processed: обработанные поля - - field_stats: статистика по каждому полю + Словарь с результатами заполнения. """ if self.active_tab != self.TAB_SETTINGS: @@ -632,13 +704,7 @@ class EditRackMaker(ModalWindowComponent): target_fields: Список целевых полей для проверки. Returns: - Словарь с результатами проверки: - - total_expected_fields: общее количество ожидаемых значений - - correctly_filled: количество корректно заполненных - - incorrectly_filled: количество некорректно заполненных - - field_errors: список ошибок по полям - - fields_verified: список проверенных полей - - expected_users: список ожидаемых пользователей + Словарь с результатами проверки. """ if self.active_tab != self.TAB_SETTINGS: @@ -859,32 +925,6 @@ class EditRackMaker(ModalWindowComponent): save_button.click() logger.debug("Clicked done button") - def fill_rack_data(self, rack_data: EditRackData) -> dict: - """Заполняет поля формы редактирования стойки. - - Args: - rack_data: Данные для заполнения. - - Returns: - Словарь с результатами заполнения. - """ - - if self.active_tab != self.TAB_GENERAL: - self.switch_to_tab(self.TAB_GENERAL) - - # Используем форму для заполнения данных - if self.edit_form: - results = self.edit_form.fill_rack_data(rack_data) - logger.info(f"Filled rack data via EditRackForm: {results}") - return results - else: - logger.error("Edit form not initialized") - return { - "text_fields_filled": 0, - "combobox_fields_filled": 0, - "checkboxes_set": 0 - } - # Проверки def verify_all_filled_fields( self, @@ -916,6 +956,11 @@ class EditRackMaker(ModalWindowComponent): if skip_fields is None: skip_fields = [] + if not self.edit_form: + logger.error("Edit form not initialized") + results["field_errors"].append("Edit form not initialized") + return results + # Проверяем текстовые поля self._verify_text_fields(rack_data, skip_fields, results) @@ -948,10 +993,6 @@ class EditRackMaker(ModalWindowComponent): results: Словарь с результатами для обновления. """ - if not self.edit_form: - logger.error("Edit form not initialized") - return - for field_label, (attr_name, field_name) in self.edit_form.TEXT_FIELDS_MAPPING.items(): expected_value = getattr(rack_data, attr_name, "") if not expected_value or not str(expected_value).strip(): @@ -1014,10 +1055,6 @@ class EditRackMaker(ModalWindowComponent): results: Словарь с результатами для обновления. """ - if not self.edit_form: - logger.error("Edit form not initialized") - return - for field_label, (attr_name, _, _) in self.edit_form.COMBOBOX_FIELDS_MAPPING.items(): expected_value = getattr(rack_data, attr_name, "") if not expected_value or not str(expected_value).strip(): @@ -1046,9 +1083,9 @@ class EditRackMaker(ModalWindowComponent): """ try: - actual_value = self._get_combobox_value(field_label) - actual_clean = actual_value.strip() if actual_value else "" - expected_clean = expected_value.strip() if expected_value else "" + actual_value = self.edit_form.get_field_value(field_label) or "" + actual_clean = actual_value.strip() + expected_clean = expected_value.strip() if actual_clean == expected_clean: results["correctly_filled"] += 1 @@ -1061,40 +1098,6 @@ class EditRackMaker(ModalWindowComponent): results["not_filled"] += 1 results["field_errors"].append(f"Error checking combobox '{field_label}': {e}") - def _get_combobox_value(self, field_label: str) -> str: - """Получает значение из combobox поля. - - Args: - field_label: Название поля. - - Returns: - Значение поля или пустая строка. - """ - - if not self.edit_form: - return "" - - actual_value = "" - # Используем локаторы из edit_form - locator = self.edit_form.COMBOBOX_FIELDS_LOCATORS.get(field_label) - - if not locator: - return actual_value - - element = self.page.locator(locator).first - if element.count() == 0: - return actual_value - - selections_container = element.locator( - "xpath=ancestor::div[contains(@class, 'v-select__selections')]" - ).first - - if selections_container.count() > 0: - value_span = selections_container.locator("span").first - actual_value = value_span.text_content() or "" - - return actual_value - def _verify_checkbox( self, rack_data: EditRackData, @@ -1138,4 +1141,4 @@ class EditRackMaker(ModalWindowComponent): ) except Exception as e: results["not_filled"] += 1 - results["field_errors"].append(f"Error checking checkbox: {str(e)}") + results["field_errors"].append(f"Error checking checkbox: {str(e)}") \ No newline at end of file diff --git a/tests/e2e/elements/test_create_rack.py b/tests/e2e/elements/test_create_rack.py index 31f742b..58d9b84 100644 --- a/tests/e2e/elements/test_create_rack.py +++ b/tests/e2e/elements/test_create_rack.py @@ -52,7 +52,7 @@ class TestCreateRack: # Переходим к Объектам self.main_page.click_main_navigation_panel_item("Объекты") - self.main_page.wait_for_timeout(1000) + self.main_page.wait_for_timeout(2000) self.main_page.click_main_navigation_panel_item("test-zone") # Создаем экземпляр страницы локации @@ -122,7 +122,7 @@ class TestCreateRack: # Ждем появления alert с текстом expected_alert_text = f"Элемент {rack_data.name} создан" - self.alert.check_alert_presence(expected_alert_text, timeout=5000) + self.alert.check_alert_presence(expected_alert_text, timeout=7000) self.alert.check_alert_absence(expected_alert_text, timeout=7000) @@ -171,7 +171,7 @@ class TestCreateRack: # Проверяем уведомление об успешном удалении expected_alert_text = "Успешно удалено" - self.alert.check_alert_presence(expected_alert_text, timeout=5000) + self.alert.check_alert_presence(expected_alert_text, timeout=7000) self.alert.check_alert_absence(expected_alert_text, timeout=7000) @@ -331,7 +331,7 @@ class TestCreateRack: self.create_child_frame.click_add_button() expected_alert_text = f"Имя {rack_name} уже используется" - self.alert.check_alert_presence(expected_alert_text, timeout=5000) + self.alert.check_alert_presence(expected_alert_text, timeout=7000) self.alert.check_alert_absence(expected_alert_text, timeout=7000) @@ -384,8 +384,8 @@ class TestCreateRack: self.create_child_frame.wait_for_timeout(500) # Проверяем alert для высоты, глубины - self.alert.check_alert_presence(expected_alert_text_height, timeout=5000) - self.alert.check_alert_presence(expected_alert_text_depth, timeout=5000) + self.alert.check_alert_presence(expected_alert_text_height, timeout=7000) + self.alert.check_alert_presence(expected_alert_text_depth, timeout=7000) # Проверяем, закрылся ли автоматически alert для высоты, глубины self.alert.check_alert_absence(expected_alert_text_height, timeout=7000) @@ -413,7 +413,7 @@ class TestCreateRack: self.create_child_frame.click_add_button() # Проверяем alert для глубины - self.alert.check_alert_presence(expected_alert_text_depth, timeout=5000) + self.alert.check_alert_presence(expected_alert_text_depth, timeout=7000) # Проверяем, закрылся ли автоматически alert для глубины self.alert.check_alert_absence(expected_alert_text_depth, timeout=7000) @@ -439,7 +439,7 @@ class TestCreateRack: self.create_child_frame.click_add_button() # Проверяем alert для высоты - self.alert.check_alert_presence(expected_alert_text_height, timeout=5000) + self.alert.check_alert_presence(expected_alert_text_height, timeout=7000) # Проверяем, закрылся ли автоматически alert для высоты self.alert.check_alert_absence(expected_alert_text_height, timeout=7000) @@ -466,7 +466,7 @@ class TestCreateRack: self.create_child_frame.wait_for_timeout(500) # Проверяем alert для имени - self.alert.check_alert_presence(expected_alert_text_name, timeout=5000) + self.alert.check_alert_presence(expected_alert_text_name, timeout=7000) # Проверяем, закрылся ли автоматически alert для высоты self.alert.check_alert_absence(expected_alert_text_name, timeout=7000)