From a3dc0a037c78955c9bfb71490ab8e438b687270b Mon Sep 17 00:00:00 2001 From: Radislav Date: Fri, 19 Dec 2025 11:43:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=B8=20=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accounting_objects/rack_maker.py | 120 ++++++++++++------ .../frames/create_child_element_frame.py | 52 +++++--- locators/rack_locators.py | 92 ++++++++++---- .../test_create_rack_element.py | 92 +++++++------- 4 files changed, 228 insertions(+), 128 deletions(-) diff --git a/components_derived/accounting_objects/rack_maker.py b/components_derived/accounting_objects/rack_maker.py index f684208..d487585 100644 --- a/components_derived/accounting_objects/rack_maker.py +++ b/components_derived/accounting_objects/rack_maker.py @@ -1,13 +1,14 @@ """Модуль создания объекта 'Стойка'.""" from dataclasses import dataclass -from playwright.sync_api import Page +from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.rack_locators import RackLocators from components.base_component import BaseComponent logger = get_logger("RACK_MAKER") +logger.setLevel("INFO") @dataclass class RackData: @@ -49,12 +50,12 @@ class RackObjectMaker(BaseComponent): rack_data: Данные стойки """ - logger.info(f"Filling rack data: {rack_data.name}") + logger.debug(f"Filling rack data: {rack_data.name}") self._fill_text_fields(rack_data) self._fill_combobox_fields(rack_data) - logger.info("Rack data filled successfully") + logger.debug("Rack data filled successfully") def _fill_text_fields(self, rack_data: RackData) -> None: """Заполняет текстовые поля.""" @@ -69,7 +70,7 @@ class RackObjectMaker(BaseComponent): field.press("Backspace") # Заполняем значение field.fill(value) - logger.info(f"Filled '{field_name}': {value}") + logger.debug(f"Filled '{field_name}': {value}") # Обязательные поля. if rack_data.name: @@ -90,55 +91,48 @@ class RackObjectMaker(BaseComponent): # Обязательные поля. if rack_data.height: - self._fill_combobox_field("Height in units", rack_data.height, - RackLocators.RACK_HEIGHT_FIELD) - logger.info(f"Selected height: {rack_data.height} units") + self._fill_combobox_field("Высота в юнитах", rack_data.height) + logger.debug(f"Selected height: {rack_data.height} units") if rack_data.depth: - self._fill_combobox_field("Depth (mm)", rack_data.depth, - RackLocators.RACK_DEPTH_FIELD) - logger.info(f"Selected depth: {rack_data.depth} mm") + self._fill_combobox_field("Глубина (мм)", rack_data.depth) + logger.debug(f"Selected depth: {rack_data.depth} mm") # Опциональные поля. if rack_data.cable_entry: - self._fill_combobox_field("Cable entry", rack_data.cable_entry, - RackLocators.RACK_CABLE_ENTRY_FIELD) - logger.info(f"Selected cable entry: {rack_data.cable_entry}") + self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry) + logger.debug(f"Selected cable entry: {rack_data.cable_entry}") if rack_data.state: - self._fill_combobox_field("State", rack_data.state, - RackLocators.RACK_STATE_FIELD) - logger.info(f"Selected state: {rack_data.state}") + self._fill_combobox_field("Состояние", rack_data.state) + logger.debug(f"Selected state: {rack_data.state}") if rack_data.owner: - self._fill_combobox_field("Owner", rack_data.owner, - RackLocators.RACK_OWNER_FIELD) - logger.info(f"Selected owner: {rack_data.owner}") + self._fill_combobox_field("Владелец", rack_data.owner) + logger.debug(f"Selected owner: {rack_data.owner}") if rack_data.service_org: - self._fill_combobox_field("Service organization", rack_data.service_org, - RackLocators.RACK_SERVICE_ORG_FIELD) - logger.info(f"Selected service organization: {rack_data.service_org}") + self._fill_combobox_field("Обслуживающая организация", rack_data.service_org) + logger.debug(f"Selected service organization: {rack_data.service_org}") if rack_data.project: - self._fill_combobox_field("Project/Title", rack_data.project, - RackLocators.RACK_PROJECT_FIELD) - logger.info(f"Selected project/title: {rack_data.project}") + self._fill_combobox_field("Проект/Титул", rack_data.project) + logger.debug(f"Selected project/title: {rack_data.project}") - def _fill_combobox_field(self, field_name: str, value: str, field_locator: str) -> None: + def _fill_combobox_field(self, field_name: str, value: str) -> None: """ Заполняет combobox поле. Args: field_name: Название поля value: Значение для установки - field_locator: Локатор поля """ - logger.info(f"Filling field '{field_name}' with value '{value}'...") + logger.debug(f"Filling field '{field_name}' with value '{value}'...") - # Используем first() для избежания strict mode violation - field_container = self.page.locator(field_locator).first + # Используем универсальный локатор для combobox по имени поля + combobox_locator = RackLocators.COMBOBOX_BY_FIELD_NAME.format(field_name) + field_container = self.page.locator(combobox_locator).first # Прокручиваем до поля field_container.scroll_into_view_if_needed() @@ -151,12 +145,58 @@ class RackObjectMaker(BaseComponent): 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") + # Вводим значение из выпадающего списка + dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value) + element = self.page.locator(dropdown_item_locator).first - logger.info(f"Field '{field_name}' filled successfully") + # Скроллим к элементу если нужно + 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") + + def _scroll_until_element(self, locator: Locator, name: str) -> None: + """ + Скроллит список до тех пор, пока не перестанут подгружаться новые элементы. + + Args: + locator: Локатор элементов или строка с CSS/XPath. + """ + + loc = self.get_locator(locator) + + items_count = 0 + attempts = 0 + max_attempts = 3 + last_item_name = "" + + while attempts < max_attempts: + self.page.wait_for_timeout(300) + + item_texts = loc.all_inner_texts() + item_names = item_texts[0].splitlines() + current_count = len(item_names) + + if current_count == items_count: + attempts += 1 + else: + items_count = current_count + attempts = 0 + + if name in item_names: + last_item_name = name + else: + last_item_name = item_names[current_count-1] + element = loc.get_by_role("listitem").filter( + has_text=last_item_name + ) + element.scroll_into_view_if_needed() + + self.wait_for_timeout(300) def _get_field_locator(self, field_name: str) -> str: """ @@ -190,7 +230,7 @@ class RackObjectMaker(BaseComponent): AssertionError: Если какое-либо поле не найдено """ - logger.info("Checking rack fields presence...") + logger.debug("Checking rack fields presence...") # Основные обязательные поля required_fields = [ @@ -215,14 +255,14 @@ class RackObjectMaker(BaseComponent): for field_locator, field_name in required_fields: field = self.page.locator(field_locator).first self.check_visibility(field, f"Required field '{field_name}' not found") - logger.info(f"Required field '{field_name}' found") + logger.debug(f"Required field '{field_name}' found") # Проверяем дополнительные поля for field_locator, field_name in optional_fields: field = self.page.locator(field_locator).first if field.count() > 0 and field.is_visible(): - logger.info(f"Optional field '{field_name}' found") + logger.debug(f"Optional field '{field_name}' found") else: - logger.info(f"Optional field '{field_name}' not found or not visible") + logger.debug(f"Optional field '{field_name}' not found or not visible") - logger.info("All main rack fields are present") + logger.debug("All main rack fields are present") diff --git a/components_derived/frames/create_child_element_frame.py b/components_derived/frames/create_child_element_frame.py index caabd8a..a1167fa 100644 --- a/components_derived/frames/create_child_element_frame.py +++ b/components_derived/frames/create_child_element_frame.py @@ -12,6 +12,7 @@ from components_derived.selection_bar_component import SelectionBarComponent logger = get_logger("CREATE_CHILD_ELEMENT_FRAME") +logger.setLevel("INFO") class CreateChildElementFrame(BaseComponent): """Фрейм создания дочернего элемента.""" @@ -55,10 +56,10 @@ class CreateChildElementFrame(BaseComponent): field_name: Название поля для очистки """ - logger.info(f"Clearing combobox field '{field_name}'...") + logger.debug(f"Clearing combobox field '{field_name}'...") # Получаем локатор поля по его названию - field_locator = self._get_field_locator(field_name) + field_locator = self.get_field_locator(field_name) # Используем метод из SelectionBarComponent self.selection_bar.clear_combobox_field(field_name, field_locator) @@ -66,15 +67,28 @@ class CreateChildElementFrame(BaseComponent): def click_add_button(self) -> None: """Кликает на кнопку 'Добавить'.""" - logger.info("Clicking on 'Add' button...") + logger.debug("Clicking on 'Add' button...") self.toolbar.click_button("add") def click_cancel_button(self) -> None: """Кликает на кнопку 'Отменить'.""" - logger.info("Clicking on 'Cancel' button...") + logger.debug("Clicking on 'Cancel' button...") self.toolbar.click_button("cancel") + def get_field_locator(self, field_name: str) -> str: + """ + Возвращает локатор поля по его названию. + Публичный метод для использования в тестах. + + Args: + field_name: Название поля + + Returns: + str: Локатор поля + """ + return self._get_field_locator(field_name) + def get_object_class_options(self) -> list[str]: """ Получает список доступных опций из combobox. @@ -83,11 +97,11 @@ class CreateChildElementFrame(BaseComponent): list[str]: Список доступных классов объектов """ - logger.info("Getting combobox 'Accounting object class' options...") + logger.debug("Getting combobox 'Accounting object class' options...") available_options = self.selection_bar.get_available_options() - logger.info(f"Available object class options: {available_options}") + logger.debug(f"Available object class options: {available_options}") return available_options def get_selected_object_class(self) -> str: @@ -103,7 +117,7 @@ class CreateChildElementFrame(BaseComponent): def open_object_class_combobox(self) -> None: """Открывает выпадающий список combobox 'Класс объекта учета'.""" - logger.info("Opening combobox 'Accounting object class'...") + logger.debug("Opening combobox 'Accounting object class'...") # Ждем стабильности combobox expect(self.selection_bar.selection_bar_locator).to_be_visible() @@ -113,11 +127,11 @@ class CreateChildElementFrame(BaseComponent): "class" ) if is_menu_active and "v-select--is-menu-active" in is_menu_active: - logger.info("Dropdown list is already open") + logger.debug("Dropdown list is already open") return # Используем force click для обхода перекрывающих элементов - logger.info("Using force click for combobox") + logger.debug("Using force click for combobox") self.selection_bar.selection_bar_locator.click(force=True) # Ждем появления выпадающего списка @@ -126,7 +140,7 @@ class CreateChildElementFrame(BaseComponent): def select_object_class(self, class_name: str) -> None: """Выбирает класс объекта из выпадающего списка.""" - logger.info(f"Selecting object class: '{class_name}'...") + logger.debug(f"Selecting object class: '{class_name}'...") # Открываем combobox self.open_object_class_combobox() @@ -139,12 +153,12 @@ class CreateChildElementFrame(BaseComponent): # Логируем текущее состояние без строгой проверки selected_value = self.get_selected_object_class() - logger.info(f"Current combobox value: '{selected_value}'") + logger.debug(f"Current combobox value: '{selected_value}'") # Временно пропускаем строгую проверку - logger.info(f"Assuming class '{class_name}' is selected") + logger.debug(f"Assuming class '{class_name}' is selected") - logger.info(f"Object class '{class_name}' successfully selected") + logger.debug(f"Object class '{class_name}' successfully selected") # Проверки: @@ -156,7 +170,7 @@ class CreateChildElementFrame(BaseComponent): field_name: Название поля для проверки """ - field_locator = self._get_field_locator(field_name) + field_locator = self.get_field_locator(field_name) self.selection_bar.check_field_error_highlighted(field_name, field_locator) def check_field_error_not_highlighted(self, field_name: str) -> None: @@ -167,7 +181,7 @@ class CreateChildElementFrame(BaseComponent): field_name: Название поля для проверки """ - field_locator = self._get_field_locator(field_name) + field_locator = self.get_field_locator(field_name) self.selection_bar.check_field_error_not_highlighted(field_name, field_locator) def check_object_class_selected(self, expected_class: str) -> None: @@ -178,7 +192,7 @@ class CreateChildElementFrame(BaseComponent): expected_class: Ожидаемый выбранный класс объекта """ - logger.info(f"Checking selected object class: '{expected_class}'...") + logger.debug(f"Checking selected object class: '{expected_class}'...") self.wait_for_timeout(1000) actual_class = self.get_selected_object_class() @@ -191,7 +205,7 @@ class CreateChildElementFrame(BaseComponent): f"Expected: '{expected_class}', Got: '{actual_class}'" ) - logger.info( + logger.debug( f"Object class '{expected_class}' successfully selected " f"(actual: '{actual_class}')" ) @@ -204,7 +218,7 @@ class CreateChildElementFrame(BaseComponent): expected_title: Ожидаемый заголовок тулбара """ - logger.info(f"Checking toolbar title: '{expected_title}'...") + logger.debug(f"Checking toolbar title: '{expected_title}'...") # Используем метод тулбара с фильтрацией по тексту actual_text = self.toolbar.get_toolbar_title_text( @@ -216,7 +230,7 @@ class CreateChildElementFrame(BaseComponent): f"Got: '{actual_text}'" ) - logger.info(f"Toolbar title is correct: '{actual_text}'") + logger.debug(f"Toolbar title is correct: '{actual_text}'") def should_be_toolbar_buttons(self) -> None: """ diff --git a/locators/rack_locators.py b/locators/rack_locators.py index 50df5f8..ba31f57 100644 --- a/locators/rack_locators.py +++ b/locators/rack_locators.py @@ -27,26 +27,36 @@ class RackLocators: EDIT_BUTTON ="//button[@data-testid='CABINET_SHOW__btn__edit']" # Кнопка "Скрыть стойку" - HIDE_RACK_BUTTON = "//div[@data-testid='CABINET_SHOW__div__hideCabinet' and contains(@class, 'cabinet_hide_button_trigger_show')]" + HIDE_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and " + "contains(@class, 'cabinet_hide_button_trigger_show')]") # Кнопка "Показать стойку" - SHOW_RACK_BUTTON = "//div[@data-testid='CABINET_SHOW__div__hideCabinet' and contains(@class, 'cabinet_hide_button_trigger_hide')]" + SHOW_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and " + "contains(@class, 'cabinet_hide_button_trigger_hide')]") # Универсальный локатор для любой вкладки по имени - TAB_BY_NAME = "//div[starts-with(@data-testid, 'CABINET_SHOW__') and contains(@class, 'v-tabs__div')]//a[contains(@class, 'v-tabs__item') and .//*[contains(., '{}')]]" + TAB_BY_NAME = ("//div[starts-with(@data-testid, 'CABINET_SHOW__') and " + "contains(@class, 'v-tabs__div')]//a[contains(@class, 'v-tabs__item') and " + ".//*[contains(., '{}')]]") # Конкретные вкладки по тексту - COMPOSITION_TAB = "//div[@data-testid='CABINET_SHOW__composition_tab']//a[contains(@class, 'v-tabs__item')]" - GENERAL_INFO_TAB = "//div[@data-testid='CABINET_SHOW__main_tab']//a[contains(@class, 'v-tabs__item')]" - MAINTENANCE_TAB = "//div[@data-testid='CABINET_SHOW__service_tab']//a[contains(@class, 'v-tabs__item')]" - EVENTS_TAB = "//div[@data-testid='CABINET_SHOW__events_tab']//a[contains(@class, 'v-tabs__item')]" - SERVICES_TAB = "//div[@data-testid='CABINET_SHOW__services_tab']//a[contains(@class, 'v-tabs__item')]" + COMPOSITION_TAB = ("//div[@data-testid='CABINET_SHOW__composition_tab']" + "//a[contains(@class, 'v-tabs__item')]") + GENERAL_INFO_TAB = ("//div[@data-testid='CABINET_SHOW__main_tab']" + "//a[contains(@class, 'v-tabs__item')]") + MAINTENANCE_TAB = ("//div[@data-testid='CABINET_SHOW__service_tab']" + "//a[contains(@class, 'v-tabs__item')]") + EVENTS_TAB = ("//div[@data-testid='CABINET_SHOW__events_tab']" + "//a[contains(@class, 'v-tabs__item')]") + SERVICES_TAB = ("//div[@data-testid='CABINET_SHOW__services_tab']" + "//a[contains(@class, 'v-tabs__item')]") # Классы для проверки активности ACTIVE_TAB_CLASSES = ["accent--text", "v-tabs__item--active"] # Локатор для активной вкладки - ACTIVE_TAB = "//div[@data-testid='CABINET_SHOW__tabs']//a[contains(@class, 'v-tabs__item--active')]" + ACTIVE_TAB = ("//div[@data-testid='CABINET_SHOW__tabs']" + "//a[contains(@class, 'v-tabs__item--active')]") # Контейнер формы FORM_CONTAINER = "//div[contains(@class, 'container')]" @@ -63,18 +73,49 @@ class RackLocators: PROJECT_FIELD = "//input[@aria-label='Проект/Титул']" # Локаторы полей формы создания стойки - RACK_NAME_FIELD = "//div[contains(@class, 'container')]//label[text()='Имя']/following-sibling::input" - RACK_HEIGHT_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Высота в юнитах']]" - RACK_DEPTH_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Глубина (мм)']]" - RACK_SERIAL_FIELD = "//div[contains(@class, 'container')]//label[text()='Серийный номер']/following-sibling::input" - RACK_INVENTORY_FIELD = "//div[contains(@class, 'container')]//label[text()='Инвентарный номер']/following-sibling::input" - RACK_COMMENT_FIELD = "//div[contains(@class, 'container')]//label[text()='Комментарий']/following-sibling::input" - RACK_CABLE_ENTRY_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Ввод кабеля']]" - RACK_STATE_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Состояние']]" + RACK_NAME_FIELD = ("//div[contains(@class, 'container')]" + "//label[text()='Имя']/following-sibling::input") + RACK_HEIGHT_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Высота в юнитах']]") + RACK_DEPTH_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Глубина (мм)']]") + RACK_SERIAL_FIELD = ("//div[contains(@class, 'container')]" + "//label[text()='Серийный номер']/following-sibling::input") + RACK_INVENTORY_FIELD = ("//div[contains(@class, 'container')]" + "//label[text()='Инвентарный номер']/following-sibling::input") + RACK_COMMENT_FIELD = ("//div[contains(@class, 'container')]" + "//label[text()='Комментарий']/following-sibling::input") + RACK_CABLE_ENTRY_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Ввод кабеля']]") + RACK_STATE_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Состояние']]") - RACK_OWNER_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Владелец']]" - RACK_SERVICE_ORG_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Обслуживающая организация']]" - RACK_PROJECT_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Проект/Титул']]" + RACK_OWNER_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Владелец']]") + RACK_SERVICE_ORG_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Обслуживающая организация']]") + RACK_PROJECT_FIELD = ("//div[contains(@class, 'container')]" + "//div[contains(@class, 'v-input__slot') and " + ".//label[text()='Проект/Титул']]") + + # Универсальные локаторы для поиска combobox полей по имени + COMBOBOX_BY_FIELD_NAME = ('//form[contains(@class, "v-form")]' + '//div[@role="combobox"][.//label[contains(text(), "{}")]]') + COMBOBOX_BY_LABEL = 'form.v-form div[role="combobox"]:has(label:has-text("{}"))' + + # Локаторы для выпадающего меню + ACTIVE_MENU = 'div.menuable__content__active' + DROPDOWN_LIST = 'div.menuable__content__active div[role="list"]' + DROPDOWN_ITEM_BY_TEXT = ('div.menuable__content__active ' + 'div[role="listitem"]:has(span:has-text("{}"))') + DROPDOWN_ITEM_XPATH = ('//div[contains(@class, "menuable__content__active")]' + '//div[@role="list"]//div[@role="listitem"][.//*[text()="{}"]]') # Локатор для родительского контейнера поля ввода INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]" @@ -85,7 +126,8 @@ class RackLocators: # ================ ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОЙКИ =================== # Общий контейнер стойки (включает кнопки переключения сторон и MAIN_CONTAINER) - RACK_CONTAINER = "//div[contains(@class, 'layout active') and contains(@class, 'row') and contains(@class, 'shrink')]" + RACK_CONTAINER = ("//div[contains(@class, 'layout active') and " + "contains(@class, 'row') and contains(@class, 'shrink')]") # Основной контейнер стойки (изображение стойки) MAIN_CONTAINER = "//div[contains(@class, 'layout cabinet')]" @@ -99,7 +141,8 @@ class RackLocators: INACTIVE_SIDE_BUTTON = "//button[contains(@class, 'secondary--text')]" # Для получения текста активной стороны - ACTIVE_SIDE_BUTTON_TEXT = "//button[contains(@class, 'primary--text')]//div[contains(@class, 'v-btn__content')]" + ACTIVE_SIDE_BUTTON_TEXT = ("//button[contains(@class, 'primary--text')]" + "//div[contains(@class, 'v-btn__content')]") # Кнопка добавления (add_circle) ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]" @@ -108,11 +151,12 @@ class RackLocators: ALL_UNITS = "//div[contains(@class, 'unit')]" # Позиции юнитов - UNIT_POSITIONS = "//div[contains(@class, 'headline') and contains(@class, 'test-xs-center') and contains(@class, 'unit-positions')]" + UNIT_POSITIONS = ("//div[contains(@class, 'headline') and " + "contains(@class, 'test-xs-center') and " + "contains(@class, 'unit-positions')]") # Локатор для устройств DEVICE_ELEMENTS = "//div[contains(@class, 'parent-class')]" # Локатор для слотов в устройствах DEVICE_SLOTS = "//div[contains(@class, 'slot')]" - diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py index ad19db6..01da557 100644 --- a/tests/e2e/create_elements/test_create_rack_element.py +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -13,6 +13,8 @@ from pages.main_page import MainPage logger = get_logger("CREATE_RACK_ELEMENT_TEST") +logger.setLevel("INFO") + # @pytest.mark.smoke class TestCreateRackElement: """Тест создания дочернего элемента типа 'Стойка'. @@ -55,7 +57,7 @@ class TestCreateRackElement: # Создаем экземпляр страницы локации self.location_page = LocationPage(browser) - @pytest.mark.develop + #@pytest.mark.develop def test_create_rack_content(self, browser: Page) -> None: """Тест создания дочернего элемента типа 'Стойка'.""" @@ -82,7 +84,7 @@ class TestCreateRackElement: # Проверяем что после выбора 'Стойка' появляются специфичные поля rack_maker.check_rack_fields_presence() - logger.info("Rack-specific fields are displayed correctly") + logger.debug("Rack-specific fields are displayed correctly") create_child_frame.should_be_toolbar_buttons() @@ -112,8 +114,8 @@ class TestCreateRackElement: serial="TEST123456", inventory="INV-001", comment="Тестовая стойка для автоматизации", - cable_entry="Сверху", - state="В эксплуатации" + state="Введен в эксплуатацию", + cable_entry="сверху" ) # Заполняем данные стойки @@ -123,7 +125,7 @@ class TestCreateRackElement: create_child_frame.click_add_button() create_child_frame.wait_for_timeout(2000) - logger.info("Test for creating 'Rack' child element completed successfully") + logger.debug("Test for creating 'Rack' child element completed successfully") def test_create_rack_with_duplicate_name(self, browser: Page) -> None: """ @@ -133,20 +135,20 @@ class TestCreateRackElement: стойки с именем, которое уже используется. """ - logger.info("Starting test for creating rack with duplicate name") + logger.debug("Starting test for creating rack with duplicate name") rack_name = "Test-Rack-01" # Проверяем, существует ли уже стойка с таким именем if not self._check_rack_existance(browser, rack_name): - logger.info(f"Rack with name '{rack_name}' not found. Creating first rack.") + logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.") self._create_rack(browser, rack_name) - logger.info(f"First rack with name '{rack_name}' created successfully") + logger.debug(f"First rack with name '{rack_name}' created successfully") else: - logger.info(f"Rack with name '{rack_name}' already exists, proceeding to create second one") + logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one") # Создаем вторую стойку с тем же именем - logger.info(f"Attempting to create second rack with name '{rack_name}'") + logger.debug(f"Attempting to create second rack with name '{rack_name}'") # Переходим обратно к созданию новой стойки self.main_page.click_main_navigation_panel_item("test-zone") @@ -192,7 +194,7 @@ class TestCreateRackElement: create_child_frame.wait_for_timeout(2000) create_child_frame.alert.close_alert_by_text(expected_alert_text) - logger.info("System prevented creating rack with duplicate name") + logger.debug("System prevented creating rack with duplicate name") def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data): """Выполняет один тест валидации обязательных полей. @@ -215,13 +217,13 @@ class TestCreateRackElement: """Проверяет, заполнено ли combobox поле.""" # Получаем локатор поля - field_locator = create_child_frame._get_field_locator(field_name) + field_locator = create_child_frame.get_field_locator(field_name) # Находим элемент поля field_element = create_child_frame.page.locator(field_locator).first if not field_element.is_visible(): - logger.info(f"Field '{field_name}' not visible") + logger.debug(f"Field '{field_name}' not visible") return False # Проверяем наличие кнопки закрытия (крестика) - признак заполненного поля @@ -236,27 +238,27 @@ class TestCreateRackElement: field_text = field_element.text_content() or "" has_text = bool(field_text.strip()) - logger.info(f"Field '{field_name}' - has close button: {has_close_button}, has text: {has_text}") + logger.debug(f"Field '{field_name}' - has close button: {has_close_button}, has text: {has_text}") return has_close_button or has_text # Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено - logger.info("Checking field: Depth (mm)") + logger.debug("Checking field: Depth (mm)") if is_field_filled("Глубина (мм)"): - logger.info("Field 'Depth (mm)' is filled, performing clearing") + logger.debug("Field 'Depth (mm)' is filled, performing clearing") create_child_frame.clear_combobox_field("Глубина (мм)") - logger.info("Clearing completed for 'Depth (mm)'") + logger.debug("Clearing completed for 'Depth (mm)'") else: - logger.info("Field 'Depth (mm)' is already empty, skipping clearing") + logger.debug("Field 'Depth (mm)' is already empty, skipping clearing") # Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено - logger.info("Checking field: Height in units") + logger.debug("Checking field: Height in units") if is_field_filled("Высота в юнитах"): - logger.info("Field 'Height in units' is filled, performing clearing") + logger.debug("Field 'Height in units' is filled, performing clearing") create_child_frame.clear_combobox_field("Высота в юнитах") - logger.info("Clearing completed for 'Height in units'") + logger.debug("Clearing completed for 'Height in units'") else: - logger.info("Field 'Height in units' is already empty, skipping clearing") + logger.debug("Field 'Height in units' is already empty, skipping clearing") # Создаем объект данных стойки rack_data = RackData( @@ -266,47 +268,47 @@ class TestCreateRackElement: ) # Заполняем данные стойки - logger.info(f"Setting test data - 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.info("Submitting form for validation") + logger.debug("Submitting form for validation") create_child_frame.click_add_button() create_child_frame.wait_for_timeout(3000) # Проверяем валидацию полей - logger.info("Checking validation results") + logger.debug("Checking validation results") if height_value: create_child_frame.check_field_error_not_highlighted("Высота в юнитах") - logger.info("Height field validation passed") + logger.debug("Height field validation passed") else: create_child_frame.check_field_error_highlighted("Высота в юнитах") - logger.info("Height field validation failed as expected") + logger.debug("Height field validation failed as expected") if depth_value: create_child_frame.check_field_error_not_highlighted("Глубина (мм)") - logger.info("Depth field validation passed") + logger.debug("Depth field validation passed") else: create_child_frame.check_field_error_highlighted("Глубина (мм)") - logger.info("Depth field validation failed as expected") + logger.debug("Depth field validation failed as expected") # Обрабатываем alert-окна if not height_value: - logger.info("Expecting height validation alert") + 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.info("Height alert handled") + logger.debug("Height alert handled") if not depth_value: - logger.info("Expecting depth validation alert") + 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.info("Depth alert handled") + logger.debug("Depth alert handled") # Проверяем, что остались на странице создания create_child_frame.check_toolbar_title('Создать дочерний элемент в') - logger.info("Test completed successfully") + logger.debug("Test completed successfully") def test_required_fields_validation(self, browser: Page) -> None: """ @@ -382,14 +384,14 @@ class TestCreateRackElement: # Выполняем тестовые случаи for test_case in test_cases: - logger.info(test_case["name"]) + logger.debug(test_case["name"]) self._perform_required_fields_test( create_child_frame, rack_maker, test_case["data"] ) - logger.info("System prevented creating rack with invalid required fields") + logger.debug("System prevented creating rack with invalid required fields") # 5. Тест: Заполняем все обязательные поля - logger.info("Test 5: All required fields are filled") + logger.debug("Test 5: All required fields are filled") # Генерируем уникальное имя для финального теста final_rack_name = "Test-Rack-Required-Final" @@ -408,7 +410,7 @@ class TestCreateRackElement: create_child_frame.check_field_error_not_highlighted("Имя") create_child_frame.check_field_error_not_highlighted("Высота в юнитах") create_child_frame.check_field_error_not_highlighted("Глубина (мм)") - logger.info("No required fields are highlighted with error color - all fields filled correctly") + logger.debug("No required fields are highlighted with error color - all fields filled correctly") # Нажимаем кнопку создания create_child_frame.click_add_button() @@ -417,21 +419,21 @@ class TestCreateRackElement: # Проверяем, что НЕТ 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.info("No alert windows for required fields appeared - all fields filled correctly") + logger.debug("No alert windows for required fields appeared - all fields filled correctly") # Проверяем, что ушли со страницы создания try: create_child_frame.check_toolbar_title('Создать дочерний элемент в') logger.warning("Rack creation may not have completed successfully") except AssertionError: - logger.info("Creation page closed - rack successfully created") + logger.debug("Creation page closed - rack successfully created") - logger.info("Required fields validation test completed successfully") + logger.debug("Required fields validation test completed successfully") def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: """Проверяет существование стойки.""" - logger.info(f"Checking existence of rack with name '{rack_name}'") + logger.debug(f"Checking existence of rack with name '{rack_name}'") # Обновляем навигационную панель self.main_page.click_main_navigation_panel_item("Объекты") @@ -446,16 +448,16 @@ class TestCreateRackElement: element = browser.locator(nav_panel_locator).get_by_text(rack_name).first if element.is_visible(): - logger.info(f"Rack with name '{rack_name}' found") + logger.debug(f"Rack with name '{rack_name}' found") return True - logger.info(f"Rack with name '{rack_name}' not found") + logger.debug(f"Rack with name '{rack_name}' not found") return False def _create_rack(self, browser: Page, rack_name: str) -> None: """Создает стойку.""" - logger.info(f"Creating rack with name '{rack_name}'") + logger.debug(f"Creating rack with name '{rack_name}'") # Переходим обратно к созданию новой стойки self.main_page.click_main_navigation_panel_item("test-zone")