diff --git a/components_derived/accounting_objects/__pycache__/rack_maker.cpython-313.pyc b/components_derived/accounting_objects/__pycache__/rack_maker.cpython-313.pyc new file mode 100644 index 0000000..a8c3763 Binary files /dev/null and b/components_derived/accounting_objects/__pycache__/rack_maker.cpython-313.pyc differ diff --git a/components_derived/frames/__pycache__/create_child_element_frame.cpython-313.pyc b/components_derived/frames/__pycache__/create_child_element_frame.cpython-313.pyc new file mode 100644 index 0000000..5e901e7 Binary files /dev/null and b/components_derived/frames/__pycache__/create_child_element_frame.cpython-313.pyc differ diff --git a/components_derived/frames/create_child_element_frame.py b/components_derived/frames/create_child_element_frame.py index 7a5fff3..9dca697 100644 --- a/components_derived/frames/create_child_element_frame.py +++ b/components_derived/frames/create_child_element_frame.py @@ -38,7 +38,7 @@ class CreateChildElementFrame(BaseComponent): has_text="Создать дочерний элемент в" ).get_by_role("button").nth(0) - # Кнопка "Отменить" - используем рабочий локатор из старой версии + # Кнопка "Отменить" - cancel_button_locator = self.page.get_by_role("navigation").filter( has_text=re.compile('Создать дочерний элемент в') ).get_by_role("button").nth(1) diff --git a/components_derived/modal_rack_edit.py b/components_derived/modal_rack_edit.py new file mode 100644 index 0000000..f39e68c --- /dev/null +++ b/components_derived/modal_rack_edit.py @@ -0,0 +1,465 @@ +"""Модуль для работы с модальным окном редактирования стойки.""" + +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_RACK_EDIT") +#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 ModalRackEdit(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 + + def should_be_toolbar_buttons(self) -> None: + """ + Проверяет наличие и функциональность кнопок тулбара. + + Raises: + AssertionError: Если кнопки недоступны или подсказки неверны + """ + + logger.debug("Checking toolbar buttons...") + + # Проверяем новые кнопки тулбара + self.toolbar.check_button_visibility("replace") + self.toolbar.check_button_tooltip("replace", "Переместить") + + self.toolbar.check_button_visibility("done") + self.toolbar.check_button_tooltip("done", "Сохранить") + + self.toolbar.check_button_visibility("close") + self.toolbar.check_button_tooltip("close", "Отменить") + + self.toolbar.check_button_visibility("remove") + self.toolbar.check_button_tooltip("remove", "Удалить") \ No newline at end of file diff --git a/pages/create_elements_tab/create_child_element_tab.py b/pages/create_elements_tab/create_child_element_tab.py deleted file mode 100644 index 178a9bc..0000000 --- a/pages/create_elements_tab/create_child_element_tab.py +++ /dev/null @@ -1,355 +0,0 @@ -"""Модуль страницы создания дочернего элемента. - -Содержит класс для работы с формой создания дочернего элемента. -""" - -from playwright.sync_api import Page, expect -from elements.tooltip_button_element import TooltipButton -from components.toolbar_component import ToolbarComponent -from components.dropdown_list_component import DropdownList -from pages.base_page import BasePage -from tools.logger import get_logger - -logger = get_logger("CREATE_CHILD_ELEMENT") - -# =============== Локаторы ================================================ -PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i" -TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']" -CREATE_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button" -PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]" - -CREATE_CHILD_TITLE = "//div[contains(@class, 'v-toolbar__title') and contains(., 'Создать дочерний элемент в')]" -OBJECT_CLASS_COMBOBOX = "//div[@role='combobox' and .//label[text()='Класс объекта учета']]" -CANCEL_BUTTON = "//div[contains(@class, 'v-toolbar__title') and contains(., 'Создать дочерний элемент в')]/..//button[contains(@class, 'v-btn--icon')]" - -# Локаторы для работы с combobox -COMBOBOX_LABEL = "label" -COMBOBOX_INPUT = "input[name='entity']" -COMBOBOX_ICON = ".v-input__icon--append" -COMBOBOX_ICON_ARROW = ".v-input__icon--append .mdi-menu-down" - -# Локаторы для выпадающего списка combobox - уточненные -LISTBOX_SELECTOR = "//div[contains(@class, 'v-menu__content')]//div[@role='list']" -OPTIONS_SELECTOR = "//div[contains(@class, 'v-menu__content')]//div[@role='listitem']//span" - -# Локаторы для получения выбранного значения -SELECTED_VALUE_SPAN = "span" -#======================================================================================================== - - -class CreateChildElementTab(BasePage): - """Класс для работы с формой создания дочернего элемента.""" - - def __init__(self, page: Page) -> None: - """ - Инициализирует объект формы создания дочернего элемента. - - Args: - page: Экземпляр страницы Playwright - """ - super().__init__(page) - - # Локаторы для кнопок - panel_header_locator = self.page.locator(PANEL_HEADER) - - # Кнопка "Создать" - первая кнопка в тулбаре - create_button_locator = panel_header_locator.locator(CREATE_BUTTON_ANCESTOR_DIV3).nth(0) - - # Кнопка "Отменить" - ищем глобально на странице - cancel_button_locator = self.page.locator(CANCEL_BUTTON) - - # Инициализация кнопок - self.create_button = TooltipButton(page, create_button_locator, "add") - self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel") - - # Инициализация тулбара с обеими кнопками - self.toolbar = ToolbarComponent(page, "") - self.toolbar.add_tooltip_button(create_button_locator, "add") - self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") - - # Инициализация компонента выпадающего списка - self.dropdown = DropdownList(page) - - def get_toolbar_title(self) -> list[str]: - """ - Получает заголовок панели инструментов. - - Returns: - list[str]: Список элементов заголовка панели инструментов - """ - toolbar_title_locator = self.page.locator(PANEL_HEADER).\ - locator(PANEL_HEADER_ANCESTOR_DIV2).get_by_role("navigation").\ - locator(TOOLBAR_CONTENT) - - return self.toolbar.get_toolbar_composite_title_text(toolbar_title_locator) - - def should_be_toolbar_buttons(self) -> None: - """ - Проверяет наличие и функциональность кнопок тулбара. - - Raises: - AssertionError: Если кнопки недоступны или подсказки неверны. - """ - - self.wait_for_timeout(2000) - - self.toolbar.check_button_visibility("cancel") - self.toolbar.check_button_tooltip("cancel", "Отменить") - self.toolbar.get_button_by_name("cancel").click() - self.wait_for_timeout(2000) - - def click_create_button(self) -> None: - """ - Кликает на кнопку 'Создать'. - """ - logger.info("Клик на кнопку 'Создать'...") - self.toolbar.get_button_by_name("add").click() - - def click_cancel_button(self) -> None: - """ - Кликает на кнопку 'Отменить'. - """ - logger.info("Клик на кнопку 'Отменить'...") - self.toolbar.get_button_by_name("cancel").click() - - def check_toolbar_title(self, expected_title: str) -> None: - """ - Проверяет заголовок тулбара. - - Args: - expected_title: Ожидаемый заголовок тулбара - - Raises: - AssertionError: Если заголовок не соответствует ожидаемому - """ - # Используем метод тулбара с нашим специфичным локатором - self.toolbar.check_toolbar_presence_by_locator(CREATE_CHILD_TITLE, - f"Заголовок тулбара '{expected_title}' не найден") - - # Получаем текст и проверяем его - actual_text = self.toolbar.get_toolbar_title_text(CREATE_CHILD_TITLE) - assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'" - - logger.info(f"Заголовок тулбара корректен: '{actual_text}'") - - def check_object_class_combobox_presence(self) -> None: - """ - Проверяет наличие combobox 'Класс объекта учета'. - - Raises: - AssertionError: Если combobox не найден - """ - logger.info("Проверка наличия combobox 'Класс объекта учета'...") - - combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) - expect(combobox_locator).to_be_visible() - - logger.info("Combobox 'Класс объекта учета' найден") - - def check_object_class_combobox_content(self) -> None: - """ - Проверяет содержимое combobox 'Класс объекта учета'. - - Raises: - AssertionError: Если содержимое не соответствует ожидаемому - """ - logger.info("Проверка содержимого combobox 'Класс объекта учета'...") - - combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) - - # Проверяем что combobox видим - expect(combobox_locator).to_be_visible() - - # Проверяем наличие label - label_locator = combobox_locator.locator(COMBOBOX_LABEL) - expect(label_locator).to_have_text("Класс объекта учета") - - # Проверяем наличие input поля - input_locator = combobox_locator.locator(COMBOBOX_INPUT) - expect(input_locator).to_be_visible() - - # Для combobox нормально иметь readonly атрибут - это стандартное поведение - # Проверяем что поле доступно для выбора (не disabled) - expect(input_locator).not_to_have_attribute("disabled", "disabled") - - # Проверяем наличие иконки стрелки - icon_locator = combobox_locator.locator(COMBOBOX_ICON_ARROW) - expect(icon_locator).to_be_visible() - - logger.info("Содержимое combobox 'Класс объекта учета' корректно") - - def open_object_class_combobox(self) -> None: - """ - Открывает выпадающий список combobox 'Класс объекта учета'. - """ - logger.info("Открытие combobox 'Класс объекта учета'...") - - combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) - listbox_locator = self.page.locator(LISTBOX_SELECTOR) - icon_locator = combobox_locator.locator(COMBOBOX_ICON) - - # Проверяем, не открыт ли уже список - listbox_already_open = False - listbox_count = listbox_locator.count() - - if listbox_count > 0: - listbox_already_open = listbox_locator.first.is_visible() - - if not listbox_already_open: - # Только если список не открыт, кликаем на иконку - icon_locator.click(timeout=10000) - logger.info("Клик на иконку combobox выполнен") - self.wait_for_timeout(1000) - - # Проверяем что список открылся - listbox_count_after = listbox_locator.count() - listbox_visible = False - - if listbox_count_after > 0: - listbox_visible = listbox_locator.first.is_visible() - - if listbox_visible: - logger.info("Выпадающий список найден и открыт") - else: - logger.warning("Не удалось открыть выпадающий список") - - def get_object_class_options(self) -> list[str]: - """ - Получает список доступных опций из combobox. - - Returns: - list[str]: Список доступных классов объектов - """ - logger.info("Получение списка опций combobox 'Класс объекта учета'...") - - # Открываем combobox (если еще не открыт) - self.open_object_class_combobox() - - # Используем метод get_item_names из DropdownList - options_list = self.dropdown.get_item_names(LISTBOX_SELECTOR) - - # Закрываем combobox (кликаем вне его) - self.page.mouse.click(10, 10) - self.wait_for_timeout(500) - - logger.info(f"Найдено опций: {len(options_list)} - {options_list}") - return options_list - - def select_object_class(self, class_name: str) -> None: - """ - Выбирает класс объекта из выпадающего списка. - - Args: - class_name: Название класса объекта для выбора - - Raises: - AssertionError: Если класс не найден в списке - """ - logger.info(f"Выбор класса объекта: '{class_name}'...") - - # Открываем combobox - self.open_object_class_combobox() - - self.dropdown.click_item_with_text(class_name) - - # Проверяем что выбор произошел - self.wait_for_timeout(1000) - selected_value = self.get_selected_object_class() - - if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower(): - # Если выбор не произошел, получаем доступные опции для отладки - available_options = self.get_object_class_options() - logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}") - raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'") - - logger.info(f"Класс объекта '{class_name}' успешно выбран") - - def get_selected_object_class(self) -> str: - """ - Получает выбранный класс объекта учета. - - Returns: - str: Выбранный класс объекта или пустая строка если ничего не выбрано - """ - combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) - - selected_value = "" - - # Ищем в span элементах - span_locator = combobox_locator.locator(SELECTED_VALUE_SPAN) - if span_locator.count() > 0: - for i in range(span_locator.count()): - span_text = span_locator.nth(i).text_content().strip() - if span_text and span_text not in ["Класс объекта учета"]: - selected_value = span_text - break - - logger.info(f"Выбранный класс объекта: '{selected_value}'") - return selected_value - - def check_object_class_selected(self, expected_class: str) -> None: - """ - Проверяет что выбран указанный класс объекта. - - Args: - expected_class: Ожидаемый выбранный класс объекта - - Raises: - AssertionError: Если выбранный класс не соответствует ожидаемому - """ - logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...") - - # Даем время на обновление значения - self.wait_for_timeout(1000) - - actual_class = self.get_selected_object_class() - - # Проверка - допускаем частичное совпадение - if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower(): - logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')") - else: - raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'") - - def check_object_class_options_content(self, expected_options: list = None) -> None: - """ - Проверяет содержимое списка опций combobox. - - Args: - expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой. - - Raises: - AssertionError: Если список опций не соответствует ожидаемому - """ - logger.info("Проверка содержимого списка опций combobox...") - - # Получаем доступные опции - available_options = self.get_object_class_options() - - if expected_options is not None: - # Проверяем соответствие ожидаемому списку - assert set(available_options) == set(expected_options), ( - f"Список опций не соответствует ожидаемому. " - f"Ожидалось: {expected_options}, Получено: {available_options}" - ) - else: - # Проверяем что список не пустой - assert len(available_options) > 0, "Список опций combobox пустой" - - logger.info(f"Содержимое списка опций корректно: {available_options}") - - def check_dropdown_item_presence(self, item_text: str) -> None: - """ - Проверяет наличие элемента в выпадающем списке. - - Args: - item_text: Текст элемента для проверки - """ - logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...") - - # Получаем все опции и проверяем наличие - available_options = self.get_object_class_options() - - if item_text not in available_options: - raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}") - - logger.info(f"Элемент '{item_text}' присутствует в списке") diff --git a/pages/create_elements_tab/create_rack_element_tab.py b/pages/create_elements_tab/create_rack_element_tab.py deleted file mode 100644 index 5965815..0000000 --- a/pages/create_elements_tab/create_rack_element_tab.py +++ /dev/null @@ -1,678 +0,0 @@ -"""Модуль страницы создания дочернего элемента. - -Содержит класс для работы с формой создания дочернего элемента. -""" -import re -from playwright.sync_api import Page, expect - -from elements.tooltip_button_element import TooltipButton -from components.toolbar_component import ToolbarComponent -from components_derived.selection_bar_component import SelectionBarComponent -from pages.main_page import MainPage -from pages.base_page import BasePage -from components.base_component import BaseComponent -from components.alert_component import AlertComponent -from components.navbar_component import NavigationPanelComponent -from locators.navigation_panel_locators import NavigationPanelLocators -from locators.combobox_locators import ComboboxLocators -from locators.rack_locators import RackLocators -from locators.alert_locators import AlertLocators -from tools.logger import get_logger - -logger = get_logger("CREATE_RACK_ELEMENT") - - -# Словарь для сопоставления названий полей с локаторами -COMBOBOX_FIELDS_MAP = { - # Обязательные поля - "Имя": RackLocators.RACK_NAME_FIELD, - "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, - "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD, - - # Дополнительные текстовые поля - "Серийный номер": RackLocators.RACK_SERIAL_FIELD, - "Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD, - "Комментарий": RackLocators.RACK_COMMENT_FIELD, - - # Combobox поля - "Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD, - "Состояние": RackLocators.RACK_STATE_FIELD, - "Владелец": RackLocators.RACK_OWNER_FIELD, - "Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD, - "Проект/Титул": RackLocators.RACK_PROJECT_FIELD -} - - -class CreateRackElementTab(BasePage): - """Класс для работы с формой создания дочернего элемента.""" - - def __init__(self, page: Page) -> None: - """ - Инициализирует объект формы создания дочернего элемента. - - Args: - page: Экземпляр страницы Playwright - """ - super().__init__(page) - - # Инициализация BaseComponent - self.base_component = BaseComponent(page) - - # Инициализация AlertComponent - self.alert = AlertComponent(page) - - # Инициализация MainPage для работы с навигацией - self.main_page = MainPage(page) - - # Инициализация NavigationPanelComponent - self.navigation_panel = NavigationPanelComponent(page) - - # Кнопка "Добавить" - первая кнопка в тулбаре - create_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(0) - - # Кнопка "Отменить" - вторая кнопка в тулбаре - cancel_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(1) - - # Инициализация кнопок - self.create_button = TooltipButton(page, create_button_locator, "add") - self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel") - - # Инициализация тулбара с обеими кнопками - self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в") - self.toolbar.add_tooltip_button(create_button_locator, "add") - self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") - - # Инициализация компонента панели выбора значения для работы с combobox - self.selection_bar = SelectionBarComponent(page, ComboboxLocators.OBJECT_CLASS_COMBOBOX) - - # =============== МЕТОДЫ ДЕЙСТВИЙ ======================== - - def click_add_button(self) -> None: - """ - Кликает на кнопку 'Добавить'. - """ - self.toolbar.click_button("add") - - def click_cancel_button(self) -> None: - """ - Кликает на кнопку 'Отменить'. - """ - self.toolbar.click_button("cancel") - - def open_object_class_combobox(self) -> None: - """ - Открывает выпадающий список combobox 'Класс объекта учета'. - """ - logger.info("Открытие combobox 'Класс объекта учета'...") - self.selection_bar.open_values_list() - - def select_object_class(self, class_name: str) -> None: - """ - Выбирает класс объекта из выпадающего списка. - - Args: - class_name: Название класса объекта для выбора - - Raises: - AssertionError: Если класс не найден в списке - """ - logger.info(f"Выбор класса объекта: '{class_name}'...") - - # Открываем combobox - self.open_object_class_combobox() - - # Выбираем значение из списка - self.selection_bar.select_value(class_name) - - # Проверяем что выбор произошел - self.wait_for_timeout(1000) - selected_value = self.get_selected_object_class() - - if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower(): - # Если выбор не произошел, получаем доступные опции для отладки - available_options = self.get_object_class_options() - logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}") - raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'") - - logger.info(f"Класс объекта '{class_name}' успешно выбран") - - def get_object_class_options(self) -> list[str]: - """ - Получает список доступных опций из combobox. - - Returns: - list[str]: Список доступных классов объектов - """ - logger.info("Получение списка опций combobox 'Класс объекта учета'...") - - available_options = self.selection_bar.get_available_options() - - logger.info(f"Доступные опции класса объекта: {available_options}") - return available_options - - def get_selected_object_class(self) -> str: - """ - Получает выбранный класс объекта учета. - - Returns: - str: Выбранный класс объекта или пустая строка если ничего не выбрано - """ - # Получаем заголовок панели выбора - return self.selection_bar.get_selection_bar_title() - - def fill_rack_data(self, name: str, height: str = "42", depth: str = "1000", - serial: str = "", inventory: str = "", comment: str = "", - cable_entry: str = "", state: str = "", owner: str = "", - service_org: str = "", project: str = "") -> None: - """ - Заполняет данные для создания стойки. - - Args: - name: Наименование стойки - height: Высота в юнитах (по умолчанию 42) - depth: Глубина в мм (по умолчанию 1000) - serial: Серийный номер - inventory: Инвентарный номер - comment: Комментарий - cable_entry: Ввод кабеля - state: Состояние - owner: Владелец - service_org: Обслуживающая организация - project: Проект/Титул - """ - logger.info(f"Заполнение данных стойки: {name}") - - # Заполняем обязательные поля - name_field = self.page.locator(RackLocators.RACK_NAME_FIELD) - name_field.fill(name) - logger.info(f"Заполнено поле 'Имя': {name}") - - self._select_combobox("Высота в юнитах", height) - logger.info(f"Выбрана высота: {height} юнитов") - - self._select_combobox("Глубина (мм)", depth) - logger.info(f"Выбрана глубина: {depth} мм") - - # Заполняем опциональные поля - if serial: - serial_field = self.page.locator(RackLocators.RACK_SERIAL_FIELD) - serial_field.fill(serial) - logger.info(f"Заполнен серийный номер: {serial}") - - if inventory: - inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD) - inventory_field.fill(inventory) - logger.info(f"Заполнен инвентарный номер: {inventory}") - - if comment: - comment_field = self.page.locator(RackLocators.RACK_COMMENT_FIELD) - comment_field.fill(comment) - logger.info(f"Добавлен комментарий: {comment}") - - # Заполняем дополнительные combobox поля - if cable_entry: - self._select_combobox("Ввод кабеля", cable_entry) - logger.info(f"Выбран ввод кабеля: {cable_entry}") - - if state: - self._select_combobox("Состояние", state) - logger.info(f"Выбрано состояние: {state}") - - if owner: - self._select_combobox("Владелец", owner) - logger.info(f"Выбран владелец: {owner}") - - if service_org: - self._select_combobox("Обслуживающая организация", service_org) - logger.info(f"Выбрана обслуживающая организация: {service_org}") - - if project: - self._select_combobox("Проект/Титул", project) - logger.info(f"Выбран проект/титул: {project}") - - logger.info("Данные стойки заполнены") - - def _select_combobox(self, field_name: str, value: str) -> None: - """ - Выбор значения в combobox. - - Args: - field_name: Название поля - value: Значение для выбора - """ - logger.info(f"Выбор '{value}' в поле '{field_name}'...") - - # Получаем статический локатор из словаря - if field_name not in COMBOBOX_FIELDS_MAP: - raise ValueError(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP") - - field_locator = COMBOBOX_FIELDS_MAP[field_name] - - # Для всех полей используем first() чтобы избежать strict mode violation - field_container = self.page.locator(field_locator).first - - # Прокручиваем до поля - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(500) - - # Проверяем видимость поля - self.base_component.check_visibility(field_container, f"Поле '{field_name}' не найдено") - - # Универсальный клик с force=True для всех полей - field_container.click(force=True) - self.wait_for_timeout(1000) - - # Вводим значение - self.page.keyboard.type(value) - self.wait_for_timeout(500) - self.page.keyboard.press("Enter") - - logger.info(f"Поле '{field_name}' заполнено") - - def create_rack(self, rack_name: str, **kwargs) -> None: - """ - Полный процесс создания стойки. - - Args: - rack_name: Наименование стойки - **kwargs: Дополнительные параметры стойки - """ - logger.info(f"Начало процесса создания стойки: {rack_name}") - - # Выбираем класс объекта "Стойка" - self.select_object_class("Стойка") - self.wait_for_timeout(1000) - - # Проверяем наличие полей стойки - self.check_rack_fields_presence() - - # Заполняем данные - self.fill_rack_data(rack_name, **kwargs) - - # Создаем стойку - self.click_add_button() - - logger.info(f"Процесс создания стойки '{rack_name}' завершен") - - def clear_combobox_field(self, field_name: str) -> None: - """ - Очищает значение в combobox поле с помощью кнопки закрытия (крестика). - - Args: - field_name: Название поля для очистки - """ - logger.info(f"Очистка combobox поля '{field_name}' с помощью кнопки закрытия...") - - if field_name not in COMBOBOX_FIELDS_MAP: - logger.warning(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP") - return - - field_locator = COMBOBOX_FIELDS_MAP[field_name] - - # Находим поле по локатору - field_container = self.page.locator(field_locator).first - - # Проверяем что поле видимо - if not field_container.is_visible(): - logger.info(f"Поле '{field_name}' не видимо, пропускаем очистку") - return - - # Прокручиваем до поля - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(500) - - # Ищем кнопку закрытия (крестик) внутри контейнера поля - close_button = field_container.locator(ComboboxLocators.COMBOBOX_CLOSE_BUTTON) - - # Проверяем наличие и видимость кнопки закрытия - if close_button.count() > 0 and close_button.is_visible(): - # Если кнопка закрытия видима - кликаем на нее - close_button.click() - self.wait_for_timeout(500) - logger.info(f"Combobox поле '{field_name}' очищено с помощью кнопки закрытия") - else: - # Если кнопки закрытия нет, просто логируем этот факт - logger.info(f"Кнопка закрытия не найдена для поля '{field_name}', очистка не выполнена") - - def clear_rack_fields(self) -> None: - """ - Очищает все поля формы создания стойки. - """ - logger.info("Очистка всех полей формы стойки...") - - # Очищаем текстовые поля - text_fields = [ - (RackLocators.RACK_NAME_FIELD, "Имя"), - (RackLocators.RACK_SERIAL_FIELD, "Серийный номер"), - (RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"), - (RackLocators.RACK_COMMENT_FIELD, "Комментарий") - ] - - for field_locator, field_name in text_fields: - field = self.page.locator(field_locator) - if field.count() > 0 and field.first.is_visible(): - field.fill("") - logger.info(f"Текстовое поле '{field_name}' очищено") - - # Очищаем combobox поля - combobox_fields = [ - "Высота в юнитах", - "Глубина (мм)", - "Ввод кабеля", - "Состояние", - "Владелец", - "Обслуживающая организация", - "Проект/Титул" - ] - - for field_name in combobox_fields: - self.clear_combobox_field(field_name) - - logger.info("Все поля формы стойки очищены") - - # =============== МЕТОДЫ ПРОВЕРОК ======================== - def check_rack_exists(self, rack_name: str) -> bool: - """ - Проверяет, существует ли уже стойка с указанным именем в навигационной панели. - - Args: - rack_name: Имя стойки для проверки - - Returns: - bool: True если стойка существует, False если нет - """ - logger.info(f"Проверка существования стойки с именем '{rack_name}'") - - self.main_page.click_main_navigation_panel_item("Объекты") - self.main_page.click_main_navigation_panel_item("Объекты") - self.wait_for_timeout(1000) - self.main_page.click_subpanel_item("test-zone") - self.wait_for_timeout(3000) - - nav_panel_locator = NavigationPanelLocators.TREEVIEW - - # Проверяем видимость элемента через is_visible - element = self.page.locator(nav_panel_locator).get_by_text(rack_name).first - - if element.is_visible(): - logger.info(f"Стойка с именем '{rack_name}' найдена") - return True - else: - logger.info(f"Стойки с именем '{rack_name}' не найдена") - return False - - def should_be_toolbar_buttons(self) -> None: - """ - Проверяет наличие и функциональность кнопок тулбара. - - Raises: - AssertionError: Если кнопки недоступны или подсказки неверны. - """ - - self.wait_for_timeout(2000) - - self.toolbar.check_button_visibility("add") - self.toolbar.check_button_tooltip("add", "Добавить") - - self.toolbar.check_button_visibility("cancel") - self.toolbar.check_button_tooltip("cancel", "Отменить") - self.toolbar.click_button("cancel") - self.wait_for_timeout(2000) - - def check_toolbar_title(self, expected_title: str) -> None: - """ - Проверяет заголовок тулбара. - - Args: - expected_title: Ожидаемый заголовок тулбара - - Raises: - AssertionError: Если заголовок не соответствует ожидаемому - """ - logger.info(f"Проверка заголовок тулбара: '{expected_title}'...") - - # Используем метод тулбара с фильтрацией по тексту - actual_text = self.toolbar.get_toolbar_title_text( - filter_text="Создать дочерний элемент в" - ) - assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'" - - logger.info(f"Заголовок тулбара корректен: '{actual_text}'") - - def check_object_class_combobox_presence(self) -> None: - """ - Проверяет наличие combobox 'Класс объекта учета'. - - Raises: - AssertionError: Если combobox не найден - """ - logger.info("Проверка наличия combobox 'Класс объекта учета'...") - - self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не найден") - logger.info("Combobox 'Класс объекта учета' найден") - - def check_object_class_combobox_content(self) -> None: - """ - Проверяет содержимое combobox 'Класс объекта учета'. - - Raises: - AssertionError: Если содержимое не соответствует ожидаемому - """ - logger.info("Проверка содержимого combobox 'Класс объекта учета'...") - - combobox_locator = self.page.locator(ComboboxLocators.OBJECT_CLASS_COMBOBOX) - - # Проверяем что combobox видим - self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не виден") - - # Проверяем наличие label - label_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_LABEL) - expect(label_locator).to_have_text("Класс объекта учета") - - # Проверяем наличие input поля - input_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_INPUT) - self.base_component.check_visibility(input_locator, "Input поле combobox не найдено") - - # Для combobox нормально иметь readonly атрибут - это стандартное поведение - # Проверяем что поле доступно для выбора (не disabled) - expect(input_locator).not_to_have_attribute("disabled", "disabled") - - # Проверяем наличие иконки стрелки - icon_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_ICON_ARROW) - self.base_component.check_visibility(icon_locator, "Иконка стрелки combobox не найдена") - - logger.info("Содержимое combobox 'Класс объекта учета' корректно") - - def check_object_class_selected(self, expected_class: str) -> None: - """ - Проверяет что выбран указанный класс объекта. - - Args: - expected_class: Ожидаемый выбранный класс объекта - - Raises: - AssertionError: Если выбранный класс не соответствует ожидаемому - """ - logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...") - - # Даем время на обновление значения - self.wait_for_timeout(1000) - - actual_class = self.get_selected_object_class() - - # Проверка - допускаем частичное совпадение - if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower(): - logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')") - else: - raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'") - - def check_object_class_options_content(self, expected_options: list = None) -> None: - """ - Проверяет содержимое списка опций combobox. - - Args: - expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой. - - Raises: - AssertionError: Если список опций не соответствует ожидаемому - """ - logger.info("Проверка содержимого списка опций combobox...") - - # Получаем доступные опции - available_options = self.get_object_class_options() - - if expected_options is not None: - # Проверяем соответствие ожидаемому списку - assert set(available_options) == set(expected_options), ( - f"Список опций не соответствует ожидаемому. " - f"Ожидалось: {expected_options}, Получено: {available_options}" - ) - else: - # Проверяем что список не пустой - assert len(available_options) > 0, "Список опций combobox пустой" - - logger.info(f"Содержимое списка опций корректно: {available_options}") - - def check_dropdown_item_presence(self, item_text: str) -> None: - """ - Проверяет наличие элемента в выпадающем списке. - - Args: - item_text: Текст элемента для проверки - """ - logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...") - - # Получаем все опции и проверяем наличие - available_options = self.get_object_class_options() - - if item_text not in available_options: - raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}") - - logger.info(f"Элемент '{item_text}' присутствует в списке") - - def check_rack_fields_presence(self) -> None: - """ - Проверяет наличие полей специфичных для стойки. - - Raises: - AssertionError: Если какое-либо поле не найдено - """ - logger.info("Проверка наличия полей для стойки...") - - # Основные обязательные поля - required_fields = [ - (RackLocators.RACK_NAME_FIELD, "Имя"), - (RackLocators.RACK_HEIGHT_FIELD, "Высота в юнитах"), - (RackLocators.RACK_DEPTH_FIELD, "Глубина (мм)") - ] - - # Дополнительные поля - optional_fields = [ - (RackLocators.RACK_SERIAL_FIELD, "Серийный номер"), - (RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"), - (RackLocators.RACK_COMMENT_FIELD, "Комментарий"), - (RackLocators.RACK_CABLE_ENTRY_FIELD, "Ввод кабеля"), - (RackLocators.RACK_STATE_FIELD, "Состояние"), - (RackLocators.RACK_OWNER_FIELD, "Владелец"), - (RackLocators.RACK_SERVICE_ORG_FIELD, "Обслуживающая организация"), - (RackLocators.RACK_PROJECT_FIELD, "Проект/Титул") - ] - - # Проверяем обязательные поля - for field_locator, field_name in required_fields: - self.base_component.check_visibility(field_locator, f"Обязательное поле '{field_name}' не найдено") - logger.info(f"Обязательное поле '{field_name}' найдено") - - # Проверяем дополнительные поля - for field_locator, field_name in optional_fields: - field = self.page.locator(field_locator) - if field.count() > 0 and field.first.is_visible(): - logger.info(f"Дополнительное поле '{field_name}' найдено") - else: - logger.info(f"Дополнительное поле '{field_name}' не найдено или не отображается") - - logger.info("Все основные поля для стойки присутствуют") - - def check_field_highlighted_error(self, field_name: str) -> None: - """ - Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). - - Args: - field_name: Название поля для проверки - """ - logger.info(f"Проверка подсветки поля '{field_name}' цветом ошибки...") - - # Локаторы только для обязательных полей - required_fields = { - "Имя": RackLocators.RACK_NAME_FIELD, - "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, - "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD - } - - if field_name not in required_fields: - raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается") - - field_locator = required_fields[field_name] - field_element = self.page.locator(field_locator) - - # Проверяем что поле видимо - self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено") - - # Ищем родительский контейнер с использованием константы - parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first - - # Проверка классов ошибки - if parent_container.count() > 0: - error_classes = AlertLocators.ERROR_CLASSES - - is_error_highlighted = False - for error_class in error_classes: - error_element = parent_container.locator(f".{error_class}") - if error_element.count() > 0: - is_error_highlighted = True - logger.info(f"Поле '{field_name}' подсвечено ошибкой") - break - - if not is_error_highlighted: - raise AssertionError(f"Поле '{field_name}' не подсвечено цветом ошибки ") - - logger.info(f"Поле '{field_name}' корректно подсвечено цветом ошибки") - - def check_field_not_highlighted_error(self, field_name: str) -> None: - """ - Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). - - Args: - field_name: Название поля для проверки - """ - logger.info(f"Проверка отсутствия подсветки ошибки у поля '{field_name}'...") - - # Локаторы только для обязательных полей - required_fields = { - "Имя": RackLocators.RACK_NAME_FIELD, - "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, - "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD - } - - if field_name not in required_fields: - raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается") - - field_locator = required_fields[field_name] - field_element = self.page.locator(field_locator) - - # Проверяем что поле видимо - self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено") - - # Ищем родительский контейнер с использованием константы - parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first - - # Поверка отсутствия классов ошибки - if parent_container.count() > 0: - error_classes = AlertLocators.ERROR_CLASSES - - for error_class in error_classes: - error_element = parent_container.locator(f".{error_class}") - if error_element.count() > 0: - raise AssertionError(f"Поле '{field_name}' подсвечено ошибкой") - - logger.info(f"Поле '{field_name}' корректно не подсвечено цветом ошибки") diff --git a/pages/rack_page.py b/pages/rack_page.py index bdc0109..37947a6 100644 --- a/pages/rack_page.py +++ b/pages/rack_page.py @@ -49,102 +49,32 @@ class RackPage(BasePage): self.show_button = TooltipButton(page, show_button_locator, "show_rack") # Кнопка "Переместить" - replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON) - self.replace_button = TooltipButton(page, replace_button_locator, "replace") + # replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON) + # self.replace_button = TooltipButton(page, replace_button_locator, "replace") # Кнопка "Сохранить" - done_button_locator = self.page.locator(RackLocators.TOOLBAR_DONE_BUTTON) - self.done_button = TooltipButton(page, done_button_locator, "done") + # done_button_locator = self.page.locator(RackLocators.TOOLBAR_DONE_BUTTON) + # self.done_button = TooltipButton(page, done_button_locator, "done") # Кнопка "Отменить" - close_button_locator = self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON) - self.close_button = TooltipButton(page, close_button_locator, "close") + # close_button_locator = self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON) + # self.close_button = TooltipButton(page, close_button_locator, "close") # Кнопка "Удалить" - remove_button_locator = self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON) - self.remove_button = TooltipButton(page, remove_button_locator, "remove") + # remove_button_locator = self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON) + # self.remove_button = TooltipButton(page, remove_button_locator, "remove") self.toolbar = ToolbarComponent(page, "") self.toolbar.add_tooltip_button(locator_button, "edit") self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack") self.toolbar.add_tooltip_button(show_button_locator, "show_rack") - 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") + + #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") - - # Проверяем тултип кнопки (может быть "Удалить" или "Remove") - try: - self.toolbar.check_button_tooltip("remove", "Удалить") - except AssertionError: - try: - self.toolbar.check_button_tooltip("remove", "Remove") - except AssertionError: - logger.debug("Could not verify tooltip text for remove button") - - # Кликаем на кнопку удаления - self.toolbar.get_button_by_name("remove").click() - self.wait_for_timeout(1000) - - # Ожидаем появления диалога подтверждения - self._handle_remove_confirmation_dialog() - - 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 get_available_tabs(self) -> list[str]: """ Возвращает список доступных вкладок. @@ -536,18 +466,6 @@ class RackPage(BasePage): # Кликаем на кнопку "Изменить" для проверки функциональности self.toolbar.get_button_by_name("edit").click() - # Проверяем новые кнопки тулбара - self.toolbar.check_button_visibility("replace") - self.toolbar.check_button_tooltip("replace", "Переместить") - - self.toolbar.check_button_visibility("done") - self.toolbar.check_button_tooltip("done", "Сохранить") - - self.toolbar.check_button_visibility("close") - self.toolbar.check_button_tooltip("close", "Отменить") - - self.toolbar.check_button_visibility("remove") - self.toolbar.check_button_tooltip("remove", "Удалить") def should_have_hide_rack_button(self) -> None: """ @@ -690,11 +608,6 @@ class RackPage(BasePage): logger.debug("%s check completed successfully", side_name) - def _handle_remove_confirmation_dialog(self) -> None: - """Обрабатывает диалог подтверждения удаления.""" - logger.debug("Handling remove confirmation dialog...") - self.confirm_remove_dialog(confirm=True) - def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None: """ Ожидает активации вкладки. diff --git a/pages/rack_tab/rack_tab.py b/pages/rack_tab/rack_tab.py deleted file mode 100644 index 01f722a..0000000 --- a/pages/rack_tab/rack_tab.py +++ /dev/null @@ -1,466 +0,0 @@ -"""Модуль тестов вкладки 'Стойка'. - -Содержит тесты для проверки функциональности -работы со стойкой оборудования. -""" - -from playwright.sync_api import Page, expect -from elements.tooltip_button_element import TooltipButton -from components.toolbar_component import ToolbarComponent -from pages.base_page import BasePage -from locators.rack_locators import RackLocators -from tools.logger import get_logger - -logger = get_logger("RACK_TAB") - -# Специфичные локаторы оставленые в основном коде -PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i" -TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']" -EDIT_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button" -PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]" - - -class RackTab(BasePage): - """Класс для работы с вкладкой стойки оборудования.""" - - def __init__(self, page: Page) -> None: - """ - Инициализирует объект вкладки стойки. - - Args: - page: Экземпляр страницы Playwright - """ - super().__init__(page) - - locator_button = self.page.locator(PANEL_HEADER).\ - locator(EDIT_BUTTON_ANCESTOR_DIV3).nth(0) - self.edit_button = TooltipButton(page, locator_button, "edit") - - self.toolbar = ToolbarComponent(page, "") - self.toolbar.add_tooltip_button(locator_button, "edit") - - def wait_for_rack_loading(self, timeout: int = 15000) -> None: - """ - Ожидает загрузки интерфейса стойки. - - Args: - timeout: Время ожидания в миллисекундах (по умолчанию 15000) - - Raises: - TimeoutError: Если загрузка не завершилась в указанное время - """ - logger.info("Ожидание загрузки интерфейса стойки...") - - # Ждем появления основного контейнера - main_container = self.page.locator(RackLocators.MAIN_CONTAINER) - expect(main_container).to_be_visible(timeout=timeout) - - # Ждем появления юнитов - units = self.page.locator(RackLocators.ALL_UNITS) - expect(units).to_have_count(20, timeout=timeout) - - logger.info("Интерфейс стойки загружен") - - def get_toolbar_title(self) -> list[str]: - """ - Получает заголовок панели инструментов. - - Returns: - list[str]: Список элементов заголовка панели инструментов - """ - toolbar_title_locator = self.page.locator(PANEL_HEADER).\ - locator(PANEL_HEADER_ANCESTOR_DIV2).get_by_role("navigation").\ - locator(TOOLBAR_CONTENT) - - return self.toolbar.get_toolbar_composite_title_text(toolbar_title_locator) - - def switch_to_tab(self, tab_name: str) -> None: - """ - Переключается на указанную вкладку. - - Args: - tab_name: Название вкладки для переключения - - Raises: - AssertionError: Если вкладка не найдена или недоступна - """ - logger.info(f"Переключение на вкладку '{tab_name}'...") - - tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) - - if tab.count() == 0: - raise AssertionError(f"Вкладка '{tab_name}' не найдена") - - # Проверяем активность ДО клика - if self.is_tab_active(tab_name): - logger.info(f"Вкладка '{tab_name}' уже активна") - return - - # Находим первую видимую вкладку с нужным именем - target_tab = None - for i in range(tab.count()): - element = tab.nth(i) - if element.is_visible() and element.is_enabled(): - target_tab = element - break - - if not target_tab: - raise AssertionError(f"Не найдена видимая/доступная вкладка '{tab_name}'") - - # Кликаем на вкладку - logger.info(f"Клик на вкладку '{tab_name}'...") - target_tab.click() - - # Ждем изменения активной вкладки - self._wait_for_tab_activation(tab_name) - - # Ждем загрузки контента - self.page.wait_for_timeout(500) - - 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: - """ - Проверяет, активна ли указанная вкладка. - - Args: - tab_name: Название вкладки для проверки - - Returns: - bool: True если вкладка активна, False в противном случае - """ - # Метод 1: Проверяем по активному классу и тексту, метод быстый, если надо универсальный оставояем метод 2 - медленный - active_tab = self.page.locator(RackLocators.ACTIVE_TAB) - - if active_tab.count() > 0 and active_tab.first.is_visible(): - active_text = active_tab.first.text_content() - if active_text and active_text.strip() == tab_name: - logger.info(f"Вкладка '{tab_name}' активна (через класс активной вкладки)") - return True - - # Метод 2: Проверяем по классам у конкретной вкладки - tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) - - if tab.count() > 0: - for i in range(tab.count()): - element = tab.nth(i) - if element.is_visible() and element.is_enabled(): - 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_name}' активна (классы: {element_class})") - return True - - logger.info(f"Вкладка '{tab_name}' не активна") - return False - - def get_available_tabs(self) -> list[str]: - """ - Возвращает список доступных вкладок используя DOM структуру. - - Returns: - list[str]: Список названий доступных вкладок - """ - tabs = [] - - # Используем локатор для верхних вкладок - tab_elements = self.page.locator(RackLocators.ALL_TABS) - - # Ждем появления элементов - tab_elements.first.wait_for(state="visible", timeout=5000) - - total_count = tab_elements.count() - logger.info(f"Всего найдено элементов верхних вкладок: {total_count}") - - for i in range(total_count): - element = tab_elements.nth(i) - - # Проверяем видимость и доступность элемента - if element.is_visible() and element.is_enabled(): - tab_text = element.text_content() - if tab_text: - tab_text = tab_text.strip() - if tab_text and tab_text not in tabs: - tabs.append(tab_text) - logger.info(f"Найдена верхняя вкладка: '{tab_text}'") - - logger.info(f"Найдены доступные верхние вкладки: {tabs}") - return tabs - - def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None: - """ - Ожидает активации вкладки. - - Args: - tab_name: Название вкладки для ожидания - timeout: Время ожидания в миллисекундах - - Raises: - AssertionError: Если вкладка не активирована в течение таймаута - """ - logger.info(f"Ожидание активации вкладки '{tab_name}'...") - - start_time = self.page.evaluate("Date.now()") - while self.page.evaluate("Date.now()") - start_time < timeout: - if self.is_tab_active(tab_name): - logger.info(f"Вкладка '{tab_name}' успешно активирована") - return - self.page.wait_for_timeout(100) - - raise AssertionError(f"Вкладка '{tab_name}' не активирована в течение {timeout}мс") - - def should_be_toolbar_buttons(self) -> None: - """ - Проверяет наличие и функциональность кнопок тулбара. - - Raises: - AssertionError: Если кнопки недоступны или подсказки неверны. - """ - logger.info("Проверка кнопок панели инструментов...") - - self.toolbar.check_button_visibility("edit") - self.toolbar.check_button_tooltip("edit", "Изменить") - self.toolbar.get_button_by_name("edit").click() - - def should_be_rack_sides_displayed(self) -> None: - """ - Проверка отображения и структуры сторон стойки. - - Raises: - AssertionError: Если стороны стойки не отображаются корректно - """ - logger.info("Проверка отображения и структуры сторон стойки...") - - # Ожидаем загрузки - self.wait_for_rack_loading() - - # БАЗОВАЯ ПРОВЕРКА: обе стороны отображаются - logger.info("--- Базовая проверка отображения сторон ---") - - front_side_section = self.page.locator(RackLocators.FRONT_SIDE_SECTION).first - expect(front_side_section).to_be_visible(timeout=10000) - logger.info("Секция лицевой стороны найдена") - - back_side_section = self.page.locator(RackLocators.BACK_SIDE_SECTION).first - expect(back_side_section).to_be_visible(timeout=10000) - logger.info("Секция обратной стороны найдена") - - # Проверяем заголовки - front_side_title = front_side_section.locator(RackLocators.FRONT_SIDE_TITLE) - expect(front_side_title).to_be_visible(timeout=5000), "Заголовок 'Лицевая сторона' не отображается" - logger.info("Заголовок 'Лицевая сторона' отображается") - - back_side_title = back_side_section.locator(RackLocators.BACK_SIDE_TITLE) - expect(back_side_title).to_be_visible(timeout=5000), "Заголовок 'Обратная сторона' не отображается" - logger.info("Заголовок 'Обратная сторона' отображается") - - # Проверяем позиции юнитов - unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) - total_positions = unit_positions.count() - logger.info(f"Всего позиций юнитов: {total_positions}") - assert total_positions > 0, "Не найдено позиций юнитов" - - # Детальная проверка лицевой стороны - logger.info("--- Детальная проверка лицевой стороны ---") - self._check_front_side_details(front_side_section) - - # Детальная проверка обратной стороны - logger.info("--- Детальная проверка обратной стороны ---") - self._check_back_side_details(back_side_section) - - logger.info("Все проверки сторон стойки пройдены успешно") - - def _check_front_side_details(self, front_side_section) -> None: - """ - Проверка структуры лицевой стороны стойки. - - Args: - front_side_section: Локатор секции лицевой стороны - - Raises: - AssertionError: Если структура лицевой стороны некорректна - """ - # Проверяем юниты в секции лицевой стороны - front_side_units = front_side_section.locator(RackLocators.FRONT_SIDE_UNITS) - unit_count = front_side_units.count() - logger.info(f"Найдено юнитов на лицевой стороне: {unit_count}") - assert unit_count >= 1, f"Не найдено юнитов на лицевой стороне. Ожидалось минимум 1, найдено {unit_count}" - - # Проверяем наличие устройств на лицевой стороне - front_side_devices = front_side_section.locator(RackLocators.FRONT_SIDE_DEVICES) - device_count = front_side_devices.count() - logger.info(f"Найдено физических устройств на лицевой стороне: {device_count}") - - if device_count > 0: - for i in range(device_count): - device = front_side_devices.nth(i) - device_title = device.get_attribute("title") - device_classes = device.get_attribute("class") or "" - logger.info(f" Устройство {i}: title='{device_title}', classes='{device_classes}'") - - def _check_back_side_details(self, back_side_section) -> None: - """ - Проверка структуры обратной стороны стойки. - - Args: - back_side_section: Локатор секции обратной стороны - - Raises: - AssertionError: Если структура обратной стороны некорректна - """ - # Проверяем юниты в секции обратной стороны - back_side_units = back_side_section.locator(RackLocators.BACK_SIDE_UNITS) - unit_count = back_side_units.count() - logger.info(f"Найдено юнитов на обратной стороне: {unit_count}") - assert unit_count >= 1, f"Не найдено юнитов на обратной стороне. Ожидалось минимум 1, найдено {unit_count}" - - # Проверяем наличие устройств на обратной стороне - back_side_devices = back_side_section.locator(RackLocators.BACK_SIDE_DEVICES) - device_count = back_side_devices.count() - logger.info(f"Найдено физических устройств на обратной стороне: {device_count}") - - if device_count > 0: - for i in range(device_count): - device = back_side_devices.nth(i) - device_title = device.get_attribute("title") - device_classes = device.get_attribute("class") or "" - logger.info(f" Устройство {i}: title='{device_title}', classes='{device_classes}'") - - def should_be_header_panel(self, expected_toolbar_title_items: list[str]) -> None: - """ - Проверяет наличие и корректность заголовка панели. - - Args: - expected_toolbar_title_items: Ожидаемые элементы заголовка - - Raises: - AssertionError: Если заголовок панели не соответствует ожиданиям - """ - panel_header_locator = self.page.locator(PANEL_HEADER) - expect(panel_header_locator).to_be_visible(), "Panel header 'Объекты'" - - if panel_header_locator.inner_text() != 'chevron_right': - assert False, "No separator 'chevron_right' after header 'Объекты'" - - actual_toolbar_title_items = self.get_toolbar_title() - - self.check_lists_equals(actual_toolbar_title_items, - expected_toolbar_title_items, - f"Miscomparison actual {actual_toolbar_title_items} and expected {expected_toolbar_title_items}") - - self.toolbar.check_button_visibility("edit") - - - def check_tab_switching(self) -> None: - """ - Проверяет переключение между вкладками стойки в соответствии с локаторами. - - Raises: - AssertionError: Если переключение на одну или более вкладок не удалось - """ - logger.info("Тестирование функциональности переключения вкладок стойки...") - - # Вкладки - defined_tabs = [ - "Общая информация", - "Обслуживание", - "События", - "Сервисы" - ] - - logger.info(f"Тестируемые определенные вкладки: {defined_tabs}") - - successful_switches = 0 - failed_switches = [] - - # Тестируем переключение на каждую определенную вкладку - for tab_name in defined_tabs: - logger.info(f"Тестирование переключения на вкладку '{tab_name}'...") - - # Проверяем существование локатора для этой вкладки - tab_locator = RackLocators.TAB_BY_NAME.format(tab_name) - tab_elements = self.page.locator(tab_locator) - - # Проверяем наличие элементов через count() - if tab_elements.count() == 0: - logger.warning(f"Вкладка '{tab_name}' не найдена на странице") - failed_switches.append(f"Вкладка '{tab_name}' не найдена") - continue - - # Находим видимую и доступную вкладку - target_tab = None - for i in range(tab_elements.count()): - element = tab_elements.nth(i) - # Проверки видимости и доступности - if element.is_visible() and element.is_enabled(): - target_tab = element - break - - if not target_tab: - logger.warning(f"Не найдена видимая/доступная вкладка '{tab_name}'") - failed_switches.append(f"Вкладка '{tab_name}' не видима/не доступна") - continue - - # Переключаемся на вкладку - logger.info(f"Переключение на вкладку '{tab_name}'...") - - # Проверяем активность ДО клика - if self.is_tab_active(tab_name): - logger.info(f"Вкладка '{tab_name}' уже активна") - successful_switches += 1 - continue - - # Кликаем на вкладку с таймаутом - target_tab.click(timeout=5000) - - # Ждем изменения активной вкладки - self._wait_for_tab_activation(tab_name) - - # Проверяем, что вкладка активна - if not self.is_tab_active(tab_name): - logger.warning(f"Вкладка '{tab_name}' не активна после переключения") - failed_switches.append(f"Вкладка '{tab_name}' не активна после клика") - continue - - logger.info(f"Успешно переключено на вкладку '{tab_name}'") - successful_switches += 1 - - # Небольшая пауза между переключениями для стабильности - self.page.wait_for_timeout(1000) - - # Формируем итоговый отчет - logger.info("=== РЕЗУЛЬТАТЫ ПЕРЕКЛЮЧЕНИЯ ВКЛАДОК ===") - logger.info(f"Успешных переключений: {successful_switches}/{len(defined_tabs)}") - - if failed_switches: - logger.info("Неудачные переключения:") - for failure in failed_switches: - logger.info(f" - {failure}") - - # Требуем успешного переключения на все определенные вкладки - if successful_switches < len(defined_tabs): - raise AssertionError( - f"Тест переключения вкладок не пройден. " - f"Только {successful_switches} из {len(defined_tabs)} определенных вкладок переключены успешно. " - f"Ошибки: {', '.join(failed_switches)}" - ) - - logger.info(f"Все {successful_switches} определенных вкладок успешно переключены!") diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py index a883483..8650277 100644 --- a/tests/e2e/create_elements/test_create_rack_element.py +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -167,10 +167,15 @@ class TestCreateRackElement: logger.debug("Edit button clicked, waiting for edit form...") - # 3. Используем метод click_remove_button, который обрабатывает весь процесс удаления - # включая диалог подтверждения + # 3. Создаем экземпляр ModalRackEdit и используем его метод + rack_edit = ModalRackEdit(browser) + + # Проверяем видимость кнопки удаления в модальном окне + rack_edit.check_toolbar_button_visibility("remove") + + # Используем метод из ModalRackEdit для удаления logger.debug("Clicking remove button...") - rack_page.click_remove_button() + rack_edit.click_remove_button() # 4. Проверяем уведомление об успешном удалении - требуется создать разработчику (заведена задача) # Создаем экземпляр фрейма для доступа к alert компоненту diff --git a/tests/e2e/elements/test_element_rack.py b/tests/e2e/elements/test_element_rack.py index ecf8292..1fe05d9 100644 --- a/tests/e2e/elements/test_element_rack.py +++ b/tests/e2e/elements/test_element_rack.py @@ -5,14 +5,16 @@ """ import pytest -from playwright.sync_api import Page +from playwright.sync_api import Page, expect from locators.navigation_panel_locators import NavigationPanelLocators +from pages.location_page import LocationPage from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData from components_derived.frames.create_child_element_frame import CreateChildElementFrame -from pages.location_page import LocationPage from pages.login_page import LoginPage from pages.main_page import MainPage from pages.rack_page import RackPage +from tools.logger import get_logger +from components_derived.modal_rack_edit import ModalRackEdit, RackEditData # Константы RACK_NAME = "Test-Rack-Functionality" @@ -118,9 +120,28 @@ class TestRackTab: # Кликаем на кнопку "Изменить" rack_page.toolbar.get_button_by_name("edit").click() - # 3. Используем метод click_remove_button, который обрабатывает весь процесс удаления - # включая диалог подтверждения - rack_page.click_remove_button() + # 3. Создаем экземпляр ModalRackEdit + rack_edit = ModalRackEdit(browser) + + # Используем метод для удаления + rack_edit.click_remove_button() + + # 4. Проверяем уведомление об успешном удалении - требуется создать разработчику (заведена задача) + # Создаем экземпляр фрейма для доступа к alert компоненту + # create_child_frame = CreateChildElementFrame(browser) + + # Проверяем наличие любого alert-окна (не обязательно точного текста) + # create_child_frame.alert.check_alert_presence("") + + # Получаем текст alert, чтобы убедиться что удаление прошло успешно + # alert_text = create_child_frame.alert.get_text() + # logger.debug(f"Alert text after deletion: {alert_text}") + + # Проверяем что в тексте есть указание на успешное удаление + # assert "удален" in alert_text.lower() or "успешно" in alert_text.lower() + + # Закрываем alert + # create_child_frame.alert.close_alert() @pytest.fixture(scope="function", autouse=True) def setup(self, browser: Page) -> None: @@ -251,3 +272,107 @@ class TestRackTab: # Переход в режим редактирования rt.should_be_toolbar_buttons() rt.wait_for_timeout(1000) + + @pytest.mark.develop + def test_rack_general_info_tab_fields(self, browser: Page) -> None: + """Тест заполнения полей вкладки 'Общая информация' стойки.""" + logger = get_logger("RACK_GENERAL_INFO_TEST") + + rt = RackPage(browser) + + # Переходим в режим редактирования + logger.debug("Switching to edit mode...") + rt.toolbar.check_button_visibility("edit") + rt.toolbar.get_button_by_name("edit").click() + rt.wait_for_timeout(3000) + + # Создаем экземпляр ModalRackEdit + rack_edit = ModalRackEdit(browser) + rack_edit.should_be_toolbar_buttons() + + # Получаем список доступных полей (используем точные названия из этого списка) + available_fields = rack_edit.get_available_fields() + logger.info(f"Available fields in form: {available_fields}") + + # Создаем маппинг: используем ТОЧНЫЕ названия полей из available_fields + field_mapping = {} + + # Текстовые поля + for field_pattern, test_value in [ + ("Имя", "Test-Rack-Functionality"), + ("Серийный номер", "SN123456789"), + ("Инвентарный номер", "INV987654321"), + ("Комментарий", "Тестовый комментарий для стойки"), + ("Выделенная мощность (Вт/ВА)", "55"), + ]: + # Ищем точное совпадение + if field_pattern in available_fields: + field_mapping[field_pattern] = ("text", test_value) + + # Combobox поля + for field_pattern, test_value in [ + ("Ввод кабеля", "Сверху"), + ("Состояние", "Введен в эксплуатацию"), + ("Владелец", "Тестовый владелец"), + ("Обслуживающая организация", "Тестовая сервисная организация"), + ("Проект/Титул", "Тестовый проект"), + ]: + if field_pattern in available_fields: + field_mapping[field_pattern] = ("combobox", test_value) + + # Заполняем каждое поле вручную + results = { + "text_fields_filled": 0, + "combobox_fields_filled": 0, + "checkboxes_set": 0 + } + + logger.info("Filling fields individually...") + + for field_name, (field_type, value) in field_mapping.items(): + logger.info(f"Filling {field_type} field '{field_name}' with '{value}'...") + + if field_type == "text": + success = rack_edit._fill_text_field(field_name, value) + if success: + results["text_fields_filled"] += 1 + logger.info(f"✓ Text field '{field_name}' filled") + else: + logger.warning(f"✗ Failed to fill text field '{field_name}'") + + elif field_type == "combobox": + success = rack_edit._fill_combobox_field(field_name, value) + if success: + results["combobox_fields_filled"] += 1 + logger.info(f"✓ Combobox field '{field_name}' filled") + else: + logger.warning(f"✗ Failed to fill combobox field '{field_name}'") + + # Устанавливаем checkbox + test_ventilation_panel = True + logger.info("Setting ventilation panel checkbox...") + success = rack_edit._set_checkbox_field("Вентиляционная панель", test_ventilation_panel) + if success: + results["checkboxes_set"] += 1 + logger.info("✓ Checkbox set") + else: + logger.warning("✗ Failed to set checkbox") + + # Проверяем результаты + logger.info(f"Fill results: {results}") + + # Проверяем что хотя бы некоторые поля были заполнены + total_filled = results.get("text_fields_filled", 0) + results.get("combobox_fields_filled", 0) + assert total_filled > 0, f"No fields were filled successfully. Results: {results}" + + # Сохраняем изменения + logger.info("Saving changes...") + # Используем метод из ModalRackEdit для кнопки "Сохранить" + rack_edit.click_done_button() + rack_edit.wait_for_timeout(3000) + + # Проверяем выход из режима редактирования + rt.toolbar.check_button_visibility("edit") + logger.info("✓ Successfully exited edit mode") + + logger.info("✓ General Info tab fields test completed")