From 3680e42c86dd608897fe94818b396ce51b513f0d Mon Sep 17 00:00:00 2001 From: Radislav Date: Mon, 19 Jan 2026 08:24:45 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20pylint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accounting_objects/rack_maker.py | 201 +++--- .../frames/create_child_element_frame.py | 40 +- pages/rack_page.py | 220 +++--- .../test_create_rack_element.py | 642 +++++++----------- 4 files changed, 514 insertions(+), 589 deletions(-) diff --git a/components_derived/accounting_objects/rack_maker.py b/components_derived/accounting_objects/rack_maker.py index 423f0a2..17cd8c9 100644 --- a/components_derived/accounting_objects/rack_maker.py +++ b/components_derived/accounting_objects/rack_maker.py @@ -35,44 +35,98 @@ class RackObjectMaker(BaseComponent): Инициализирует компонент создания стойки. Args: - page: Экземпляр страницы Playwright + page (Page): Экземпляр страницы Playwright """ super().__init__(page) # Действия: - def fill_rack_data(self, rack_data: RackData) -> None: + def _fill_combobox_field(self, field_name: str, value: str, fields_locators: dict) -> None: """ - Заполняет данные для создания стойки. + Заполняет combobox поле. Args: - rack_data: Данные стойки + field_name (str): Название поля + value (str): Значение для установки + fields_locators (dict): Словарь с найденными полями формы + + Raises: + ValueError: Если поле не найдено в форме """ - logger.debug(f"Filling rack data: {rack_data.name}") + # Получаем контейнер поля по его названию + field_container = fields_locators.get(field_name) - self._fill_text_fields(rack_data) - self._fill_combobox_fields(rack_data) + if not field_container: + logger.error(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}") + raise ValueError(f"Field '{field_name}' not found in form") - logger.debug("Rack data filled successfully") + logger.debug(f"Filling field '{field_name}' with value '{value}'...") - def _get_form_fields(self) -> dict: - """ - Получает все поля формы стойки. + # Прокручиваем до поля + field_container.scroll_into_view_if_needed() + self.wait_for_timeout(300) - Returns: - dict: Словарь {название поля: Locator контейнера поля} - """ + # Проверяем видимость поля + self.check_visibility(field_container, f"Field '{field_name}' not found") - # Получаем контейнер формы (второй элемент) - container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) + # Находим кнопку открытия выпадающего списка внутри контейнера поля + open_button = field_container.locator(".v-input__append-inner").first - if container_locator.count() == 0: - logger.error("Form container not found") - raise ValueError("Form container not found") + # Кликаем для открытия выпадающего списка + open_button.click(force=True) + self.wait_for_timeout(300) - return self.get_input_fields_locators(container_locator) + # Вводим значение из выпадающего списка + dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value) + element = self.page.locator(dropdown_item_locator).first + + # Скроллим к элементу если нужно + self._scroll_until_element( + self.page.locator(RackLocators.DROPDOWN_LIST).first, + value + ) + self.wait_for_timeout(300) + element.click() + + logger.debug(f"Field '{field_name}' filled successfully") + + def _fill_combobox_fields(self, rack_data: RackData) -> None: + """Заполняет combobox поля.""" + + # Получаем все поля формы + fields_locators = self._get_form_fields() + + # Обязательные поля. + if rack_data.height: + self._fill_combobox_field("Высота в юнитах", rack_data.height, fields_locators) + logger.debug(f"Selected height: {rack_data.height} units") + + if rack_data.depth: + self._fill_combobox_field("Глубина (мм)", rack_data.depth, fields_locators) + logger.debug(f"Selected depth: {rack_data.depth} mm") + + # Опциональные поля. + if rack_data.cable_entry: + self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry, fields_locators) + logger.debug(f"Selected cable entry: {rack_data.cable_entry}") + + if rack_data.state: + self._fill_combobox_field("Состояние", rack_data.state, fields_locators) + logger.debug(f"Selected state: {rack_data.state}") + + if rack_data.owner: + self._fill_combobox_field("Владелец", rack_data.owner, fields_locators) + logger.debug(f"Selected owner: {rack_data.owner}") + + if rack_data.service_org: + self._fill_combobox_field("Обслуживающая организация", rack_data.service_org, fields_locators) + logger.debug(f"Selected service organization: {rack_data.service_org}") + + if rack_data.project: + self._fill_combobox_field("Проект/Титул", rack_data.project, fields_locators) + logger.debug(f"Selected project/title: {rack_data.project}") def _fill_text_fields(self, rack_data: RackData) -> None: """Заполняет текстовые поля.""" @@ -144,95 +198,33 @@ class RackObjectMaker(BaseComponent): logger.debug("Text fields filled successfully") - def _fill_combobox_fields(self, rack_data: RackData) -> None: - """Заполняет combobox поля.""" - - # Получаем все поля формы - fields_locators = self._get_form_fields() - - # Обязательные поля. - if rack_data.height: - self._fill_combobox_field("Высота в юнитах", rack_data.height, fields_locators) - logger.debug(f"Selected height: {rack_data.height} units") - - if rack_data.depth: - self._fill_combobox_field("Глубина (мм)", rack_data.depth, fields_locators) - logger.debug(f"Selected depth: {rack_data.depth} mm") - - # Опциональные поля. - if rack_data.cable_entry: - self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry, fields_locators) - logger.debug(f"Selected cable entry: {rack_data.cable_entry}") - - if rack_data.state: - self._fill_combobox_field("Состояние", rack_data.state, fields_locators) - logger.debug(f"Selected state: {rack_data.state}") - - if rack_data.owner: - self._fill_combobox_field("Владелец", rack_data.owner, fields_locators) - logger.debug(f"Selected owner: {rack_data.owner}") - - if rack_data.service_org: - self._fill_combobox_field("Обслуживающая организация", rack_data.service_org, fields_locators) - logger.debug(f"Selected service organization: {rack_data.service_org}") - - if rack_data.project: - self._fill_combobox_field("Проект/Титул", rack_data.project, fields_locators) - logger.debug(f"Selected project/title: {rack_data.project}") - - def _fill_combobox_field(self, field_name: str, value: str, fields_locators: dict) -> None: + def _get_form_fields(self) -> dict: """ - Заполняет combobox поле. + Получает все поля формы стойки. - Args: - field_name: Название поля - value: Значение для установки - fields_locators: Словарь с найденными полями формы + Returns: + dict: Словарь {название поля: Locator контейнера поля} + + Raises: + ValueError: Если контейнер формы не найден """ - # Получаем контейнер поля по его названию - field_container = fields_locators.get(field_name) + # Получаем контейнер формы (второй элемент) + container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) - if not field_container: - logger.error(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}") - raise ValueError(f"Field '{field_name}' not found in form") + if container_locator.count() == 0: + logger.error("Form container not found") + raise ValueError("Form container not found") - logger.debug(f"Filling field '{field_name}' with value '{value}'...") - - # Прокручиваем до поля - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(300) - - # Проверяем видимость поля - self.check_visibility(field_container, f"Field '{field_name}' not found") - - # Находим кнопку открытия выпадающего списка внутри контейнера поля - open_button = field_container.locator(".v-input__append-inner").first - - # Кликаем для открытия выпадающего списка - open_button.click(force=True) - self.wait_for_timeout(300) - - # Вводим значение из выпадающего списка - dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value) - element = self.page.locator(dropdown_item_locator).first - - # Скроллим к элементу если нужно - self._scroll_until_element( - self.page.locator(RackLocators.DROPDOWN_LIST).first, - value - ) - self.wait_for_timeout(300) - element.click() - - logger.debug(f"Field '{field_name}' filled successfully") + return self.get_input_fields_locators(container_locator) def _scroll_until_element(self, locator: Locator, name: str) -> None: """ Скроллит список до тех пор, пока не перестанут подгружаться новые элементы. Args: - locator: Локатор элементов или строка с CSS/XPath. + locator (Locator): Локатор элементов или строка с CSS/XPath + name (str): Имя элемента для поиска """ loc = self.get_locator(locator) @@ -266,6 +258,21 @@ class RackObjectMaker(BaseComponent): self.wait_for_timeout(300) + def fill_rack_data(self, rack_data: RackData) -> None: + """ + Заполняет данные для создания стойки. + + Args: + rack_data (RackData): Данные стойки + """ + + logger.debug(f"Filling rack data: {rack_data.name}") + + self._fill_text_fields(rack_data) + self._fill_combobox_fields(rack_data) + + logger.debug("Rack data filled successfully") + # Проверки: def check_rack_fields_presence(self) -> None: diff --git a/components_derived/frames/create_child_element_frame.py b/components_derived/frames/create_child_element_frame.py index d7a8fe1..7a5fff3 100644 --- a/components_derived/frames/create_child_element_frame.py +++ b/components_derived/frames/create_child_element_frame.py @@ -1,7 +1,7 @@ """Модуль фрейма создания дочернего элемента.""" import re -from playwright.sync_api import expect, Page, Locator +from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.rack_locators import RackLocators from locators.selection_bar_locators import SelectionBarLocators @@ -23,7 +23,7 @@ class CreateChildElementFrame(BaseComponent): Инициализирует фрейм создания дочернего элемента. Args: - page: Экземпляр страницы Playwright + page (Page): Экземпляр страницы Playwright """ super().__init__(page) @@ -54,7 +54,7 @@ class CreateChildElementFrame(BaseComponent): Очищает combobox поле по его названию. Args: - field_name: Название поля для очистки + field_name (str): Название поля для очистки """ logger.debug(f"Clearing combobox field '{field_name}'...") @@ -114,13 +114,14 @@ class CreateChildElementFrame(BaseComponent): """ return self.selection_bar.get_selection_bar_title() + def is_field_filled(self, field_name: str, container_locator: Locator = None) -> bool: """ Проверяет, заполнено ли combobox или текстовое поле. Args: - field_name: Название поля для проверки - container_locator: Локатор контейнера формы (опционально) + field_name (str): Название поля для проверки + container_locator (Locator, optional): Локатор контейнера формы Returns: bool: True если поле заполнено, False в противном случае @@ -192,7 +193,12 @@ class CreateChildElementFrame(BaseComponent): logger.debug("Combobox menu is already open") def select_object_class(self, class_name: str) -> None: - """Выбирает класс объекта из выпадающего списка.""" + """ + Выбирает класс объекта из выпадающего списка. + + Args: + class_name (str): Название класса объекта для выбора + """ logger.debug(f"Selecting object class: '{class_name}'...") @@ -214,7 +220,11 @@ class CreateChildElementFrame(BaseComponent): Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). Args: - field_name: Название поля для проверки + field_name (str): Название поля для проверки + + Raises: + ValueError: Если поле не найдено в форме + AssertionError: Если поле не подсвечено ошибкой """ logger.debug(f"Checking field '{field_name}' for error highlighting...") @@ -247,7 +257,11 @@ class CreateChildElementFrame(BaseComponent): Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). Args: - field_name: Название поля для проверки + field_name (str): Название поля для проверки + + Raises: + ValueError: Если поле не найдено в форме + AssertionError: Если поле подсвечено ошибкой """ logger.debug(f"Checking field '{field_name}' for absence of error highlighting...") @@ -280,7 +294,10 @@ class CreateChildElementFrame(BaseComponent): Проверяет что выбран указанный класс объекта. Args: - expected_class: Ожидаемый выбранный класс объекта + expected_class (str): Ожидаемый выбранный класс объекта + + Raises: + AssertionError: Если выбранный класс не соответствует ожидаемому """ logger.debug(f"Checking selected object class: '{expected_class}'...") @@ -306,7 +323,10 @@ class CreateChildElementFrame(BaseComponent): Проверяет заголовок тулбара. Args: - expected_title: Ожидаемый заголовок тулбара + expected_title (str): Ожидаемый заголовок тулбара + + Raises: + AssertionError: Если заголовок не соответствует ожидаемому """ logger.debug(f"Checking toolbar title: '{expected_title}'...") diff --git a/pages/rack_page.py b/pages/rack_page.py index 6c6f28d..bdc0109 100644 --- a/pages/rack_page.py +++ b/pages/rack_page.py @@ -15,7 +15,7 @@ from pages.base_page import BasePage logger = get_logger("RACK_PAGE") -#logger.setLevel("INFO") +logger.setLevel("INFO") # Специфичные локаторы оставленые в основном коде PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i" @@ -31,7 +31,7 @@ class RackPage(BasePage): Инициализирует объект вкладки стойки. Args: - page: Экземпляр страницы Playwright + page (Page): Экземпляр страницы Playwright """ super().__init__(page) @@ -87,10 +87,10 @@ class RackPage(BasePage): # Проверяем тултип кнопки (может быть "Удалить" или "Remove") try: self.toolbar.check_button_tooltip("remove", "Удалить") - except: + except AssertionError: try: self.toolbar.check_button_tooltip("remove", "Remove") - except: + except AssertionError: logger.debug("Could not verify tooltip text for remove button") # Кликаем на кнопку удаления @@ -100,12 +100,50 @@ class RackPage(BasePage): # Ожидаем появления диалога подтверждения self._handle_remove_confirmation_dialog() - def _handle_remove_confirmation_dialog(self) -> None: + def confirm_remove_dialog(self, confirm: bool = True) -> None: """ - Обрабатывает диалог подтверждения удаления. + Подтверждает или отклоняет удаление в диалоговом окне. + + Args: + confirm (bool): Если True - подтвердить удаление, если False - отменить """ - logger.debug("Handling remove confirmation dialog...") - self.confirm_remove_dialog(confirm=True) + 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]: """ @@ -124,7 +162,7 @@ class RackPage(BasePage): tab_elements.first.wait_for(state="visible", timeout=5000) total_count = tab_elements.count() - logger.debug(f"Total top tab elements found: {total_count}") + logger.debug("Total top tab elements found: %d", total_count) for i in range(total_count): element = tab_elements.nth(i) @@ -136,9 +174,9 @@ class RackPage(BasePage): tab_text = tab_text.strip() if tab_text and tab_text not in tabs: tabs.append(tab_text) - logger.debug(f"Top tab found: '{tab_text}'") + logger.debug("Top tab found: '%s'", tab_text) - logger.debug(f"Available top tabs found: {tabs}") + logger.debug("Available top tabs found: %s", tabs) return tabs def get_current_active_side(self) -> Optional[str]: @@ -210,13 +248,13 @@ class RackPage(BasePage): Переключается на указанную вкладку. Args: - tab_name: Название вкладки для переключения + tab_name (str): Название вкладки для переключения Raises: AssertionError: Если вкладка не найдена или недоступна """ - logger.debug(f"Switching to tab '{tab_name}'...") + logger.debug("Switching to tab '%s'...", tab_name) tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) @@ -224,7 +262,7 @@ class RackPage(BasePage): # Проверяем активность ДО клика if self.is_tab_active(tab_name): - logger.debug(f"Tab '{tab_name}' is already active") + logger.debug("Tab '%s' is already active", tab_name) return # Находим первую видимую вкладку с нужным именем @@ -238,7 +276,7 @@ class RackPage(BasePage): assert target_tab is not None, f"No visible/available tab '{tab_name}' found" # Кликаем на вкладку - logger.debug(f"Clicking on tab '{tab_name}'...") + logger.debug("Clicking on tab '%s'...", tab_name) target_tab.click() # Ждем изменения активной вкладки @@ -272,21 +310,21 @@ class RackPage(BasePage): if main_container.count() == 0: logger.warning("Main rack container not found") else: - logger.debug(f"Main rack container found (count: {main_container.count()})") + logger.debug("Main rack container found (count: %d)", main_container.count()) expect(main_container.first).to_be_attached() # Проверяем наличие позиций юнитов unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) if unit_positions.count() > 0: - logger.debug(f"Unit positions found: {unit_positions.count()}") + logger.debug("Unit positions found: %d", unit_positions.count()) if unit_positions.first.text_content(): content = unit_positions.first.text_content().strip() - logger.debug(f"First position: {content}") + logger.debug("First position: %s", content) # Проверяем наличие кнопок добавления open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON) if open_buttons.count() > 0: - logger.debug(f"'add_circle' buttons found: {open_buttons.count()}") + logger.debug("'add_circle' buttons found: %d", open_buttons.count()) logger.debug("Rack interface loaded") @@ -306,8 +344,8 @@ class RackPage(BasePage): device_title = first_device.get_attribute("title") or "No title" logger.debug( - f"Devices found: {device_count} " - f"(first: ID={device_id}, Title={device_title})" + "Devices found: %d (first: ID=%s, Title=%s)", + device_count, device_id, device_title ) else: logger.debug("No devices detected") @@ -315,6 +353,9 @@ class RackPage(BasePage): def check_tab_switching(self) -> None: """ Проверяет переключение между вкладками стойки. + + Raises: + AssertionError: Если не удалось переключиться на все вкладки """ logger.debug("Testing rack tab switching functionality...") @@ -328,14 +369,14 @@ class RackPage(BasePage): "Сервисы" ] - logger.debug(f"Defined tabs to test: {defined_tabs}") + logger.debug("Defined tabs to test: %s", defined_tabs) successful_switches = 0 failed_switches = [] # Тестируем переключение на каждую определенную вкладку for tab_name in defined_tabs: - logger.debug(f"Testing switch to tab '{tab_name}'...") + logger.debug("Testing switch to tab '%s'...", tab_name) try: # Переключаемся на вкладку @@ -343,15 +384,15 @@ class RackPage(BasePage): # Проверяем, что вкладка активна if self.is_tab_active(tab_name): - logger.debug(f"Successfully switched to tab '{tab_name}'") + logger.debug("Successfully switched to tab '%s'", tab_name) successful_switches += 1 else: - logger.warning(f"Tab '{tab_name}' not active after switching") + logger.warning("Tab '%s' not active after switching", tab_name) failed_switches.append(f"Tab '{tab_name}' is not active after click") except (AssertionError, TimeoutError) as e: # Ловим только конкретные исключения, которые могут возникнуть при переключении вкладок - logger.error(f"Error switching to tab '{tab_name}': {e}") + logger.error("Error switching to tab '%s': %s", tab_name, e) failed_switches.append(f"Tab '{tab_name}' error: {str(e)}") # Небольшая пауза между переключениями @@ -359,12 +400,12 @@ class RackPage(BasePage): # Формируем итоговый отчет logger.debug("=== TAB SWITCHING RESULTS ===") - logger.debug(f"Successful switches: {successful_switches}/{len(defined_tabs)}") + logger.debug("Successful switches: %d/%d", successful_switches, len(defined_tabs)) if failed_switches: logger.debug("Failed switches:") for failure in failed_switches: - logger.debug(f" - {failure}") + logger.debug(" - %s", failure) # Требуем успешного переключения на все определенные вкладки assert successful_switches == len(defined_tabs), ( @@ -374,14 +415,14 @@ class RackPage(BasePage): f"Errors: {', '.join(failed_switches)}" ) - logger.debug(f"All {successful_switches} defined tabs successfully switched!") + logger.debug("All %d defined tabs successfully switched!", successful_switches) def is_tab_active(self, tab_name: str) -> bool: """ Проверяет, активна ли указанная вкладка. Args: - tab_name: Название вкладки для проверки + tab_name (str): Название вкладки для проверки Returns: bool: True если вкладка активна, False в противном случае @@ -393,10 +434,10 @@ class RackPage(BasePage): 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.debug(f"Tab '{tab_name}' is active (via active tab class)") + logger.debug("Tab '%s' is active (via active tab class)", tab_name) return True - logger.debug(f"Tab '{tab_name}' is not active") + logger.debug("Tab '%s' is not active", tab_name) return False def should_be_panel_header(self, expected_toolbar_title_items: list[str]) -> None: @@ -404,7 +445,7 @@ class RackPage(BasePage): Проверяет наличие и корректность заголовка панели. Args: - expected_toolbar_title_items: Ожидаемые элементы заголовка + expected_toolbar_title_items (list[str]): Ожидаемые элементы заголовка Raises: AssertionError: Если заголовок панели не соответствует ожиданиям @@ -426,7 +467,12 @@ class RackPage(BasePage): ) def should_be_rack_sides_displayed(self) -> None: - """Проверка отображения и структуры сторон стойки.""" + """ + Проверка отображения и структуры сторон стойки. + + Raises: + AssertionError: Если стороны стойки не отображаются корректно + """ logger.debug("Checking rack sides display and structure...") @@ -448,7 +494,7 @@ class RackPage(BasePage): # Проверяем, какая сторона активна по умолчанию current_active = self.get_current_active_side() - logger.debug(f"Current active side: {current_active}") + logger.debug("Current active side: %s", current_active) # Дополнительная проверка устройств self.check_physical_devices_presence() @@ -461,7 +507,7 @@ class RackPage(BasePage): # Возвращаемся на исходную активную сторону if current_active: - logger.debug(f"Returning to original active side: {current_active}") + logger.debug("Returning to original active side: %s", current_active) if current_active == "Лицевая сторона": front_side_button.click() else: @@ -469,7 +515,7 @@ class RackPage(BasePage): self.wait_for_timeout(1000) final_active = self.get_current_active_side() - logger.debug(f"Final active side: {final_active}") + logger.debug("Final active side: %s", final_active) logger.debug("All rack sides checks passed successfully") @@ -478,7 +524,7 @@ class RackPage(BasePage): Проверяет наличие и функциональность кнопок тулбара. Raises: - AssertionError: Если кнопки недоступны или подсказки неверны. + AssertionError: Если кнопки недоступны или подсказки неверны """ logger.debug("Checking toolbar buttons...") @@ -503,11 +549,12 @@ class RackPage(BasePage): self.toolbar.check_button_visibility("remove") self.toolbar.check_button_tooltip("remove", "Удалить") - def should_have_hide_rack_button(self) -> None: """ Проверка кнопки "Скрыть стойку". - Проверяет видимость, тултип, кликабельность и эффект скрытия стойки. + + Raises: + AssertionError: Если кнопка не отображается или не работает """ logger.debug("Checking 'Hide rack' button...") @@ -534,7 +581,9 @@ class RackPage(BasePage): def should_have_show_rack_button(self) -> None: """ Проверка кнопки "Показать стойку". - Проверяет наличие, тултип, кликабельность и эффект показа стойки. + + Raises: + AssertionError: Если кнопка не отображается или не работает """ logger.debug("Checking 'Show rack' button...") @@ -565,56 +614,56 @@ class RackPage(BasePage): Проверка структуры конкретной стороны стойки. Args: - side_name: Название стороны для логов + side_name (str): Название стороны для логов side_button: Локатор кнопки стороны Raises: AssertionError: Если структура стороны некорректна """ - logger.debug(f"Checking {side_name}...") + logger.debug("Checking %s...", side_name) # Логируем текущее состояние кнопки перед кликом button_classes = side_button.get_attribute("class") or "" - logger.debug(f"Button classes before click: {button_classes}") + logger.debug("Button classes before click: %s", button_classes) # Проверяем, активна ли уже эта сторона current_active = self.get_current_active_side() - logger.debug(f"Current active side (before click): '{current_active}'") + logger.debug("Current active side (before click): '%s'", current_active) if current_active == side_name: - logger.debug(f"{side_name} is already active") + logger.debug("%s is already active", side_name) else: # Если не активна, кликаем для переключения - logger.debug(f"Switching to {side_name}...") + logger.debug("Switching to %s...", side_name) side_button.click() # Даем время на перерисовку классов (увеличиваем время) self.wait_for_timeout(2500) # Проверяем классы после клика button_classes_after = side_button.get_attribute("class") or "" - logger.debug(f"Button classes after click: {button_classes_after}") + logger.debug("Button classes after click: %s", button_classes_after) # Проверяем, что нужная сторона стала активной active_side = self.get_current_active_side() - logger.debug(f"Active side after switching: '{active_side}'") + logger.debug("Active side after switching: '%s'", active_side) assert active_side == side_name, \ f"Wrong side is active: '{active_side}', expected: '{side_name}'" - logger.debug(f"{side_name} successfully activated") + logger.debug("%s successfully activated", side_name) # Проверяем позиции юнитов unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) total_positions = unit_positions.count() - logger.debug(f"Total unit positions: {total_positions}") + logger.debug("Total unit positions: %d", total_positions) assert total_positions > 0, f"No unit positions found on {side_name}" # Проверяем юниты all_units = self.page.locator(RackLocators.ALL_UNITS) all_units_count = all_units.count() units_per_side = all_units_count // 2 - logger.debug(f"Units on {side_name}: {units_per_side}") + logger.debug("Units on %s: %d", side_name, units_per_side) # Проверяем устройства devices = self.page.locator(RackLocators.DEVICE_ELEMENTS) @@ -631,77 +680,40 @@ class RackPage(BasePage): slots = first_device.locator(RackLocators.DEVICE_SLOTS) slot_count = slots.count() - logger.debug(f"Devices found: {device_count} (showing first)") - logger.debug(f" Device: ID={device_id}") - logger.debug(f" Title: {device_title}") - logger.debug(f" Classes: {device_classes}") - logger.debug(f" Slots: {slot_count}") + logger.debug("Devices found: %d (showing first)", device_count) + logger.debug(" Device: ID=%s", device_id) + logger.debug(" Title: %s", device_title) + logger.debug(" Classes: %s", device_classes) + logger.debug(" Slots: %d", slot_count) else: logger.debug("No devices detected") - logger.debug(f"{side_name} check completed successfully") + 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: """ Ожидает активации вкладки. Args: - tab_name: Название вкладки для ожидания - timeout: Время ожидания в миллисекундах + tab_name (str): Название вкладки для ожидания + timeout (int, optional): Время ожидания в миллисекундах, по умолчанию 5000 Raises: AssertionError: Если вкладка не активирована в течение таймаута """ - logger.debug(f"Waiting for tab '{tab_name}' activation...") + logger.debug("Waiting for tab '%s' activation...", 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.debug(f"Tab '{tab_name}' successfully activated") + logger.debug("Tab '%s' successfully activated", tab_name) return self.wait_for_timeout(100) assert False, f"Tab '{tab_name}' not activated within {timeout}ms" - - def confirm_remove_dialog(self, confirm: bool = True) -> None: - """ - Подтверждает или отклоняет удаление в диалоговом окне. - """ - 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(f"Dialog text: {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, f"Button not found with selector" - - # Кликаем на кнопку - logger.debug(f"Clicking button with text: {button.first.text_content()}") - button.first.click() - self.wait_for_timeout(2000) - - # Проверяем, что диалог закрылся - self.wait_for_timeout(1000) - - logger.debug("Remove confirmation completed") - diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py index 76aceaf..a883483 100644 --- a/tests/e2e/create_elements/test_create_rack_element.py +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -15,7 +15,7 @@ from pages.rack_page import RackPage logger = get_logger("CREATE_RACK_ELEMENT_TEST") -#logger.setLevel("INFO") +logger.setLevel("INFO") # @pytest.mark.smoke class TestCreateRackElement: @@ -26,7 +26,6 @@ class TestCreateRackElement: 2. test_create_rack_child_element: Проверяет создание дочернего элемента типа 'Стойка' 3. test_create_rack_with_duplicate_name: Проверяет создание стойки с дублирующимся именем 4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки - 5. test_delete_created_rack: Тест удаления созданной стойки """ # Инициализируем атрибуты @@ -57,87 +56,57 @@ class TestCreateRackElement: # Создаем экземпляр страницы локации self.location_page = LocationPage(browser) - @pytest.fixture(scope="function", autouse=True) - def rack_cleanup_fixture(self, request, browser: Page): - """ - Фикстура для удаления статических тестовых стоек после тестов. - Удаляет стойки, которые создаются в тестах. - """ - # Список статических стоек, которые создаются в тестах - static_racks = [ - "Test-Rack-01", - "Test-Rack-Duplicate", - "Test-Rack-Required-Final", - "Test-Rack-Delete" - ] + @pytest.fixture + def cleanup_racks(self, browser: Page): + """Фикстура для очистки созданных стоек.""" + # Список для хранения созданных в тесте стоек + created_racks = [] - yield + yield created_racks # После завершения теста удаляем созданные стойки - logger.debug("Cleaning up test racks...") + if created_racks: + logger.debug(f"Cleaning up racks: {created_racks}") - self.main_page.wait_for_timeout(500) - self.main_page.click_subpanel_item("test-zone") - self.main_page.wait_for_timeout(1000) + self.main_page.wait_for_timeout(500) + self.main_page.click_subpanel_item("test-zone") + self.main_page.wait_for_timeout(1000) - # Удаляем каждую статическую стойку если она существует - for rack_name in static_racks: - # Проверяем существование стойки - if self._check_rack_existance(browser, rack_name): - logger.debug(f"Deleting rack '{rack_name}'...") + # Удаляем каждую стойку если она существует + for rack_name in created_racks: + # Проверяем существование стойки + if self._check_rack_existance(browser, rack_name): + logger.debug(f"Deleting rack '{rack_name}'...") - # Переходим на страницу стойки для удаления - self.main_page.click_subpanel_item(rack_name, parent="test-zone") - self.main_page.wait_for_timeout(1000) + # Переходим на страницу стойки для удаления + self.main_page.click_subpanel_item(rack_name, parent="test-zone") + self.main_page.wait_for_timeout(1000) - # Удаляем стойку - self._delete_rack_from_context_menu(browser, rack_name) + # Удаляем стойку + self._delete_rack_from_context_menu(browser, rack_name) - # Проверяем удаление - self.main_page.click_subpanel_item("test-zone") - self.main_page.wait_for_timeout(500) + # Проверяем удаление + self.main_page.click_subpanel_item("test-zone") + self.main_page.wait_for_timeout(500) - # Дополнительная проверка удаления - rack_still_exists = self._check_rack_existance(browser, rack_name) - if rack_still_exists: - logger.error(f"Rack '{rack_name}' still exists after deletion attempt") + # Дополнительная проверка удаления + rack_still_exists = self._check_rack_existance(browser, rack_name) + if rack_still_exists: + logger.error(f"Rack '{rack_name}' still exists after deletion attempt") - logger.debug("Test racks cleanup completed") + logger.debug("Racks cleanup completed") + else: + logger.debug("No racks to cleanup") - #@pytest.mark.develop - def test_create_rack_content(self, browser: Page) -> None: - """Тест создания дочернего элемента типа 'Стойка'.""" - # Проверяем что кнопка "Создать" доступна - self.location_page.should_be_toolbar_buttons() + def _create_rack(self, browser: Page, rack_name: str) -> None: + """Создает стойку. - # Нажимаем кнопку "Создать" на тулбаре - self.location_page.click_create_button() - - # Создаем фрейм создания дочернего элемента - create_child_frame = CreateChildElementFrame(browser) - - # Нажимаем на плашку "Класс объекта учета" - create_child_frame.open_object_class_combobox() - - # Из выпадающего меню выбираем пункт "Стойка" - create_child_frame.select_object_class("Стойка") - - # Открывается набор плашек для задания параметров стойки - rack_maker = RackObjectMaker(browser) - - # Проверяем заголовок формы создания - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - - # Проверяем что после выбора 'Стойка' появляются специфичные поля - rack_maker.check_rack_fields_presence() - logger.debug("Rack-specific fields are displayed correctly") - - create_child_frame.should_be_toolbar_buttons() - - #@pytest.mark.develop - def test_create_rack_child_element(self, browser: Page) -> None: - """Тест создания дочернего элемента типа 'Стойка'.""" + Args: + browser: Страница Playwright + rack_name: Имя стойки для создания + """ + logger.debug(f"Creating rack with name '{rack_name}'") # Нажимаем кнопку "Создать" на тулбаре self.location_page.click_create_button() @@ -156,151 +125,71 @@ class TestCreateRackElement: # Создаем объект данных стойки rack_data = RackData( - name="Test-Rack-01", + name=rack_name, height="42", - depth="1000", - serial="TEST123456", - inventory="INV-001", - comment="Тестовая стойка для автоматизации", - state="Введен в эксплуатацию", - cable_entry="сверху" + depth="1000" ) - # Сохраняем имя стойки в переменную - rack_name = rack_data.name - # Заполняем данные стойки rack_maker.fill_rack_data(rack_data) - # Нажимаем кнопку "Добавить" + # Нажимаем кнопку создания create_child_frame.click_add_button() - # 1. Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача) - # Проверяем наличие alert-окна + def _delete_rack_from_context_menu(self, browser: Page, rack_name: str) -> None: + """Удаляет стойку через контекстное меню. + + Args: + browser: Страница Playwright + rack_name: Имя стойки для удаления + """ + logger.debug(f"Deleting rack '{rack_name}' from context menu...") + + # 1. Находим элемент стойки в навигационной панели + rack_element = browser.locator(NavigationPanelLocators.TREEVIEW).get_by_text(rack_name, exact=True).first + + # Прокручиваем до элемента если нужно + rack_element.scroll_into_view_if_needed() + self.main_page.wait_for_timeout(500) + + # 2. Проверяем и нажимаем кнопку "Изменить" + logger.debug("Step 1: Clicking 'Edit' button...") + rack_page = RackPage(browser) + + # Проверяем видимость кнопки + rack_page.toolbar.check_button_visibility("edit") + + # Проверяем тултип кнопки + rack_page.toolbar.check_button_tooltip("edit", "Изменить") + + # Кликаем на кнопку "Изменить" + rack_page.toolbar.get_button_by_name("edit").click() + + logger.debug("Edit button clicked, waiting for edit form...") + + # 3. Используем метод click_remove_button, который обрабатывает весь процесс удаления + # включая диалог подтверждения + logger.debug("Clicking remove button...") + rack_page.click_remove_button() + + # 4. Проверяем уведомление об успешном удалении - требуется создать разработчику (заведена задача) + # Создаем экземпляр фрейма для доступа к alert компоненту + # create_child_frame = CreateChildElementFrame(browser) + + # Проверяем наличие любого alert-окна (не обязательно точного текста) # create_child_frame.alert.check_alert_presence("") - # Получаем текст alert + # Получаем текст alert, чтобы убедиться что удаление прошло успешно # alert_text = create_child_frame.alert.get_text() - # logger.debug(f"Alert text after creation: {alert_text}") + # logger.debug(f"Alert text after deletion: {alert_text}") - # Проверяем, что в тексте есть указание на успешное создание - # assert "создан" in alert_text.lower() or "успешно" in alert_text.lower() - # assert final_rack_name in alert_text + # Проверяем что в тексте есть указание на успешное удаление + # assert "удален" in alert_text.lower() or "успешно" in alert_text.lower() # Закрываем alert # create_child_frame.alert.close_alert() - # 2. Проверяем, что стойка создана и отображается - logger.debug(f"Verifying that rack '{rack_name}' was created...") - - # Обновляем навигационную панель - self.main_page.click_main_navigation_panel_item("test-zone") - - # Проверяем существование стойки в навигационной панели - rack_exists = self._check_rack_existance(browser, rack_name) - assert rack_exists, f"Rack '{rack_name}' should be visible in navigation panel after creation" - - logger.debug(f"Rack '{rack_name}' is visible in navigation panel") - - # 3. Очистка: Удаление созданной стойки - logger.debug(f"Cleaning up: deleting test rack '{rack_name}'...") - - # Переходим на страницу стойки для удаления - self.main_page.click_subpanel_item(rack_name, parent="test-zone") - self.main_page.wait_for_timeout(1000) - - # Удаляем стойку - self._delete_rack_from_context_menu(browser, rack_name) - - # Проверяем, что стойка удалена - self.main_page.wait_for_timeout(2000) - rack_still_exists = self._check_rack_existance(browser, rack_name) - assert not rack_still_exists, f"Rack '{rack_name}' should be deleted" - logger.debug(f"Test rack '{rack_name}' successfully cleaned up") - - logger.debug("Test for creating 'Rack' child element completed successfully") - - @pytest.mark.develop - def test_create_rack_with_duplicate_name(self, browser: Page) -> None: - """ - Тест создания стойки с уже существующим именем. - - Проверяет, что система корректно обрабатывает попытку создания - стойки с именем, которое уже используется. - """ - - logger.debug("Starting test for creating rack with duplicate name") - - rack_name = "Test-Rack-Duplicate" - - # Проверяем, существует ли уже стойка с таким именем - if not self._check_rack_existance(browser, rack_name): - logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.") - self._create_rack(browser, rack_name) - logger.debug(f"First rack with name '{rack_name}' created successfully") - else: - logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one") - - # Создаем вторую стойку с тем же именем - logger.debug(f"Attempting to create second rack with name '{rack_name}'") - - # Нажимаем кнопку "Создать" на тулбаре - self.location_page.click_create_button() - - # Создаем фрейм создания дочернего элемента - create_child_frame = CreateChildElementFrame(browser) - - # Нажимаем на плашку "Класс объекта учета" - create_child_frame.open_object_class_combobox() - - # Из выпадающего меню выбираем пункт "Стойка" - create_child_frame.select_object_class("Стойка") - - # Открывается набор плашек для задания параметров стойки - rack_maker = RackObjectMaker(browser) - - # Создаем объект данных для второй стойки - rack_data = RackData( - name=rack_name, - height="42", - depth="450" - ) - - # Пытаемся создать вторую стойку с тем же именем - rack_maker.fill_rack_data(rack_data) - - # Нажимаем кнопку создания - create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(2000) - - # Проверяем наличие alert-окна с сообщением о дублирующемся имени - expected_alert_text = f"Имя {rack_name} уже используется" - create_child_frame.alert.check_alert_presence(expected_alert_text) - - # Проверяем, что остались на странице создания (стойка не создана) - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - - # Закрываем alert-окно с помощью кнопки закрытия - create_child_frame.wait_for_timeout(2000) - create_child_frame.alert.close_alert_by_text(expected_alert_text) - - # 3. Очистка: Удаление созданной стойки - logger.debug(f"Cleaning up: deleting test rack '{rack_name}'...") - - # Переходим на страницу стойки для удаления - self.main_page.click_subpanel_item(rack_name, parent="test-zone") - self.main_page.wait_for_timeout(1000) - - # Удаляем стойку - self._delete_rack_from_context_menu(browser, rack_name) - - # Проверяем, что стойка удалена - self.main_page.wait_for_timeout(2000) - rack_still_exists = self._check_rack_existance(browser, rack_name) - assert not rack_still_exists, f"Rack '{rack_name}' should be deleted" - logger.debug(f"Test rack '{rack_name}' successfully cleaned up") - - logger.debug("System prevented creating rack with duplicate name") + logger.debug("Rack deletion completed") def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data): """Выполняет один тест валидации обязательных полей. @@ -310,7 +199,6 @@ class TestCreateRackElement: rack_maker: Объект для работы со стойкой test_data: Словарь с данными теста """ - # Распаковываем данные теста name_value = test_data["name"] height_value = test_data["height"] @@ -321,7 +209,8 @@ class TestCreateRackElement: # Получаем контейнер формы container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) - logger.debug(f"Available fields: {list(create_child_frame.get_input_fields_locators(container_locator).keys())}") + logger.debug(f"Available fields:\ + {list(create_child_frame.get_input_fields_locators(container_locator).keys())}") # Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено logger.debug("Checking field: Глубина (мм)") @@ -392,16 +281,176 @@ class TestCreateRackElement: create_child_frame.check_toolbar_title('Создать дочерний элемент в') logger.debug("Test completed successfully") - #@pytest.mark.develop - def test_required_fields_validation(self, browser: Page) -> None: + + def test_create_rack_child_element(self, browser: Page, cleanup_racks) -> None: + """Тест создания дочернего элемента типа 'Стойка'.""" + # Нажимаем кнопку "Создать" на тулбаре + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) + + # Нажимаем на плашку "Класс объекта учета" + create_child_frame.open_object_class_combobox() + + # Из выпадающего меню выбираем пункт "Стойка" + create_child_frame.select_object_class("Стойка") + + # Открывается набор плашек для задания параметров стойки + rack_maker = RackObjectMaker(browser) + + # Создаем объект данных стойки + rack_data = RackData( + name="Test-Rack-01", + height="42", + depth="1000", + serial="TEST123456", + inventory="INV-001", + comment="Тестовая стойка для автоматизации", + state="Введен в эксплуатацию", + cable_entry="сверху" + ) + + # Сохраняем имя стойки в переменную + rack_name = rack_data.name + cleanup_racks.append(rack_name) + + # Заполняем данные стойки + rack_maker.fill_rack_data(rack_data) + + # Нажимаем кнопку "Добавить" + create_child_frame.click_add_button() + + # 1. Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача) + # Проверяем наличие alert-окна + # create_child_frame.alert.check_alert_presence("") + + # Получаем текст alert + # alert_text = create_child_frame.alert.get_text() + # logger.debug(f"Alert text after creation: {alert_text}") + + # Проверяем, что в тексте есть указание на успешное создание + # assert "создан" in alert_text.lower() or "успешно" in alert_text.lower() + # assert final_rack_name in alert_text + + # Закрываем alert + # create_child_frame.alert.close_alert() + + # 2. Проверяем, что стойка создана и отображается + logger.debug(f"Verifying that rack '{rack_name}' was created...") + + # Обновляем навигационную панель + self.main_page.click_main_navigation_panel_item("test-zone") + + # Проверяем существование стойки в навигационной панели + rack_exists = self._check_rack_existance(browser, rack_name) + assert rack_exists, f"Rack '{rack_name}' should be visible in navigation panel after creation" + + logger.debug(f"Rack '{rack_name}' is visible in navigation panel") + + logger.debug("Test for creating 'Rack' child element completed successfully") + + def test_create_rack_content(self, browser: Page) -> None: + """Тест проверки содержимого формы создания стойки.""" + # Проверяем что кнопка "Создать" доступна + self.location_page.should_be_toolbar_buttons() + + # Нажимаем кнопку "Создать" на тулбаре + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) + + # Нажимаем на плашку "Класс объекта учета" + create_child_frame.open_object_class_combobox() + + # Из выпадающего меню выбираем пункт "Стойка" + create_child_frame.select_object_class("Стойка") + + # Открывается набор плашек для задания параметров стойки + rack_maker = RackObjectMaker(browser) + + # Проверяем заголовок формы создания + create_child_frame.check_toolbar_title('Создать дочерний элемент в') + + # Проверяем что после выбора 'Стойка' появляются специфичные поля + rack_maker.check_rack_fields_presence() + logger.debug("Rack-specific fields are displayed correctly") + + create_child_frame.should_be_toolbar_buttons() + + def test_create_rack_with_duplicate_name(self, browser: Page, cleanup_racks) -> None: + """Тест создания стойки с уже существующим именем. + + Проверяет, что система корректно обрабатывает попытку создания + стойки с именем, которое уже используется. """ - Тест проверки обязательных полей при создании стойки. + logger.debug("Starting test for creating rack with duplicate name") + + rack_name = "Test-Rack-Duplicate" + + # Проверяем, существует ли уже стойка с таким именем + if not self._check_rack_existance(browser, rack_name): + logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.") + self._create_rack(browser, rack_name) + logger.debug(f"First rack with name '{rack_name}' created successfully") + # Добавляем стойку в список для очистки + cleanup_racks.append(rack_name) + else: + logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one") + + # Создаем вторую стойку с тем же именем + logger.debug(f"Attempting to create second rack with name '{rack_name}'") + + # Нажимаем кнопку "Создать" на тулбаре + self.location_page.click_create_button() + + # Создаем фрейм создания дочернего элемента + create_child_frame = CreateChildElementFrame(browser) + + # Нажимаем на плашку "Класс объекта учета" + create_child_frame.open_object_class_combobox() + + # Из выпадающего меню выбираем пункт "Стойка" + create_child_frame.select_object_class("Стойка") + + # Открывается набор плашек для задания параметров стойки + rack_maker = RackObjectMaker(browser) + + # Создаем объект данных для второй стойки + rack_data = RackData( + name=rack_name, + height="42", + depth="450" + ) + + # Пытаемся создать вторую стойку с тем же именем + rack_maker.fill_rack_data(rack_data) + + # Нажимаем кнопку создания + create_child_frame.click_add_button() + create_child_frame.wait_for_timeout(2000) + + # Проверяем наличие alert-окна с сообщением о дублирующемся имени + expected_alert_text = f"Имя {rack_name} уже используется" + create_child_frame.alert.check_alert_presence(expected_alert_text) + + # Проверяем, что остались на странице создания (стойка не создана) + create_child_frame.check_toolbar_title('Создать дочерний элемент в') + + # Закрываем alert-окно с помощью кнопки закрытия + create_child_frame.wait_for_timeout(2000) + create_child_frame.alert.close_alert_by_text(expected_alert_text) + + logger.debug("System prevented creating rack with duplicate name") + + def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None: + """Тест проверки обязательных полей при создании стойки. Проверяет, что система корректно валидирует обязательные поля: - Поле 'Высота в юнитах' должно быть заполнено - Поле 'Глубина (мм)' должно быть заполнено """ - # Текст сообщения alert-окна expected_alert_text_height = "поле Высота в юнитах должно быть заполнено" expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено" @@ -459,15 +508,15 @@ class TestCreateRackElement: for test_case in test_cases: logger.debug(test_case["name"]) self._perform_required_fields_test( - create_child_frame, rack_maker, test_case["data"] - ) + create_child_frame, rack_maker, test_case["data"]) logger.debug("System prevented creating rack with invalid required fields") # 4. Тест: Заполняем все обязательные поля logger.debug("Test 4: All required fields are filled") # Генерируем уникальное имя для финального теста - final_rack_name = "Test-Rack-Required-Final" + final_rack_name = "Test-Rack-Required-04" + cleanup_racks.append(final_rack_name) # **ВАЖНО: Очищаем поля перед заполнением** logger.debug("Clearing fields before filling...") @@ -538,147 +587,20 @@ class TestCreateRackElement: # Закрываем alert # create_child_frame.alert.close_alert() - # Переходим на страницу стойки для удаления - self.main_page.click_subpanel_item(final_rack_name, parent="test-zone") - self.main_page.wait_for_timeout(500) - - # Удаляем стойку - self._delete_rack_from_context_menu(browser, final_rack_name) - - # Проверяем, что стойка удалена - self.main_page.wait_for_timeout(1000) - rack_still_exists = self._check_rack_existance(browser, final_rack_name) - assert not rack_still_exists, f"Rack '{final_rack_name}' should be deleted" - logger.debug("Required fields validation test completed successfully") - #@pytest.mark.develop - def test_delete_created_rack(self, browser: Page) -> None: - """ - Тест удаления созданной стойки. + # Вспомогательные методы проверки - Сценарий: - 1. Создаем стойку с уникальным именем - 2. Проверяем её существование - 3. Переходим к стойке и удаляем её - 4. Проверяем, что стойка больше не существует - """ - - logger.debug("Starting test for deleting created rack") - - # Генерируем уникальное имя для стойки - rack_name = f"Test-Rack-Delete" - - logger.debug(f"Test rack name: {rack_name}") - - # 1. Создаем стойку - logger.debug("Step 1: Creating rack...") - self._create_rack(browser, rack_name) - - logger.debug(f"Rack '{rack_name}' created successfully") - - # 2. Проверяем существование стойки - logger.debug("Step 2: Verifying rack existence...") - - # Используем существующий метод _check_rack_existance - rack_exists = self._check_rack_existance(browser, rack_name) - - assert rack_exists, f"Rack '{rack_name}' not found after creation" - logger.debug(f"Rack '{rack_name}' exists - verified with _check_rack_existance method") - - # Кликаем на стойку для перехода - self.main_page.click_subpanel_item(rack_name, parent="test-zone") - - # 3. Проверяем, что мы на странице стойки - logger.debug("Step 3: Checking we're on rack page...") - - expected_toolbar_subtitles = [ - "test-zone", - 'chevron_right', - rack_name - ] - - rack_page = RackPage(browser) - rack_page.should_be_panel_header(expected_toolbar_subtitles) - - # 4. Открываем контекстное меню и удаляем стойку - logger.debug("Step 4: Deleting rack...") - self._delete_rack_from_context_menu(browser, rack_name) - - # 5. Проверяем, что стойка удалена с использованием существующего метода - logger.debug("Step 5: Verifying rack deletion...") - - # Переходим обратно в test-zone для проверки - self.main_page.click_subpanel_item("test-zone") - - # Используем существующий метод для проверки отсутствия стойки - rack_still_exists = self._check_rack_existance(browser, rack_name) - - assert not rack_still_exists, f"Rack '{rack_name}' still exists after deletion" - - logger.debug(f"Rack '{rack_name}' successfully deleted - verified with _check_rack_existance method") - logger.debug("Rack deletion test completed successfully") - - def _delete_rack_from_context_menu(self, browser: Page, rack_name: str) -> None: - """ - Удаляет стойку через контекстное меню. + def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: + """Проверяет существование стойки. Args: browser: Страница Playwright - rack_name: Имя стойки для удаления + rack_name: Имя стойки для проверки + + Returns: + bool: True если стойка существует, False в противном случае """ - - logger.debug(f"Deleting rack '{rack_name}' from context menu...") - - # 1. Находим элемент стойки в навигационной панели - rack_element = browser.locator(NavigationPanelLocators.TREEVIEW).get_by_text(rack_name, exact=True).first - - # Прокручиваем до элемента если нужно - rack_element.scroll_into_view_if_needed() - self.main_page.wait_for_timeout(500) - - # 2. Проверяем и нажимаем кнопку "Изменить" - logger.debug("Step 1: Clicking 'Edit' button...") - rack_page = RackPage(browser) - - # Проверяем видимость кнопки - rack_page.toolbar.check_button_visibility("edit") - - # Проверяем тултип кнопки - rack_page.toolbar.check_button_tooltip("edit", "Изменить") - - # Кликаем на кнопку "Изменить" - rack_page.toolbar.get_button_by_name("edit").click() - - logger.debug("Edit button clicked, waiting for edit form...") - - # 3. Используем метод click_remove_button, который обрабатывает весь процесс удаления - # включая диалог подтверждения - logger.debug("Clicking remove button...") - rack_page.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() - - logger.debug("Rack deletion completed") - - def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: - """Проверяет существование стойки.""" - logger.debug(f"Checking existence of rack with name '{rack_name}'") # Обновляем навигационную панель @@ -697,39 +619,3 @@ class TestCreateRackElement: logger.debug(f"Rack with name '{rack_name}' not found") return False - - def _create_rack(self, browser: Page, rack_name: str) -> None: - """Создает стойку.""" - - logger.debug(f"Creating rack with name '{rack_name}'") - - # Переходим обратно к созданию новой стойки - #self.main_page.click_main_navigation_panel_item("test-zone") - - # Нажимаем кнопку "Создать" на тулбаре - self.location_page.click_create_button() - - # Создаем фрейм создания дочернего элемента - create_child_frame = CreateChildElementFrame(browser) - - # Нажимаем на плашку "Класс объекта учета" - create_child_frame.open_object_class_combobox() - - # Из выпадающего меню выбираем пункт "Стойка" - create_child_frame.select_object_class("Стойка") - - # Открывается набор плашек для задания параметров стойки - rack_maker = RackObjectMaker(browser) - - # Создаем объект данных стойки - rack_data = RackData( - name=rack_name, - height="42", - depth="1000" - ) - - # Заполняем данные стойки - rack_maker.fill_rack_data(rack_data) - - # Нажимаем кнопку создания - create_child_frame.click_add_button()