diff --git a/components_derived/accounting_objects/rack_maker.py b/components_derived/accounting_objects/rack_maker.py index 64b9c17..17cd8c9 100644 --- a/components_derived/accounting_objects/rack_maker.py +++ b/components_derived/accounting_objects/rack_maker.py @@ -35,114 +35,62 @@ 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 - def _fill_text_fields(self, rack_data: RackData) -> None: - """Заполняет текстовые поля.""" + # Скроллим к элементу если нужно + self._scroll_until_element( + self.page.locator(RackLocators.DROPDOWN_LIST).first, + value + ) + self.wait_for_timeout(300) + element.click() - logger.debug("Filling text fields...") - - # Получаем все поля формы - fields_locators = self._get_form_fields() - - logger.debug(f"Available text fields: {list(fields_locators.keys())}") - - def clear_and_fill(field_name: str, value: str): - """Очищает поле и заполняет его значением.""" - - if not value: - logger.debug(f"Skipping empty value for field '{field_name}'") - return - - # Получаем контейнер поля - field_container = fields_locators.get(field_name) - - if not field_container: - logger.warning(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}") - return - - # Находим input внутри контейнера - input_field = field_container.locator("input").first - - if input_field.count() == 0: - logger.warning(f"Input element not found in container for field '{field_name}'") - return - - # Проверяем видимость - if not input_field.is_visible(): - logger.debug(f"Field '{field_name}' is not visible, scrolling into view...") - input_field.scroll_into_view_if_needed() - self.wait_for_timeout(500) - - # Проверяем, не disabled ли поле - is_disabled = input_field.get_attribute("disabled") - is_readonly = input_field.get_attribute("readonly") - - if is_disabled or is_readonly: - logger.warning(f"Field '{field_name}' is disabled or readonly") - return - - # Очищаем поле - input_field.click() - input_field.press("Control+A") - input_field.press("Backspace") - - # Заполняем значение - input_field.fill(value) - logger.debug(f"Filled '{field_name}': {value}") - - # Обязательные поля - if rack_data.name: - clear_and_fill("Имя", rack_data.name) - - # Опциональные поля - if rack_data.serial: - clear_and_fill("Серийный номер", rack_data.serial) - - if rack_data.inventory: - clear_and_fill("Инвентарный номер", rack_data.inventory) - - if rack_data.comment: - clear_and_fill("Комментарий", rack_data.comment) - - logger.debug("Text fields filled successfully") + logger.debug(f"Field '{field_name}' filled successfully") def _fill_combobox_fields(self, rack_data: RackData) -> None: """Заполняет combobox поля.""" @@ -180,59 +128,103 @@ class RackObjectMaker(BaseComponent): 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: - """ - Заполняет combobox поле. + def _fill_text_fields(self, rack_data: RackData) -> None: + """Заполняет текстовые поля.""" - Args: - field_name: Название поля - value: Значение для установки - fields_locators: Словарь с найденными полями формы + logger.debug("Filling text fields...") + + # Получаем все поля формы + fields_locators = self._get_form_fields() + + logger.debug(f"Available text fields: {list(fields_locators.keys())}") + + def clear_and_fill(field_name: str, value: str): + """Очищает поле и заполняет его значением.""" + + if not value: + logger.debug(f"Skipping empty value for field '{field_name}'") + return + + # Получаем контейнер поля + field_container = fields_locators.get(field_name) + + if not field_container: + logger.warning(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}") + return + + # Находим input внутри контейнера + input_field = field_container.locator("input").first + + if input_field.count() == 0: + logger.warning(f"Input element not found in container for field '{field_name}'") + return + + # Проверяем видимость + if not input_field.is_visible(): + logger.debug(f"Field '{field_name}' is not visible, scrolling into view...") + input_field.scroll_into_view_if_needed() + self.wait_for_timeout(300) + + # Проверяем, не disabled ли поле + is_disabled = input_field.get_attribute("disabled") + is_readonly = input_field.get_attribute("readonly") + + if is_disabled or is_readonly: + logger.warning(f"Field '{field_name}' is disabled or readonly") + return + + # Очищаем поле + input_field.click() + input_field.press("Control+A") + input_field.press("Backspace") + + # Заполняем значение + input_field.fill(value) + logger.debug(f"Filled '{field_name}': {value}") + + # Обязательные поля + if rack_data.name: + clear_and_fill("Имя", rack_data.name) + + # Опциональные поля + if rack_data.serial: + clear_and_fill("Серийный номер", rack_data.serial) + + if rack_data.inventory: + clear_and_fill("Инвентарный номер", rack_data.inventory) + + if rack_data.comment: + clear_and_fill("Комментарий", rack_data.comment) + + logger.debug("Text fields filled successfully") + + def _get_form_fields(self) -> dict: + """ + Получает все поля формы стойки. + + 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(500) - - # Проверяем видимость поля - 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(1000) - - # Вводим значение из выпадающего списка - 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(500) - 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 c5bd55e..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}'...") @@ -72,7 +72,7 @@ class CreateChildElementFrame(BaseComponent): # Прокручиваем до поля field_container.scroll_into_view_if_needed() - self.wait_for_timeout(500) + self.wait_for_timeout(300) # Проверяем видимость if not field_container.is_visible(): @@ -88,7 +88,7 @@ class CreateChildElementFrame(BaseComponent): # Если кнопка закрытия видима - кликаем на нее close_button.click(force=True) - self.wait_for_timeout(500) + self.wait_for_timeout(300) logger.debug(f"Combobox field '{field_name}' cleared using close button") else: logger.debug(f"Close button (i.mdi-close) not found for field '{field_name}'") @@ -115,6 +115,61 @@ 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 (str): Название поля для проверки + container_locator (Locator, optional): Локатор контейнера формы + + Returns: + bool: True если поле заполнено, False в противном случае + """ + + logger.debug(f"Checking if field '{field_name}' is filled...") + + # Если контейнер не передан, используем контейнер по умолчанию + if container_locator is None: + container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) + + # Получаем словарь всех полей формы + fields_locators = self.get_input_fields_locators(container_locator) + + if field_name not in fields_locators: + logger.debug(f"Field '{field_name}' not found in fields_locators") + return False + + # Получаем контейнер поля + field_container = fields_locators[field_name] + + if not field_container.is_visible(): + logger.debug(f"Field '{field_name}' not visible") + return False + + # Проверяем наличие выбранного значения через v-chip (чип выбранного значения в combobox) + selected_chip = field_container.locator(".v-chip").first + + # Проверяем наличие текста в поле + field_text = field_container.text_content() or "" + has_text = bool(field_text.strip()) + + # Проверяем наличие чипа + has_chip = selected_chip.count() > 0 and selected_chip.is_visible() + + # Для текстовых полей проверяем значение input + if not has_chip: + input_field = field_container.locator("input").first + if input_field.count() > 0: + input_value = input_field.input_value() or "" + has_input_value = bool(input_value.strip()) + logger.debug(f"Field '{field_name}' - has input value: {has_input_value}") + has_text = has_text or has_input_value + + logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}") + + return has_chip or has_text + def open_object_class_combobox(self) -> None: """Открывает выпадающий список combobox.""" @@ -138,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}'...") @@ -149,7 +209,7 @@ class CreateChildElementFrame(BaseComponent): self.selection_bar.select_value(class_name) # Даем время на применение выбора - self.wait_for_timeout(3000) + self.wait_for_timeout(300) logger.debug(f"Object class '{class_name}' successfully selected") @@ -160,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...") @@ -193,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...") @@ -226,12 +294,15 @@ class CreateChildElementFrame(BaseComponent): Проверяет что выбран указанный класс объекта. Args: - expected_class: Ожидаемый выбранный класс объекта + expected_class (str): Ожидаемый выбранный класс объекта + + Raises: + AssertionError: Если выбранный класс не соответствует ожидаемому """ logger.debug(f"Checking selected object class: '{expected_class}'...") - self.wait_for_timeout(1000) + self.wait_for_timeout(500) actual_class = self.get_selected_object_class() is_match = (expected_class.lower() in actual_class.lower() or @@ -252,7 +323,10 @@ class CreateChildElementFrame(BaseComponent): Проверяет заголовок тулбара. Args: - expected_title: Ожидаемый заголовок тулбара + expected_title (str): Ожидаемый заголовок тулбара + + Raises: + AssertionError: Если заголовок не соответствует ожидаемому """ logger.debug(f"Checking toolbar title: '{expected_title}'...") @@ -274,11 +348,9 @@ class CreateChildElementFrame(BaseComponent): Проверяет наличие и функциональность кнопок тулбара. """ - 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) + self.wait_for_timeout(500) diff --git a/locators/rack_locators.py b/locators/rack_locators.py index eb2abe9..214af2f 100644 --- a/locators/rack_locators.py +++ b/locators/rack_locators.py @@ -113,3 +113,16 @@ class RackLocators: # Кнопка "Показать стойку" SHOW_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and " "contains(@class, 'cabinet_hide_button_trigger_hide')]") + + # Кнопки тулбара стойки + TOOLBAR_REPLACE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__replace']" + TOOLBAR_DONE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__done']" + TOOLBAR_CLOSE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__close']" + TOOLBAR_REMOVE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__remove']" + + # Диалог удаления + REMOVE_DIALOG = "[data-testid='cabinet-bar__toolbar__dialog-remove']" + + # Кнопки подтверждения удаления + CONFIRM_REMOVE_YES_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__yes']" + CONFIRM_REMOVE_NO_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__no']" diff --git a/pages/rack_page.py b/pages/rack_page.py index f924c31..bdc0109 100644 --- a/pages/rack_page.py +++ b/pages/rack_page.py @@ -31,7 +31,7 @@ class RackPage(BasePage): Инициализирует объект вкладки стойки. Args: - page: Экземпляр страницы Playwright + page (Page): Экземпляр страницы Playwright """ super().__init__(page) @@ -48,13 +48,103 @@ class RackPage(BasePage): show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON) 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") + + # Кнопка "Сохранить" + 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") + + # Кнопка "Удалить" + 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") # Действия + 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]: """ Возвращает список доступных вкладок. @@ -72,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) @@ -84,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]: @@ -158,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)) @@ -172,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 # Находим первую видимую вкладку с нужным именем @@ -186,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() # Ждем изменения активной вкладки @@ -220,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") @@ -254,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") @@ -263,6 +353,9 @@ class RackPage(BasePage): def check_tab_switching(self) -> None: """ Проверяет переключение между вкладками стойки. + + Raises: + AssertionError: Если не удалось переключиться на все вкладки """ logger.debug("Testing rack tab switching functionality...") @@ -276,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: # Переключаемся на вкладку @@ -291,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)}") # Небольшая пауза между переключениями @@ -307,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), ( @@ -322,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 в противном случае @@ -341,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: @@ -352,7 +445,7 @@ class RackPage(BasePage): Проверяет наличие и корректность заголовка панели. Args: - expected_toolbar_title_items: Ожидаемые элементы заголовка + expected_toolbar_title_items (list[str]): Ожидаемые элементы заголовка Raises: AssertionError: Если заголовок панели не соответствует ожиданиям @@ -374,7 +467,12 @@ class RackPage(BasePage): ) def should_be_rack_sides_displayed(self) -> None: - """Проверка отображения и структуры сторон стойки.""" + """ + Проверка отображения и структуры сторон стойки. + + Raises: + AssertionError: Если стороны стойки не отображаются корректно + """ logger.debug("Checking rack sides display and structure...") @@ -396,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() @@ -409,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: @@ -417,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") @@ -426,19 +524,37 @@ class RackPage(BasePage): Проверяет наличие и функциональность кнопок тулбара. Raises: - AssertionError: Если кнопки недоступны или подсказки неверны. + AssertionError: Если кнопки недоступны или подсказки неверны """ logger.debug("Checking toolbar buttons...") + # Проверяем основные кнопки self.toolbar.check_button_visibility("edit") self.toolbar.check_button_tooltip("edit", "Изменить") + + # Кликаем на кнопку "Изменить" для проверки функциональности 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: """ Проверка кнопки "Скрыть стойку". - Проверяет видимость, тултип, кликабельность и эффект скрытия стойки. + + Raises: + AssertionError: Если кнопка не отображается или не работает """ logger.debug("Checking 'Hide rack' button...") @@ -465,7 +581,9 @@ class RackPage(BasePage): def should_have_show_rack_button(self) -> None: """ Проверка кнопки "Показать стойку". - Проверяет наличие, тултип, кликабельность и эффект показа стойки. + + Raises: + AssertionError: Если кнопка не отображается или не работает """ logger.debug("Checking 'Show rack' button...") @@ -496,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) @@ -562,34 +680,39 @@ 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) diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py index 318da95..a883483 100644 --- a/tests/e2e/create_elements/test_create_rack_element.py +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -10,6 +10,7 @@ from components_derived.frames.create_child_element_frame import CreateChildElem from pages.location_page import LocationPage from pages.login_page import LoginPage from pages.main_page import MainPage +from pages.rack_page import RackPage logger = get_logger("CREATE_RACK_ELEMENT_TEST") @@ -46,24 +47,66 @@ class TestCreateRackElement: # Мы на главной странице self.main_page = MainPage(browser) self.main_page.should_be_navigation_panel() - self.main_page.wait_for_timeout(2000) # Переходим к Объектам self.main_page.click_main_navigation_panel_item("Объекты") - self.main_page.wait_for_timeout(2000) - + self.main_page.wait_for_timeout(1000) self.main_page.click_main_navigation_panel_item("test-zone") - self.main_page.wait_for_timeout(2000) # Создаем экземпляр страницы локации self.location_page = LocationPage(browser) - #@pytest.mark.develop - def test_create_rack_content(self, browser: Page) -> None: - """Тест создания дочернего элемента типа 'Стойка'.""" + @pytest.fixture + def cleanup_racks(self, browser: Page): + """Фикстура для очистки созданных стоек.""" + # Список для хранения созданных в тесте стоек + created_racks = [] - # Проверяем что кнопка "Создать" доступна - self.location_page.should_be_toolbar_buttons() + yield created_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) + + # Удаляем каждую стойку если она существует + 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._delete_rack_from_context_menu(browser, rack_name) + + # Проверяем удаление + 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") + + logger.debug("Racks cleanup completed") + else: + logger.debug("No racks to cleanup") + + + def _create_rack(self, browser: Page, rack_name: str) -> None: + """Создает стойку. + + Args: + browser: Страница Playwright + rack_name: Имя стойки для создания + """ + logger.debug(f"Creating rack with name '{rack_name}'") # Нажимаем кнопку "Создать" на тулбаре self.location_page.click_create_button() @@ -80,19 +123,167 @@ class TestCreateRackElement: # Открывается набор плашек для задания параметров стойки 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() + + 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_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 _perform_required_fields_test(self, create_child_frame, rack_maker, test_data): + """Выполняет один тест валидации обязательных полей. + + Args: + create_child_frame: Фрейм создания дочернего элемента + rack_maker: Объект для работы со стойкой + test_data: Словарь с данными теста + """ + # Распаковываем данные теста + name_value = test_data["name"] + height_value = test_data["height"] + depth_value = test_data["depth"] + expected_alert_height = test_data["expected_alert_height"] + expected_alert_depth = test_data["expected_alert_depth"] + + # Получаем контейнер формы + 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("Checking field: Глубина (мм)") + if create_child_frame.is_field_filled("Глубина (мм)", container_locator): + logger.debug("Field 'Глубина (мм)' is filled, performing clearing") + create_child_frame.clear_combobox_field("Глубина (мм)") + logger.debug("Clearing completed for 'Глубина (мм)'") + else: + logger.debug("Field 'Глубина (мм)' is already empty, skipping clearing") + + # Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено + logger.debug("Checking field: Высота в юнитах") + if create_child_frame.is_field_filled("Высота в юнитах", container_locator): + logger.debug("Field 'Высота в юнитах' is filled, performing clearing") + create_child_frame.clear_combobox_field("Высота в юнитах") + logger.debug("Clearing completed for 'Высота в юнитах'") + else: + logger.debug("Field 'Высота в юнитах' is already empty, skipping clearing") + + # Создаем объект данных стойки + rack_data = RackData( + name=name_value, + height=height_value, + depth=depth_value + ) + + # Заполняем данные стойки + logger.debug(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'") + rack_maker.fill_rack_data(rack_data) + + # Нажимаем кнопку создания + logger.debug("Submitting form for validation") + create_child_frame.click_add_button() + create_child_frame.wait_for_timeout(500) + + # Проверяем валидацию полей + logger.debug("Checking validation results") + + # Обрабатываем alert-окна + if not height_value: + logger.debug("Expecting height validation alert") + create_child_frame.alert.check_alert_presence(expected_alert_height) + create_child_frame.alert.close_alert_by_text(expected_alert_height) + logger.debug("Height alert handled") + + if not depth_value: + logger.debug("Expecting depth validation alert") + create_child_frame.alert.check_alert_presence(expected_alert_depth) + create_child_frame.alert.close_alert_by_text(expected_alert_depth) + logger.debug("Depth alert handled") + + # Проверяем подсветку обязательных полей + if height_value: + create_child_frame.check_field_error_not_highlighted("Высота в юнитах") + logger.debug("Height field validation passed") + else: + create_child_frame.check_field_error_highlighted("Высота в юнитах") + logger.debug("Height field validation failed as expected") + + if depth_value: + create_child_frame.check_field_error_not_highlighted("Глубина (мм)") + logger.debug("Depth field validation passed") + else: + create_child_frame.check_field_error_highlighted("Глубина (мм)") + logger.debug("Depth field validation failed as expected") + + # Проверяем, что остались на странице создания create_child_frame.check_toolbar_title('Создать дочерний элемент в') + logger.debug("Test completed successfully") - # Проверяем что после выбора 'Стойка' появляются специфичные поля - 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: + def test_create_rack_child_element(self, browser: Page, cleanup_racks) -> None: """Тест создания дочернего элемента типа 'Стойка'.""" - # Нажимаем кнопку "Создать" на тулбаре self.location_page.click_create_button() @@ -120,33 +311,91 @@ class TestCreateRackElement: 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() - create_child_frame.wait_for_timeout(2000) + + # 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") - #@pytest.mark.develop - def test_create_rack_with_duplicate_name(self, browser: Page) -> None: - """ - Тест создания стойки с уже существующим именем. + 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-01" + 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") @@ -195,136 +444,13 @@ class TestCreateRackElement: logger.debug("System prevented creating rack with duplicate name") - def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data): - """Выполняет один тест валидации обязательных полей. - - Args: - create_child_frame: Фрейм создания дочернего элемента - rack_maker: Объект для работы со стойкой - test_data: Словарь с данными теста - """ - - # Распаковываем данные теста - name_value = test_data["name"] - height_value = test_data["height"] - depth_value = test_data["depth"] - expected_alert_height = test_data["expected_alert_height"] - expected_alert_depth = test_data["expected_alert_depth"] - - # Получаем контейнер формы - container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1) - fields_locators = create_child_frame.get_input_fields_locators(container_locator) - - logger.debug(f"Available fields: {list(fields_locators.keys())}") - - # Функция для проверки заполненности combobox поля - def is_field_filled(field_name: str) -> bool: - """Проверяет, заполнено ли combobox поле.""" - - if field_name not in fields_locators: - logger.debug(f"Field '{field_name}' not found in fields_locators") - return False - - # Получаем контейнер поля - field_container = fields_locators[field_name] - - if not field_container.is_visible(): - logger.debug(f"Field '{field_name}' not visible") - return False - - # Проверяем наличие выбранного значения через v-chip (чип выбранного значения в combobox) - selected_chip = field_container.locator(".v-chip").first - - # Проверяем наличие текста в поле - field_text = field_container.text_content() or "" - has_text = bool(field_text.strip()) - - # Проверяем наличие чипа - has_chip = selected_chip.count() > 0 and selected_chip.is_visible() - - logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}") - - return has_chip or has_text - - # Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено - logger.debug("Checking field: Глубина (мм)") - if is_field_filled("Глубина (мм)"): - logger.debug("Field 'Глубина (мм)' is filled, performing clearing") - create_child_frame.clear_combobox_field("Глубина (мм)") - logger.debug("Clearing completed for 'Глубина (мм)'") - else: - logger.debug("Field 'Глубина (мм)' is already empty, skipping clearing") - - # Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено - logger.debug("Checking field: Высота в юнитах") - if is_field_filled("Высота в юнитах"): - logger.debug("Field 'Высота в юнитах' is filled, performing clearing") - create_child_frame.clear_combobox_field("Высота в юнитах") - logger.debug("Clearing completed for 'Высота в юнитах'") - else: - logger.debug("Field 'Высота в юнитах' is already empty, skipping clearing") - - # Создаем объект данных стойки - rack_data = RackData( - name=name_value, - height=height_value, - depth=depth_value - ) - - # Заполняем данные стойки - logger.debug(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'") - rack_maker.fill_rack_data(rack_data) - - # Нажимаем кнопку создания - logger.debug("Submitting form for validation") - create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(1000) - - # Проверяем валидацию полей - logger.debug("Checking validation results") - - # Обрабатываем alert-окна - if not height_value: - logger.debug("Expecting height validation alert") - create_child_frame.alert.check_alert_presence(expected_alert_height) - create_child_frame.alert.close_alert_by_text(expected_alert_height) - logger.debug("Height alert handled") - - if not depth_value: - logger.debug("Expecting depth validation alert") - create_child_frame.alert.check_alert_presence(expected_alert_depth) - create_child_frame.alert.close_alert_by_text(expected_alert_depth) - logger.debug("Depth alert handled") - - # Проверяем подсветку обязательных полей - if height_value: - create_child_frame.check_field_error_not_highlighted("Высота в юнитах") - logger.debug("Height field validation passed") - else: - create_child_frame.check_field_error_highlighted("Высота в юнитах") - logger.debug("Height field validation failed as expected") - - if depth_value: - create_child_frame.check_field_error_not_highlighted("Глубина (мм)") - logger.debug("Depth field validation passed") - else: - create_child_frame.check_field_error_highlighted("Глубина (мм)") - logger.debug("Depth field validation failed as expected") - - # Проверяем, что остались на странице создания - 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_required_fields_validation(self, browser: Page, cleanup_racks) -> None: + """Тест проверки обязательных полей при создании стойки. Проверяет, что система корректно валидирует обязательные поля: - Поле 'Высота в юнитах' должно быть заполнено - Поле 'Глубина (мм)' должно быть заполнено """ - # Текст сообщения alert-окна expected_alert_text_height = "поле Высота в юнитах должно быть заполнено" expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено" @@ -347,9 +473,9 @@ class TestCreateRackElement: # Тестовые данные test_cases = [ { - "name": "Test 1: Creating rack with default field values", + "name": "Test 1: Required fields are not filled", "data": { - "name": "", + "name": "Test-Rack-Required-01", "height": "", "depth": "", "expected_alert_height": expected_alert_text_height, @@ -357,19 +483,9 @@ class TestCreateRackElement: } }, { - "name": "Test 2: Required fields are not filled", + "name": "Test 2: Only 'Height in units' field is filled", "data": { - "name": "", - "height": "", - "depth": "", - "expected_alert_height": expected_alert_text_height, - "expected_alert_depth": expected_alert_text_depth - } - }, - { - "name": "Test 3: Only 'Height in units' field is filled", - "data": { - "name": "", + "name": "Test-Rack-Required-02", "height": "42", "depth": "", "expected_alert_height": expected_alert_text_height, @@ -377,9 +493,9 @@ class TestCreateRackElement: } }, { - "name": "Test 4: Only 'Depth (mm)' field is filled", + "name": "Test 3: Only 'Depth (mm)' field is filled", "data": { - "name": "", + "name": "Test-Rack-Required-03", "height": "", "depth": "1000", "expected_alert_height": expected_alert_text_height, @@ -392,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") - # 5. Тест: Заполняем все обязательные поля - logger.debug("Test 5: All required fields are filled") + # 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...") @@ -411,33 +527,26 @@ class TestCreateRackElement: # Очищаем поле "Высота в юнитах" если оно заполнено if "Высота в юнитах" in fields_locators: - field_container = fields_locators["Высота в юнитах"] - # Проверяем наличие текста в поле - field_text = field_container.inner_text() or "" - if field_text.strip(): + if create_child_frame.is_field_filled("Высота в юнитах", container_locator): logger.debug("Clearing 'Высота в юнитах' field...") create_child_frame.clear_combobox_field("Высота в юнитах") create_child_frame.wait_for_timeout(500) # Очищаем поле "Глубина (мм)" если оно заполнено if "Глубина (мм)" in fields_locators: - field_container = fields_locators["Глубина (мм)"] - # Проверяем наличие текста в поле - field_text = field_container.inner_text() or "" - if field_text.strip(): + if create_child_frame.is_field_filled("Глубина (мм)", container_locator): logger.debug("Clearing 'Глубина (мм)' field...") create_child_frame.clear_combobox_field("Глубина (мм)") create_child_frame.wait_for_timeout(500) # Очищаем поле "Имя" если оно заполнено if "Имя" in fields_locators: - field_container = fields_locators["Имя"] - # Находим input внутри контейнера - input_field = field_container.locator("input").first - if input_field.count() > 0: - current_value = input_field.input_value() - if current_value.strip(): - logger.debug("Clearing 'Имя' field...") + if create_child_frame.is_field_filled("Имя", container_locator): + logger.debug("Clearing 'Имя' field...") + # Специальная обработка для текстового поля + field_container = fields_locators["Имя"] + input_field = field_container.locator("input").first + if input_field.count() > 0: input_field.click() input_field.press("Control+A") input_field.press("Backspace") @@ -461,38 +570,48 @@ class TestCreateRackElement: # Нажимаем кнопку создания create_child_frame.click_add_button() - create_child_frame.wait_for_timeout(1000) + create_child_frame.wait_for_timeout(500) - # Проверяем, что НЕТ alert-окон для всех обязательных полей - #create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000) - #create_child_frame.alert.check_alert_absence(expected_alert_text_depth, 1000) - #logger.debug("No alert windows for required fields appeared - all fields filled correctly") + # Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача) + # Проверяем наличие alert-окна + # create_child_frame.alert.check_alert_presence("") - # Проверяем, что ушли со страницы создания - try: - create_child_frame.check_toolbar_title('Создать дочерний элемент в') - logger.warning("Rack creation may not have completed successfully") - except AssertionError: - logger.debug("Creation page closed - rack successfully created") + # Получаем текст 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() logger.debug("Required fields validation test completed successfully") - def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: - """Проверяет существование стойки.""" + # Вспомогательные методы проверки + def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: + """Проверяет существование стойки. + + Args: + browser: Страница Playwright + rack_name: Имя стойки для проверки + + Returns: + bool: True если стойка существует, False в противном случае + """ logger.debug(f"Checking existence of rack with name '{rack_name}'") # Обновляем навигационную панель self.main_page.click_main_navigation_panel_item("Объекты") self.main_page.click_main_navigation_panel_item("Объекты") - self.main_page.wait_for_timeout(1000) self.main_page.click_subpanel_item("test-zone") - self.main_page.wait_for_timeout(3000) nav_panel_locator = NavigationPanelLocators.TREEVIEW # Проверяем видимость элемента - element = browser.locator(nav_panel_locator).get_by_text(rack_name).first + element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first if element.is_visible(): logger.debug(f"Rack with name '{rack_name}' found") @@ -500,41 +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.main_page.wait_for_timeout(2000) - - # Нажимаем кнопку "Создать" на тулбаре - 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() - create_child_frame.wait_for_timeout(2000)