From 4d3731d9f7f4eb6f534564aea13efd1494705498 Mon Sep 17 00:00:00 2001 From: Radislav Date: Thu, 22 Jan 2026 10:12:01 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=8B=20=D1=81=D0=BE=20=D1=81=D1=82=D0=BE=D0=B9=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/rack_maker.cpython-313.pyc | Bin 0 -> 14467 bytes ...create_child_element_frame.cpython-313.pyc | Bin 0 -> 16678 bytes .../frames/create_child_element_frame.py | 2 +- components_derived/modal_rack_edit.py | 465 ++++++++++++ .../create_child_element_tab.py | 355 --------- .../create_rack_element_tab.py | 678 ------------------ pages/rack_page.py | 113 +-- pages/rack_tab/rack_tab.py | 466 ------------ .../test_create_rack_element.py | 11 +- tests/e2e/elements/test_element_rack.py | 135 +++- 10 files changed, 617 insertions(+), 1608 deletions(-) create mode 100644 components_derived/accounting_objects/__pycache__/rack_maker.cpython-313.pyc create mode 100644 components_derived/frames/__pycache__/create_child_element_frame.cpython-313.pyc create mode 100644 components_derived/modal_rack_edit.py delete mode 100644 pages/create_elements_tab/create_child_element_tab.py delete mode 100644 pages/create_elements_tab/create_rack_element_tab.py delete mode 100644 pages/rack_tab/rack_tab.py 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 0000000000000000000000000000000000000000..a8c37637579e6f5dcb4082977bdcb5f7ababcf2f GIT binary patch literal 14467 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 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")