diff --git a/components/dropdown_list_component.py b/components/dropdown_list_component.py index 160aa47..a139953 100644 --- a/components/dropdown_list_component.py +++ b/components/dropdown_list_component.py @@ -88,6 +88,113 @@ class DropdownList(BaseComponent): self.page.wait_for_timeout(300) self.check_item_with_text(last_item_name) + def open_combobox(self, combobox_locator: str | Locator, listbox_locator: str | Locator, icon_locator: str | Locator = None) -> None: + """ + Открывает выпадающий список combobox. + + Args: + combobox_locator: Локатор combobox + listbox_locator: Локатор выпадающего списка + icon_locator: Локатор иконки для клика (опционально) + """ + logger.info("Открытие combobox...") + + combobox = self.get_locator(combobox_locator) + listbox = self.get_locator(listbox_locator) + + # Прокручиваем до combobox + combobox.scroll_into_view_if_needed() + self.page.wait_for_timeout(1000) + + # Проверяем, не открыт ли уже список + listbox_already_open = False + listbox_count = listbox.count() + + if listbox_count > 0: + listbox_already_open = listbox.first.is_visible() + + if not listbox_already_open: + # Если указан локатор иконки, кликаем на него, иначе на сам combobox + if icon_locator: + icon = combobox.locator(icon_locator) + icon.scroll_into_view_if_needed() + icon.click(timeout=10000) + else: + combobox.click(timeout=10000) + logger.info("Клик на combobox выполнен") + self.page.wait_for_timeout(1000) + + # Проверяем что список открылся + listbox_count_after = listbox.count() + listbox_visible = False + + if listbox_count_after > 0: + listbox_visible = listbox.first.is_visible() + + if listbox_visible: + logger.info("Выпадающий список найден и открыт") + else: + logger.warning("Не удалось открыть выпадающий список") + + def get_combobox_options(self, combobox_locator: str | Locator, listbox_locator: str | Locator, icon_locator: str | Locator = None) -> list[str]: + """ + Получает список доступных опций из combobox. + + Args: + combobox_locator: Локатор combobox + listbox_locator: Локатор выпадающего списка + icon_locator: Локатор иконки для клика (опционально) + + Returns: + list[str]: Список доступных опций + """ + logger.info("Получение списка опций combobox...") + + # Открываем combobox (если еще не открыт) + self.open_combobox(combobox_locator, listbox_locator, icon_locator) + + options_list = self.get_item_names(listbox_locator) + + # Закрываем combobox (кликаем вне его) + self.page.mouse.click(10, 10) + self.page.wait_for_timeout(500) + + logger.info(f"Найдено опций: {len(options_list)} - {options_list}") + return options_list + + def get_selected_combobox_value(self, combobox_locator: str | Locator, value_locator: str | Locator = None) -> str: + """ + Получает выбранное значение из combobox. + + Args: + combobox_locator: Локатор combobox + value_locator: Локатор элемента с выбранным значением (опционально) + + Returns: + str: Выбранное значение или пустая строка если ничего не выбрано + """ + combobox = self.get_locator(combobox_locator) + + selected_value = "" + + if value_locator: + # Используем переданный локатор для значения + value_element = combobox.locator(value_locator) + if value_element.count() > 0: + selected_value = value_element.first.text_content().strip() + else: + # Ищем в span элементах по умолчанию + span_locator = combobox.locator("span") + if span_locator.count() > 0: + for i in range(span_locator.count()): + span_text = span_locator.nth(i).text_content().strip() + if span_text and span_text not in ["Класс объекта учета"]: # Можно сделать исключения параметром + selected_value = span_text + break + + logger.info(f"Выбранное значение combobox: '{selected_value}'") + return selected_value + # Проверки: def check_item_with_text(self, text: str) -> None: """Проверяет наличие и доступность элемента списка. @@ -98,7 +205,7 @@ class DropdownList(BaseComponent): Raises: AssertionError: Если элемент отсутствует или недоступен. """ - + element = self.page.get_by_role("listitem").filter(has_text=text) if element.count() > 1: rtext = f"^{text}$" diff --git a/locators/alert_locators.py b/locators/alert_locators.py index c21b587..12b1d86 100644 --- a/locators/alert_locators.py +++ b/locators/alert_locators.py @@ -14,10 +14,14 @@ class AlertLocators: ALERT_MESSAGE (str): текстового сообщения в alert-окне. ALERT_DISMISS_BUTTON (str): кнопки закрытия alert-окна. ALERT_BY_TEXT (str): alert-окна с определенным текстом (шаблон). + ERROR_CLASSES (list): классы для подсветки ошибок валидации. """ ALERT_ROLE: str = "alert" ALERT_BASE: str = "//div[contains(@class,'v-alert')]" ALERT_MESSAGE: str = f"{ALERT_BASE}/div" ALERT_DISMISS_BUTTON: str = "//a[@class='v-alert__dismissible']" - ALERT_BY_TEXT: str = f"{ALERT_BASE}[contains(., '{{text}}')]" \ No newline at end of file + ALERT_BY_TEXT: str = f"{ALERT_BASE}[contains(., '{{text}}')]" + + # Классы для подсветки ошибок валидации полей + ERROR_CLASSES: list = ["error--text"] diff --git a/locators/rack_locators.py b/locators/rack_locators.py index 17d4ccd..c79b5cd 100644 --- a/locators/rack_locators.py +++ b/locators/rack_locators.py @@ -11,7 +11,7 @@ class RackLocators: - Вкладки стойки (верхние вкладки) - Секции лицевой и обратной сторон стойки - Юниты и устройства на стойке - - Поля формы редактирования стойки + - Поля формы редактирования и создания стойки - Контейнеры и структурные элементы """ @@ -39,7 +39,7 @@ class RackLocators: # Контейнер формы FORM_CONTAINER = "//div[contains(@class, 'container')]" - # Локаторы полей + # Локаторы полей формы редактирования стойки NAME_FIELD = "//input[@aria-label='Имя']" SERIAL_NUMBER_FIELD = "//input[@aria-label='Серийный номер']" INVENTORY_NUMBER_FIELD = "//input[@aria-label='Инвентарный номер']" @@ -50,6 +50,22 @@ class RackLocators: SERVICE_ORG_FIELD = "//input[@aria-label='Обслуживающая организация']" PROJECT_FIELD = "//input[@aria-label='Проект/Титул']" + # Локаторы полей формы создания стойки + RACK_NAME_FIELD = "//label[text()='Имя']/following-sibling::input" + RACK_HEIGHT_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Высота в юнитах']]" + RACK_DEPTH_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Глубина (мм)']]" + RACK_SERIAL_FIELD = "//label[text()='Серийный номер']/following-sibling::input" + RACK_INVENTORY_FIELD = "//label[text()='Инвентарный номер']/following-sibling::input" + RACK_COMMENT_FIELD = "//label[text()='Комментарий']/following-sibling::input" + RACK_CABLE_ENTRY_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Ввод кабеля']]" + RACK_STATE_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot white') and .//label[text()='Состояние']]" + RACK_OWNER_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Владелец']]" + RACK_SERVICE_ORG_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Обслуживающая организация']]" + RACK_PROJECT_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Проект/Титул']]" + + # Локатор для родительского контейнера поля ввода + INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]" + # Локаторы для отображения сторон стойки FRONT_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and not(contains(@class, 'back'))]" BACK_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and contains(@class, 'back')]" diff --git a/pages/create_elements_tab/__pycache__/create_rack_element_tab.cpython-313.pyc b/pages/create_elements_tab/__pycache__/create_rack_element_tab.cpython-313.pyc index 1178e22..a8b9842 100644 Binary files a/pages/create_elements_tab/__pycache__/create_rack_element_tab.cpython-313.pyc and b/pages/create_elements_tab/__pycache__/create_rack_element_tab.cpython-313.pyc differ diff --git a/pages/create_elements_tab/create_rack_element_tab.py b/pages/create_elements_tab/create_rack_element_tab.py index bca7985..682fa32 100644 --- a/pages/create_elements_tab/create_rack_element_tab.py +++ b/pages/create_elements_tab/create_rack_element_tab.py @@ -5,7 +5,6 @@ import re from playwright.sync_api import Page, expect - from elements.tooltip_button_element import TooltipButton from components.toolbar_component import ToolbarComponent from components.dropdown_list_component import DropdownList @@ -15,37 +14,33 @@ from components.base_component import BaseComponent from components.alert_component import AlertComponent from components.navbar_component import NavigationPanelComponent from locators.navigation_panel_locators import NavigationPanelLocators -from locators.combobox_locators import ComboboxLocators # Новый импорт +from locators.combobox_locators import ComboboxLocators +from locators.rack_locators import RackLocators +from locators.alert_locators import AlertLocators from tools.logger import get_logger logger = get_logger("CREATE_RACK_ELEMENT") -# =============== Локаторы ================================================ - -# Локаторы для полей стойки -RACK_NAME_FIELD = "//label[text()='Имя']/following-sibling::input" -RACK_HEIGHT_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Высота в юнитах']]" -RACK_DEPTH_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Глубина (мм)']]" -RACK_SERIAL_FIELD = "//label[text()='Серийный номер']/following-sibling::input" -RACK_INVENTORY_FIELD = "//label[text()='Инвентарный номер']/following-sibling::input" -RACK_COMMENT_FIELD = "//label[text()='Комментарий']/following-sibling::input" -RACK_CABLE_ENTRY_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Ввод кабеля']]" -RACK_STATE_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Состояние']]" -RACK_OWNER_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Владелец']]" -RACK_SERVICE_ORG_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Обслуживающая организация']]" -RACK_PROJECT_FIELD = "//div[contains(@class, 'v-input__slot') and .//label[text()='Проект/Титул']]" # Словарь для сопоставления названий полей с локаторами COMBOBOX_FIELDS_MAP = { - "Высота в юнитах": RACK_HEIGHT_FIELD, - "Глубина (мм)": RACK_DEPTH_FIELD, - "Ввод кабеля": RACK_CABLE_ENTRY_FIELD, - "Состояние": RACK_STATE_FIELD, - "Владелец": RACK_OWNER_FIELD, - "Обслуживающая организация": RACK_SERVICE_ORG_FIELD, - "Проект/Титул": RACK_PROJECT_FIELD + # Обязательные поля + "Имя": RackLocators.RACK_NAME_FIELD, + "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, + "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD, + + # Дополнительные текстовые поля + "Серийный номер": RackLocators.RACK_SERIAL_FIELD, + "Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD, + "Комментарий": RackLocators.RACK_COMMENT_FIELD, + + # Combobox поля + "Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD, + "Состояние": RackLocators.RACK_STATE_FIELD, + "Владелец": RackLocators.RACK_OWNER_FIELD, + "Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD, + "Проект/Титул": RackLocators.RACK_PROJECT_FIELD } -#======================================================================================================== class CreateRackElementTab(BasePage): @@ -90,6 +85,326 @@ class CreateRackElementTab(BasePage): # Инициализация компонента выпадающего списка self.dropdown = DropdownList(page) + # =============== МЕТОДЫ ДЕЙСТВИЙ ======================== + + def click_add_button(self) -> None: + """ + Кликает на кнопку 'Добавить'. + """ + self.toolbar.click_button("add") + + def click_cancel_button(self) -> None: + """ + Кликает на кнопку 'Отменить'. + """ + self.toolbar.click_button("cancel") + + def open_object_class_combobox(self) -> None: + """ + Открывает выпадающий список combobox 'Класс объекта учета'. + """ + logger.info("Открытие combobox 'Класс объекта учета'...") + self.dropdown.open_combobox( + ComboboxLocators.OBJECT_CLASS_COMBOBOX, + ComboboxLocators.LISTBOX_SELECTOR, + ComboboxLocators.COMBOBOX_ICON + ) + + def select_object_class(self, class_name: str) -> None: + """ + Выбирает класс объекта из выпадающего списка. + + Args: + class_name: Название класса объекта для выбора + + Raises: + AssertionError: Если класс не найден в списке + """ + logger.info(f"Выбор класса объекта: '{class_name}'...") + + # Открываем combobox + self.open_object_class_combobox() + + self.dropdown.click_item_with_text(class_name) + + # Проверяем что выбор произошел + self.wait_for_timeout(1000) + selected_value = self.get_selected_object_class() + + if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower(): + # Если выбор не произошел, получаем доступные опции для отладки + available_options = self.get_object_class_options() + logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}") + raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'") + + logger.info(f"Класс объекта '{class_name}' успешно выбран") + + def fill_rack_data(self, name: str, height: str = "42", depth: str = "1000", + serial: str = "", inventory: str = "", comment: str = "", + cable_entry: str = "", state: str = "", owner: str = "", + service_org: str = "", project: str = "") -> None: + """ + Заполняет данные для создания стойки. + + Args: + name: Наименование стойки + height: Высота в юнитах (по умолчанию 42) + depth: Глубина в мм (по умолчанию 1000) + serial: Серийный номер + inventory: Инвентарный номер + comment: Комментарий + cable_entry: Ввод кабеля + state: Состояние + owner: Владелец + service_org: Обслуживающая организация + project: Проект/Титул + """ + logger.info(f"Заполнение данных стойки: {name}") + + # Заполняем обязательные поля + name_field = self.page.locator(RackLocators.RACK_NAME_FIELD) + name_field.fill(name) + logger.info(f"Заполнено поле 'Имя': {name}") + + self._select_combobox("Высота в юнитах", height) + logger.info(f"Выбрана высота: {height} юнитов") + + self._select_combobox("Глубина (мм)", depth) + logger.info(f"Выбрана глубина: {depth} мм") + + # Заполняем опциональные поля + if serial: + serial_field = self.page.locator(RackLocators.RACK_SERIAL_FIELD) + serial_field.fill(serial) + logger.info(f"Заполнен серийный номер: {serial}") + + if inventory: + inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD) + inventory_field.fill(inventory) + logger.info(f"Заполнен инвентарный номер: {inventory}") + + if comment: + comment_field = self.page.locator(RackLocators.RACK_COMMENT_FIELD) + comment_field.fill(comment) + logger.info(f"Добавлен комментарий: {comment}") + + # Заполняем дополнительные combobox поля + if cable_entry: + self._select_combobox("Ввод кабеля", cable_entry) + logger.info(f"Выбран ввод кабеля: {cable_entry}") + + if state: + self._select_combobox("Состояние", state) + logger.info(f"Выбрано состояние: {state}") + + if owner: + self._select_combobox("Владелец", owner) + logger.info(f"Выбран владелец: {owner}") + + if service_org: + self._select_combobox("Обслуживающая организация", service_org) + logger.info(f"Выбрана обслуживающая организация: {service_org}") + + if project: + self._select_combobox("Проект/Титул", project) + logger.info(f"Выбран проект/титул: {project}") + + logger.info("Данные стойки заполнены") + + def _select_combobox(self, field_name: str, value: str) -> None: + """ + Выбор значения в combobox. + + Args: + field_name: Название поля + value: Значение для выбора + """ + logger.info(f"Выбор '{value}' в поле '{field_name}'...") + + # Получаем статический локатор из словаря + if field_name not in COMBOBOX_FIELDS_MAP: + raise ValueError(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP") + + field_locator = COMBOBOX_FIELDS_MAP[field_name] + + # Для всех полей используем first() чтобы избежать strict mode violation + field_container = self.page.locator(field_locator).first + + # Прокручиваем до поля + field_container.scroll_into_view_if_needed() + self.wait_for_timeout(500) + + # Проверяем видимость поля + self.base_component.check_visibility(field_container, f"Поле '{field_name}' не найдено") + + # Универсальный клик с force=True для всех полей + field_container.click(force=True) + self.wait_for_timeout(1000) + + # Вводим значение + self.page.keyboard.type(value) + self.wait_for_timeout(500) + self.page.keyboard.press("Enter") + + logger.info(f"Поле '{field_name}' заполнено") + + def create_rack(self, rack_name: str, **kwargs) -> None: + """ + Полный процесс создания стойки. + + Args: + rack_name: Наименование стойки + **kwargs: Дополнительные параметры стойки + """ + logger.info(f"Начало процесса создания стойки: {rack_name}") + + # Выбираем класс объекта "Стойка" + self.select_object_class("Стойка") + self.wait_for_timeout(1000) + + # Проверяем наличие полей стойки + self.check_rack_fields_presence() + + # Заполняем данные + self.fill_rack_data(rack_name, **kwargs) + + # Создаем стойку + self.click_add_button() + + logger.info(f"Процесс создания стойки '{rack_name}' завершен") + + def clear_combobox_field(self, field_name: str) -> None: + """ + Очищает значение в combobox поле с помощью кнопки закрытия (крестика). + + Args: + field_name: Название поля для очистки + """ + logger.info(f"Очистка combobox поля '{field_name}' с помощью кнопки закрытия...") + + if field_name not in COMBOBOX_FIELDS_MAP: + logger.warning(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP") + return + + field_locator = COMBOBOX_FIELDS_MAP[field_name] + + # Находим поле по локатору + field_container = self.page.locator(field_locator).first + + # Проверяем что поле видимо + if not field_container.is_visible(): + logger.info(f"Поле '{field_name}' не видимо, пропускаем очистку") + return + + # Прокручиваем до поля + field_container.scroll_into_view_if_needed() + self.wait_for_timeout(500) + + # Ищем кнопку закрытия (крестик) внутри контейнера поля + close_button = field_container.locator(ComboboxLocators.COMBOBOX_CLOSE_BUTTON) + + # Проверяем наличие и видимость кнопки закрытия + if close_button.count() > 0 and close_button.is_visible(): + # Если кнопка закрытия видима - кликаем на нее + close_button.click() + self.wait_for_timeout(500) + logger.info(f"Combobox поле '{field_name}' очищено с помощью кнопки закрытия") + else: + # Если кнопки закрытия нет, просто логируем этот факт + logger.info(f"Кнопка закрытия не найдена для поля '{field_name}', очистка не выполнена") + + def clear_rack_fields(self) -> None: + """ + Очищает все поля формы создания стойки. + """ + logger.info("Очистка всех полей формы стойки...") + + # Очищаем текстовые поля + text_fields = [ + (RackLocators.RACK_NAME_FIELD, "Имя"), + (RackLocators.RACK_SERIAL_FIELD, "Серийный номер"), + (RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"), + (RackLocators.RACK_COMMENT_FIELD, "Комментарий") + ] + + for field_locator, field_name in text_fields: + field = self.page.locator(field_locator) + if field.count() > 0 and field.first.is_visible(): + field.fill("") + logger.info(f"Текстовое поле '{field_name}' очищено") + + # Очищаем combobox поля + combobox_fields = [ + "Высота в юнитах", + "Глубина (мм)", + "Ввод кабеля", + "Состояние", + "Владелец", + "Обслуживающая организация", + "Проект/Титул" + ] + + for field_name in combobox_fields: + self.clear_combobox_field(field_name) + + logger.info("Все поля формы стойки очищены") + + def get_object_class_options(self) -> list[str]: + """ + Получает список доступных опций из combobox. + + Returns: + list[str]: Список доступных классов объектов + """ + return self.dropdown.get_combobox_options( + ComboboxLocators.OBJECT_CLASS_COMBOBOX, + ComboboxLocators.LISTBOX_SELECTOR, + ComboboxLocators.COMBOBOX_ICON + ) + + def get_selected_object_class(self) -> str: + """ + Получает выбранный класс объекта учета. + + Returns: + str: Выбранный класс объекта или пустая строка если ничего не выбрано + """ + return self.dropdown.get_selected_combobox_value( + ComboboxLocators.OBJECT_CLASS_COMBOBOX, + ComboboxLocators.SELECTED_VALUE_SPAN + ) + + # =============== МЕТОДЫ ПРОВЕРОК ======================== + def check_rack_exists(self, rack_name: str) -> bool: + """ + Проверяет, существует ли уже стойка с указанным именем в навигационной панели. + + Args: + rack_name: Имя стойки для проверки + + Returns: + bool: True если стойка существует, False если нет + """ + logger.info(f"Проверка существования стойки с именем '{rack_name}'") + + self.main_page.click_main_navigation_panel_item("Объекты") + self.main_page.click_main_navigation_panel_item("Объекты") + self.wait_for_timeout(1000) + self.main_page.click_subpanel_item("test-zone") + self.wait_for_timeout(3000) + + nav_panel_locator = NavigationPanelLocators.TREEVIEW + + # Проверяем видимость элемента через is_visible + element = self.page.locator(nav_panel_locator).get_by_text(rack_name).first + + if element.is_visible(): + logger.info(f"Стойка с именем '{rack_name}' найдена") + return True + else: + logger.info(f"Стойки с именем '{rack_name}' не найдена") + return False + def should_be_toolbar_buttons(self) -> None: """ Проверяет наличие и функциональность кнопок тулбара. @@ -108,18 +423,6 @@ class CreateRackElementTab(BasePage): self.toolbar.click_button("cancel") self.wait_for_timeout(2000) - def click_add_button(self) -> None: - """ - Кликает на кнопку 'Добавить'. - """ - self.toolbar.click_button("add") - - def click_cancel_button(self) -> None: - """ - Кликает на кнопку 'Отменить'. - """ - self.toolbar.click_button("cancel") - def check_toolbar_title(self, expected_title: str) -> None: """ Проверяет заголовок тулбара. @@ -130,7 +433,7 @@ class CreateRackElementTab(BasePage): Raises: AssertionError: Если заголовок не соответствует ожидаемому """ - logger.info(f"Проверка заголовка тулбара: '{expected_title}'...") + logger.info(f"Проверка заголовок тулбара: '{expected_title}'...") # Используем метод тулбара с фильтрацией по тексту actual_text = self.toolbar.get_toolbar_title_text( @@ -184,120 +487,6 @@ class CreateRackElementTab(BasePage): logger.info("Содержимое combobox 'Класс объекта учета' корректно") - def open_object_class_combobox(self) -> None: - """ - Открывает выпадающий список combobox 'Класс объекта учета'. - """ - logger.info("Открытие combobox 'Класс объекта учета'...") - - combobox_locator = self.page.locator(ComboboxLocators.OBJECT_CLASS_COMBOBOX) - listbox_locator = self.page.locator(ComboboxLocators.LISTBOX_SELECTOR) - icon_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_ICON) - - # Прокручиваем до combobox - combobox_locator.scroll_into_view_if_needed() - self.wait_for_timeout(1000) - - # Проверяем, не открыт ли уже список - listbox_already_open = False - listbox_count = listbox_locator.count() - - if listbox_count > 0: - listbox_already_open = listbox_locator.first.is_visible() - - if not listbox_already_open: - # Только если список не открыт, кликаем на иконку - icon_locator.scroll_into_view_if_needed() - icon_locator.click(timeout=10000) - logger.info("Клик на иконку combobox выполнен") - self.wait_for_timeout(1000) - - # Проверяем что список открылся - listbox_count_after = listbox_locator.count() - listbox_visible = False - - if listbox_count_after > 0: - listbox_visible = listbox_locator.first.is_visible() - - if listbox_visible: - logger.info("Выпадающий список найден и открыт") - else: - logger.warning("Не удалось открыть выпадающий список") - - def get_object_class_options(self) -> list[str]: - """ - Получает список доступных опций из combobox. - - Returns: - list[str]: Список доступных классов объектов - """ - logger.info("Получение списка опций combobox 'Класс объекта учета'...") - - # Открываем combobox (если еще не открыт) - self.open_object_class_combobox() - - # Используем метод get_item_names из DropdownList - options_list = self.dropdown.get_item_names(ComboboxLocators.LISTBOX_SELECTOR) - - # Закрываем combobox (кликаем вне его) - self.page.mouse.click(10, 10) - self.wait_for_timeout(500) - - logger.info(f"Найдено опций: {len(options_list)} - {options_list}") - return options_list - - def select_object_class(self, class_name: str) -> None: - """ - Выбирает класс объекта из выпадающего списка. - - Args: - class_name: Название класса объекта для выбора - - Raises: - AssertionError: Если класс не найден в списке - """ - logger.info(f"Выбор класса объекта: '{class_name}'...") - - # Открываем combobox - self.open_object_class_combobox() - - self.dropdown.click_item_with_text(class_name) - - # Проверяем что выбор произошел - self.wait_for_timeout(1000) - selected_value = self.get_selected_object_class() - - if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower(): - # Если выбор не произошел, получаем доступные опции для отладки - available_options = self.get_object_class_options() - logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}") - raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'") - - logger.info(f"Класс объекта '{class_name}' успешно выбран") - - def get_selected_object_class(self) -> str: - """ - Получает выбранный класс объекта учета. - - Returns: - str: Выбранный класс объекта или пустая строка если ничего не выбрано - """ - combobox_locator = self.page.locator(ComboboxLocators.OBJECT_CLASS_COMBOBOX) - - selected_value = "" - - # Ищем в span элементах - span_locator = combobox_locator.locator(ComboboxLocators.SELECTED_VALUE_SPAN) - if span_locator.count() > 0: - for i in range(span_locator.count()): - span_text = span_locator.nth(i).text_content().strip() - if span_text and span_text not in ["Класс объекта учета"]: - selected_value = span_text - break - - logger.info(f"Выбранный класс объекта: '{selected_value}'") - return selected_value - def check_object_class_selected(self, expected_class: str) -> None: """ Проверяет что выбран указанный класс объекта. @@ -365,8 +554,6 @@ class CreateRackElementTab(BasePage): logger.info(f"Элемент '{item_text}' присутствует в списке") - # =============== МЕТОДЫ ДЛЯ РАБОТЫ СО СТОЙКОЙ ======================== - def check_rack_fields_presence(self) -> None: """ Проверяет наличие полей специфичных для стойки. @@ -378,21 +565,21 @@ class CreateRackElementTab(BasePage): # Основные обязательные поля required_fields = [ - (RACK_NAME_FIELD, "Имя"), - (RACK_HEIGHT_FIELD, "Высота в юнитах"), - (RACK_DEPTH_FIELD, "Глубина (мм)") + (RackLocators.RACK_NAME_FIELD, "Имя"), + (RackLocators.RACK_HEIGHT_FIELD, "Высота в юнитах"), + (RackLocators.RACK_DEPTH_FIELD, "Глубина (мм)") ] # Дополнительные поля optional_fields = [ - (RACK_SERIAL_FIELD, "Серийный номер"), - (RACK_INVENTORY_FIELD, "Инвентарный номер"), - (RACK_COMMENT_FIELD, "Комментарий"), - (RACK_CABLE_ENTRY_FIELD, "Ввод кабеля"), - (RACK_STATE_FIELD, "Состояние"), - (RACK_OWNER_FIELD, "Владелец"), - (RACK_SERVICE_ORG_FIELD, "Обслуживающая организация"), - (RACK_PROJECT_FIELD, "Проект/Титул") + (RackLocators.RACK_SERIAL_FIELD, "Серийный номер"), + (RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"), + (RackLocators.RACK_COMMENT_FIELD, "Комментарий"), + (RackLocators.RACK_CABLE_ENTRY_FIELD, "Ввод кабеля"), + (RackLocators.RACK_STATE_FIELD, "Состояние"), + (RackLocators.RACK_OWNER_FIELD, "Владелец"), + (RackLocators.RACK_SERVICE_ORG_FIELD, "Обслуживающая организация"), + (RackLocators.RACK_PROJECT_FIELD, "Проект/Титул") ] # Проверяем обязательные поля @@ -410,250 +597,86 @@ class CreateRackElementTab(BasePage): logger.info("Все основные поля для стойки присутствуют") - def fill_rack_data(self, name: str, height: str = "42", depth: str = "1000", - serial: str = "", inventory: str = "", comment: str = "", - cable_entry: str = "", state: str = "", owner: str = "", - service_org: str = "", project: str = "") -> None: + def check_field_highlighted_error(self, field_name: str) -> None: """ - Заполняет данные для создания стойки. + Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). Args: - name: Наименование стойки - height: Высота в юнитах (по умолчанию 42) - depth: Глубина в мм (по умолчанию 1000) - serial: Серийный номер - inventory: Инвентарный номер - comment: Комментарий - cable_entry: Ввод кабеля - state: Состояние - owner: Владелец - service_org: Обслуживающая организация - project: Проект/Титул + field_name: Название поля для проверки """ - logger.info(f"Заполнение данных стойки: {name}") + logger.info(f"Проверка подсветки поля '{field_name}' цветом ошибки...") - # Заполняем обязательные поля - name_field = self.page.locator(RACK_NAME_FIELD) - name_field.fill(name) - logger.info(f"Заполнено поле 'Имя': {name}") + # Локаторы только для обязательных полей + required_fields = { + "Имя": RackLocators.RACK_NAME_FIELD, + "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, + "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD + } - self._select_combobox("Высота в юнитах", height) - logger.info(f"Выбрана высота: {height} юнитов") + if field_name not in required_fields: + raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается") - self._select_combobox("Глубина (мм)", depth) - logger.info(f"Выбрана глубина: {depth} мм") - - # Заполняем опциональные поля - if serial: - serial_field = self.page.locator(RACK_SERIAL_FIELD) - serial_field.fill(serial) - logger.info(f"Заполнен серийный номер: {serial}") - - if inventory: - inventory_field = self.page.locator(RACK_INVENTORY_FIELD) - inventory_field.fill(inventory) - logger.info(f"Заполнен инвентарный номер: {inventory}") - - if comment: - comment_field = self.page.locator(RACK_COMMENT_FIELD) - comment_field.fill(comment) - logger.info(f"Добавлен комментарий: {comment}") - - # Заполняем дополнительные combobox поля - if cable_entry: - self._select_combobox("Ввод кабеля", cable_entry) - logger.info(f"Выбран ввод кабеля: {cable_entry}") - - if state: - self._select_combobox("Состояние", state) - logger.info(f"Выбрано состояние: {state}") - - if owner: - self._select_combobox("Владелец", owner) - logger.info(f"Выбран владелец: {owner}") - - if service_org: - self._select_combobox("Обслуживающая организация", service_org) - logger.info(f"Выбрана обслуживающая организация: {service_org}") - - if project: - self._select_combobox("Проект/Титул", project) - logger.info(f"Выбран проект/титул: {project}") - - logger.info("Данные стойки заполнены") - - def _select_combobox(self, field_name: str, value: str) -> None: - """ - Выбор значения в combobox. - - Args: - field_name: Название поля - value: Значение для выбора - """ - logger.info(f"Выбор '{value}' в поле '{field_name}'...") - - # Получаем статический локатор из словаря - if field_name not in COMBOBOX_FIELDS_MAP: - raise ValueError(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP") - - field_locator = COMBOBOX_FIELDS_MAP[field_name] - field_container = self.page.locator(field_locator) - - # Прокручиваем до поля - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(500) - - # Проверяем видимость поля - self.base_component.check_visibility(field_container, f"Поле '{field_name}' не найдено") - - # Кликаем на контейнер чтобы активировать поле - field_container.click() - self.wait_for_timeout(1000) - - # Вводим значение - self.page.keyboard.type(value) - self.wait_for_timeout(500) - self.page.keyboard.press("Enter") - - logger.info(f"Поле '{field_name}' заполнено") - - def create_rack(self, rack_name: str, **kwargs) -> None: - """ - Полный процесс создания стойки. - - Args: - rack_name: Наименование стойки - **kwargs: Дополнительные параметры стойки - """ - logger.info(f"Начало процесса создания стойки: {rack_name}") - - # Выбираем класс объекта "Стойка" - self.select_object_class("Стойка") - self.wait_for_timeout(1000) - - # Проверяем наличие полей стойки - self.check_rack_fields_presence() - - # Заполняем данные - self.fill_rack_data(rack_name, **kwargs) - - # Создаем стойку - self.click_add_button() - - logger.info(f"Процесс создания стойки '{rack_name}' завершен") - - def clear_name_field(self) -> None: - """ - Очищает поле 'Имя'. - """ - logger.info("Очистка поля 'Имя'...") - name_field = self.page.locator(RACK_NAME_FIELD) - name_field.fill("") - logger.info("Поле 'Имя' очищено") - - def check_rack_exists(self, rack_name: str) -> bool: - """ - Проверяет, существует ли уже стойка с указанным именем в навигационной панели. - - Args: - rack_name: Имя стойки для проверки - - Returns: - bool: True если стойка существует, False если нет - """ - logger.info(f"Проверка существования стойки с именем '{rack_name}'") - - self.main_page.click_main_navigation_panel_item("Объекты") - self.main_page.click_main_navigation_panel_item("Объекты") - self.wait_for_timeout(1000) - self.main_page.click_subpanel_item("test-zone") - self.wait_for_timeout(1000) - - # Используем TREEVIEW локатор из NavigationPanelLocators - nav_panel_locator = NavigationPanelLocators.TREEVIEW - - # Проверяем видимость элемента через is_visible - element = self.page.locator(nav_panel_locator).get_by_text(rack_name).first - - if element.is_visible(): - logger.info(f"Стойка с именем '{rack_name}' найдена") - return True - else: - logger.info(f"Стойки с именем '{rack_name}' не найдена") - return False - - def clear_combobox_field(self, field_name: str) -> None: - """ - Очищает значение в combobox поле с помощью кнопки закрытия (крестика). - - Args: - field_name: Название поля для очистки - """ - logger.info(f"Очистка combobox поля '{field_name}' с помощью кнопки закрытия...") - - if field_name not in COMBOBOX_FIELDS_MAP: - logger.warning(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP") - return - - field_locator = COMBOBOX_FIELDS_MAP[field_name] - - # Находим поле по локатору - field_container = self.page.locator(field_locator).first + field_locator = required_fields[field_name] + field_element = self.page.locator(field_locator) # Проверяем что поле видимо - if not field_container.is_visible(): - logger.info(f"Поле '{field_name}' не видимо, пропускаем очистку") - return + self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено") - # Прокручиваем до поля - field_container.scroll_into_view_if_needed() - self.wait_for_timeout(500) + # Ищем родительский контейнер с использованием константы + parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first - # Ищем кнопку закрытия (крестик) внутри контейнера поля - close_button = field_container.locator(ComboboxLocators.COMBOBOX_CLOSE_BUTTON) + # Проверка классов ошибки + if parent_container.count() > 0: + error_classes = AlertLocators.ERROR_CLASSES - # Проверяем наличие и видимость кнопки закрытия - if close_button.count() > 0 and close_button.is_visible(): - # Если кнопка закрытия видима - кликаем на нее - close_button.click() - self.wait_for_timeout(500) - logger.info(f"Combobox поле '{field_name}' очищено с помощью кнопки закрытия") - else: - # Если кнопки закрытия нет, просто логируем этот факт - logger.info(f"Кнопка закрытия не найдена для поля '{field_name}', очистка не выполнена") + is_error_highlighted = False + for error_class in error_classes: + error_element = parent_container.locator(f".{error_class}") + if error_element.count() > 0: + is_error_highlighted = True + logger.info(f"Поле '{field_name}' подсвечено ошибкой") + break - def clear_rack_fields(self) -> None: + if not is_error_highlighted: + raise AssertionError(f"Поле '{field_name}' не подсвечено цветом ошибки ") + + logger.info(f"Поле '{field_name}' корректно подсвечено цветом ошибки") + + def check_field_not_highlighted_error(self, field_name: str) -> None: """ - Очищает все поля формы создания стойки. + Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). + + Args: + field_name: Название поля для проверки """ - logger.info("Очистка всех полей формы стойки...") + logger.info(f"Проверка отсутствия подсветки ошибки у поля '{field_name}'...") - # Очищаем текстовые поля - text_fields = [ - (RACK_NAME_FIELD, "Имя"), - (RACK_SERIAL_FIELD, "Серийный номер"), - (RACK_INVENTORY_FIELD, "Инвентарный номер"), - (RACK_COMMENT_FIELD, "Комментарий") - ] + # Локаторы только для обязательных полей + required_fields = { + "Имя": RackLocators.RACK_NAME_FIELD, + "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, + "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD + } - for field_locator, field_name in text_fields: - field = self.page.locator(field_locator) - if field.count() > 0 and field.first.is_visible(): - field.fill("") - logger.info(f"Текстовое поле '{field_name}' очищено") + if field_name not in required_fields: + raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается") - # Очищаем combobox поля - combobox_fields = [ - "Высота в юнитах", - "Глубина (мм)", - "Ввод кабеля", - "Состояние", - "Владелец", - "Обслуживающая организация", - "Проект/Титул" - ] + field_locator = required_fields[field_name] + field_element = self.page.locator(field_locator) - for field_name in combobox_fields: - self.clear_combobox_field(field_name) + # Проверяем что поле видимо + self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено") - logger.info("Все поля формы стойки очищены") + # Ищем родительский контейнер с использованием константы + parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first + + # Поверка отсутствия классов ошибки + if parent_container.count() > 0: + error_classes = AlertLocators.ERROR_CLASSES + + for error_class in error_classes: + error_element = parent_container.locator(f".{error_class}") + if error_element.count() > 0: + raise AssertionError(f"Поле '{field_name}' подсвечено ошибкой") + + logger.info(f"Поле '{field_name}' корректно не подсвечено цветом ошибки") diff --git a/tests/e2e/create_elements/__pycache__/test_create_child_element.cpython-313-pytest-8.4.1.pyc b/tests/e2e/create_elements/__pycache__/test_create_child_element.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 8c72922..0000000 Binary files a/tests/e2e/create_elements/__pycache__/test_create_child_element.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/create_elements/__pycache__/test_create_rack_element.cpython-313-pytest-8.4.1.pyc b/tests/e2e/create_elements/__pycache__/test_create_rack_element.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 120ab2a..0000000 Binary files a/tests/e2e/create_elements/__pycache__/test_create_rack_element.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py index 711320b..65690eb 100644 --- a/tests/e2e/create_elements/test_create_rack_element.py +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -37,17 +37,6 @@ class TestCreateRackElement: main_page.click_main_navigation_panel_item("test-zone") main_page.wait_for_timeout(2000) - # Создаем экземпляр страницы и переходим к созданию дочернего элемента - #child_element_page = CreateChildElementTab(browser) - #child_element_page.click_create_button() - #child_element_page.select_object_class("Стойка") - #child_element_page.check_object_class_selected("Стойка") - #child_element_page.wait_for_timeout(2000) - - #@pytest.mark.develop - def test_create_rack_content(self, browser: Page) -> None: - """Тест создания дочернего элемента типа 'Стойка'.""" - # Создаем экземпляр страницы и переходим к созданию дочернего элемента child_element_page = CreateChildElementTab(browser) child_element_page.click_create_button() @@ -55,6 +44,10 @@ class TestCreateRackElement: child_element_page.check_object_class_selected("Стойка") child_element_page.wait_for_timeout(2000) + #@pytest.mark.develop + def test_create_rack_content(self, browser: Page) -> None: + """Тест создания дочернего элемента типа 'Стойка'.""" + rack_element_page = CreateRackElementTab(browser) # Проверяем заголовок формы создания @@ -66,17 +59,9 @@ class TestCreateRackElement: rack_element_page.should_be_toolbar_buttons() - #@pytest.mark.develop def test_create_rack_child_element(self, browser: Page) -> None: """Тест создания дочернего элемента типа 'Стойка'.""" - # Создаем экземпляр страницы и переходим к созданию дочернего элемента - child_element_page = CreateChildElementTab(browser) - child_element_page.click_create_button() - child_element_page.select_object_class("Стойка") - child_element_page.check_object_class_selected("Стойка") - child_element_page.wait_for_timeout(2000) - rack_element_page = CreateRackElementTab(browser) # Проверяем что после выбора 'Стойка' появляются специфичные поля @@ -92,7 +77,9 @@ class TestCreateRackElement: depth="1000", serial="TEST123456", inventory="INV-001", - comment="Тестовая стойка для автоматизации" + comment="Тестовая стойка для автоматизации", + cable_entry="Сверху", + state="В эксплуатации" ) # Нажимаем кнопку создания @@ -102,7 +89,6 @@ class TestCreateRackElement: logger.info("Тест создания дочернего элемента 'Стойка' завершен успешно") - #@pytest.mark.develop def test_create_rack_with_duplicate_name(self, browser: Page) -> None: """ Тест создания стойки с уже существующим именем. @@ -113,7 +99,9 @@ class TestCreateRackElement: logger.info("Запуск теста создания стойки с дублирующимся именем") rack_name = "Test-Rack-01" + rack_element_page = CreateRackElementTab(browser) + rack_element_page.click_cancel_button() # Проверяем, существует ли уже стойка с таким именем if not rack_element_page.check_rack_exists(rack_name): @@ -138,12 +126,12 @@ class TestCreateRackElement: # Создаем первую стойку rack_element_page.click_add_button() - rack_element_page.wait_for_timeout(3000) + rack_element_page.wait_for_timeout(2000) logger.info(f"Первая стойка с именем '{rack_name}' успешно создана") else: logger.info(f"Стойка с именем '{rack_name}' уже существует, переходим к созданию второй") - # Теперь пытаемся создать вторую стойку с тем же именем + # Cоздаем вторую стойку с тем же именем logger.info(f"Пытаемся создать вторую стойку с именем '{rack_name}'") # Переходим обратно к созданию новой стойки @@ -171,20 +159,16 @@ class TestCreateRackElement: # Проверяем наличие alert-окна с сообщением о дублирующемся имени expected_alert_text = f"Имя {rack_name} уже используется" rack_element_page.alert.check_alert_presence(expected_alert_text) - logger.info(f"Alert-окно с текстом '{expected_alert_text}' успешно отображено") # Проверяем, что остались на странице создания (стойка не создана) rack_element_page.check_toolbar_title('Создать дочерний элемент в') + + # Закрываем alert-окно с помощью кнопки закрытия + rack_element_page.wait_for_timeout(2000) + rack_element_page.alert.close_alert_by_text(expected_alert_text) + logger.info("Система не позволила создать стойку с дублирующимся именем") - # Проверяем исчезновение alert-окна через некоторое время - rack_element_page.wait_for_timeout(5000) - rack_element_page.alert.check_alert_absence(expected_alert_text, timeout=20000) - logger.info("Alert-окно успешно исчезло") - - logger.info("Тест создания стойки с дублирующимся именем завершен успешно") - - @pytest.mark.develop def test_required_fields_validation(self, browser: Page) -> None: """ Тест проверки обязательных полей при создании стойки. @@ -194,33 +178,35 @@ class TestCreateRackElement: - Поле 'Высота в юнитах' должно быть заполнено - Поле 'Глубина (мм)' должно быть заполнено """ + # Текст сообщения alert-окна - #expected_alert_text_name = f"поле Имя должно быть заполнено" # Ошибка, поле не отслеживается + expected_alert_text_name = f"поле Имя должно быть заполнено" expected_alert_text_height = f"поле Высота в юнитах должно быть заполнено" expected_alert_text_depth = f"поле Глубина (мм) должно быть заполнено" - - # Создаем экземпляр страницы и переходим к созданию дочернего элемента - child_element_page = CreateChildElementTab(browser) - child_element_page.click_create_button() - child_element_page.select_object_class("Стойка") - child_element_page.check_object_class_selected("Стойка") - logger.info("Класс объекта 'Стойка' успешно выбран") - child_element_page.wait_for_timeout(2000) - rack_element_page = CreateRackElementTab(browser) # Проверяем наличие полей стойки rack_element_page.check_rack_fields_presence() - logger.info("Специфичные поля для стойки отображаются корректно") # 1. Тест: Попытка создания стойки поля - default - logger.info("Тест 1: Попытка создания стойки без заполнения обязательных полей") + logger.info("Тест 1: Создание стойки заполнене полей - default") # Нажимаем кнопку создания без заполнения данных rack_element_page.click_add_button() rack_element_page.wait_for_timeout(2000) + # Проверяем подсветку обязательных полей 'Высота в юнитах' и 'Глубина (мм)' цветом ошибки + #rack_element_page.check_field_not_highlighted_error("Имя") + rack_element_page.check_field_highlighted_error("Высота в юнитах") + rack_element_page.check_field_highlighted_error("Глубина (мм)") + + logger.info("Проверка валидации поля 'Имя' временно отключена - ожидаем фикс от разработчика") + # Проверяем наличие alert-окна с сообщением о заполнении Имя + #rack_element_page.alert.check_alert_presence(expected_alert_text_name) + # Закрываем alert-окно Имя с помощью кнопки закрытия + #rack_element_page.alert.close_alert_by_text(expected_alert_text_name) + # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах rack_element_page.alert.check_alert_presence(expected_alert_text_height) # Закрываем alert-окно Высота в юнитах с помощью кнопки закрытия @@ -249,10 +235,16 @@ class TestCreateRackElement: rack_element_page.click_add_button() rack_element_page.wait_for_timeout(2000) + # Проверяем подсветку всех обязательных полей цветом ошибки + #rack_element_page.check_field_highlighted_error("Имя") + rack_element_page.check_field_highlighted_error("Высота в юнитах") + rack_element_page.check_field_highlighted_error("Глубина (мм)") + + logger.info("Проверка валидации поля 'Имя' временно отключена - ожидаем фикс от разработчика") # Проверяем наличие alert-окна с сообщением о заполнении Имя - #rack_element_page.alert.check_alert_presence(expected_alert_text_name) # Ошибка, поле не отслеживается + #rack_element_page.alert.check_alert_presence(expected_alert_text_name) # Закрываем alert-окно Имя с помощью кнопки закрытия - #rack_element_page.alert.close_alert_by_text(expected_alert_text_name) # Ошибка, поле не отслеживается + #rack_element_page.alert.close_alert_by_text(expected_alert_text_name) # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах rack_element_page.alert.check_alert_presence(expected_alert_text_height) @@ -267,6 +259,7 @@ class TestCreateRackElement: # Проверяем, что остались на той же странице rack_element_page.check_toolbar_title('Создать дочерний элемент в') logger.info("Система не позволила создать стойку без имени, высоты и глубины") + rack_element_page.wait_for_timeout(2000) # 3. Тест: Заполняем только поле 'Высота в юнитах' logger.info("Тест 3: Заполняем только поле 'Высота в юнитах'") @@ -285,10 +278,13 @@ class TestCreateRackElement: rack_element_page.click_add_button() rack_element_page.wait_for_timeout(2000) - # Проверяем наличие alert-окна с сообщением о заполнении Имя - #rack_element_page.alert.check_alert_presence(expected_alert_text_name) # Ошибка, поле не отслеживается - # Закрываем alert-окно Имя с помощью кнопки закрытия - #rack_element_page.alert.close_alert_by_text(expected_alert_text_name) # Ошибка, поле не отслеживается + # Проверяем подсветку полей 'Имя' и 'Глубина (мм)' цветом ошибки + #rack_element_page.check_field_highlighted_error("Имя") + rack_element_page.check_field_not_highlighted_error("Высота в юнитах") + rack_element_page.check_field_highlighted_error("Глубина (мм)") + + # Проверяем, что НЕТ alert-окна для поля 'Высота в юнитах' (оно заполнено) + rack_element_page.alert.check_alert_absence(expected_alert_text_height, 1000) # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) rack_element_page.alert.check_alert_presence(expected_alert_text_depth) @@ -298,6 +294,7 @@ class TestCreateRackElement: # Проверяем, что остались на той же странице rack_element_page.check_toolbar_title('Создать дочерний элемент в') logger.info("Система не позволила создать стойку без имени и глубины") + rack_element_page.wait_for_timeout(2000) # 4. Тест: Заполняем только поле 'Глубина (мм)' logger.info("Тест 4: Заполняем только поле 'Глубина (мм)'") @@ -316,10 +313,15 @@ class TestCreateRackElement: rack_element_page.click_add_button() rack_element_page.wait_for_timeout(2000) - # Проверяем наличие alert-окна с сообщением о заполнении Имя - #rack_element_page.alert.check_alert_presence(expected_alert_text_name) # Ошибка, поле не отслеживается - # Закрываем alert-окно Имя с помощью кнопки закрытия - #rack_element_page.alert.close_alert_by_text(expected_alert_text_name) # Ошибка, поле не отслеживается + # Проверяем подсветку полей 'Имя' и 'Высота в юнитах' цветом ошибки + #rack_element_page.check_field_highlighted_error("Имя") + rack_element_page.check_field_highlighted_error("Высота в юнитах") + rack_element_page.check_field_not_highlighted_error("Глубина (мм)") + + logger.info("Проверка отсутствия alert-окна для поля 'Глубина (мм)' временно отключена - ожидаем фикс от разработчика") + # Проверяем, что НЕТ alert-окна для поля 'Глубина (мм)' (оно заполнено) + #rack_element_page.alert.check_alert_absence(expected_alert_text_depth, 1000) + #logger.info("Alert-окно для поля 'Глубина (мм)' не появилось - поле заполнено корректно") # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах rack_element_page.alert.check_alert_presence(expected_alert_text_height) @@ -328,7 +330,7 @@ class TestCreateRackElement: # Проверяем, что остались на той же странице rack_element_page.check_toolbar_title('Создать дочерний элемент в') - logger.info("Система не позволила создать стойку без высоты") + logger.info("Система не позволила создать стойку без имени и высоты") rack_element_page.wait_for_timeout(2000) # 5. Тест: Заполняем все обязательные поля @@ -344,12 +346,24 @@ class TestCreateRackElement: depth="1000" ) + # Проверяем, что ни одно поле не подсвечено цветом ошибки (все заполнены корректно) + rack_element_page.check_field_not_highlighted_error("Имя") + rack_element_page.check_field_not_highlighted_error("Высота в юнитах") + rack_element_page.check_field_not_highlighted_error("Глубина (мм)") + logger.info("Ни одно обязательное поле не подсвечено цветом ошибки - все поля заполнены корректно") + # Нажимаем кнопку создания rack_element_page.click_add_button() # Ждем завершения создания (должны перейти на другую страницу) rack_element_page.wait_for_timeout(3000) + # Проверяем, что НЕТ alert-окон для всех обязательных полей (все заполнены корректно) + rack_element_page.alert.check_alert_absence(expected_alert_text_name, 1000) + rack_element_page.alert.check_alert_absence(expected_alert_text_height, 1000) + rack_element_page.alert.check_alert_absence(expected_alert_text_depth, 1000) + logger.info("Alert-окна для обязательных полей не появились - все поля заполнены корректно") + # Проверяем, что ушли со страницы создания (косвенная проверка успешного создания) try: rack_element_page.check_toolbar_title('Создать дочерний элемент в') @@ -358,5 +372,3 @@ class TestCreateRackElement: logger.info("Страница создания закрыта - стойка успешно создана") logger.info("Тест проверки обязательных полей завершен успешно") - - diff --git a/tests/e2e/rack/__pycache__/test_create_rack.cpython-313-pytest-8.4.1.pyc b/tests/e2e/rack/__pycache__/test_create_rack.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index ca13e96..0000000 Binary files a/tests/e2e/rack/__pycache__/test_create_rack.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/rack/__pycache__/test_rack_general_info.cpython-313-pytest-8.4.1.pyc b/tests/e2e/rack/__pycache__/test_rack_general_info.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 3c66cff..0000000 Binary files a/tests/e2e/rack/__pycache__/test_rack_general_info.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/rack/__pycache__/test_rack_general_info__.cpython-313-pytest-8.4.1.pyc b/tests/e2e/rack/__pycache__/test_rack_general_info__.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 8da8bf3..0000000 Binary files a/tests/e2e/rack/__pycache__/test_rack_general_info__.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/rack/__pycache__/test_rack_tab.cpython-313-pytest-8.4.1.pyc b/tests/e2e/rack/__pycache__/test_rack_tab.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index e3b81cc..0000000 Binary files a/tests/e2e/rack/__pycache__/test_rack_tab.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/sessions/__pycache__/test_current_sessions_tab.cpython-313-pytest-8.4.1.pyc b/tests/e2e/sessions/__pycache__/test_current_sessions_tab.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 6f194d2..0000000 Binary files a/tests/e2e/sessions/__pycache__/test_current_sessions_tab.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/sessions/__pycache__/test_session_settings_tab.cpython-313-pytest-8.4.1.pyc b/tests/e2e/sessions/__pycache__/test_session_settings_tab.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index d2ffe62..0000000 Binary files a/tests/e2e/sessions/__pycache__/test_session_settings_tab.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/users/__pycache__/__init__.cpython-313.pyc b/tests/e2e/users/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index d7e74a6..0000000 Binary files a/tests/e2e/users/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/tests/e2e/users/__pycache__/test_add_user.cpython-313-pytest-8.4.1.pyc b/tests/e2e/users/__pycache__/test_add_user.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 3516048..0000000 Binary files a/tests/e2e/users/__pycache__/test_add_user.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/users/__pycache__/test_edit_user.cpython-313-pytest-8.4.1.pyc b/tests/e2e/users/__pycache__/test_edit_user.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index a8d6b24..0000000 Binary files a/tests/e2e/users/__pycache__/test_edit_user.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/users/__pycache__/test_user_card.cpython-313-pytest-8.4.1.pyc b/tests/e2e/users/__pycache__/test_user_card.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 543edc7..0000000 Binary files a/tests/e2e/users/__pycache__/test_user_card.cpython-313-pytest-8.4.1.pyc and /dev/null differ diff --git a/tests/e2e/users/__pycache__/test_users_tab.cpython-313-pytest-8.4.1.pyc b/tests/e2e/users/__pycache__/test_users_tab.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index ee5dd0b..0000000 Binary files a/tests/e2e/users/__pycache__/test_users_tab.cpython-313-pytest-8.4.1.pyc and /dev/null differ