From e4aba34421cf430a047a3fbdeff1af38566d1f75 Mon Sep 17 00:00:00 2001 From: Radislav Date: Mon, 17 Nov 2025 08:44:21 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D1=81=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=A1=D1=82=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- components/alert_component.py | 57 +- components/navbar_component.py | 17 + components/toolbar_component.py | 2 +- docs/locators/rack_locators.md | 6 + docs/locators/settings_form_locators.md | 6 + docs/pages/current_session_tab.md | 6 + docs/pages/session_settings_tab.md | 6 + docs/tests/e2e/rack/test_rack_tab.md | 6 + .../e2e/sessions/test_current_sessions_tab.md | 6 + .../e2e/sessions/test_session_settings_tab.md | 6 + locators/alert_locators.py | 23 + locators/combobox_locators.py | 33 + .../create_child_element_tab.cpython-313.pyc | Bin 0 -> 19049 bytes .../create_rack_element_tab.cpython-313.pyc | Bin 0 -> 34051 bytes .../create_child_element_tab.py | 355 ++++++++++ .../create_rack_element_tab.py | 659 ++++++++++++++++++ .../__pycache__/rack_create.cpython-313.pyc | Bin 0 -> 21880 bytes .../rack_general_info.cpython-313.pyc | Bin 0 -> 5923 bytes .../__pycache__/rack_tab.cpython-313.pyc | Bin 0 -> 26153 bytes pages/{rack_pages => rack_tab}/rack_tab.py | 0 pages/users_tab.py | 2 +- ...child_element.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 9111 bytes ..._rack_element.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 14228 bytes .../test_create_child_element.py | 129 ++++ .../test_create_rack_element.py | 362 ++++++++++ ...t_create_rack.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 5769 bytes ..._general_info.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 3030 bytes ...eneral_info__.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 2828 bytes ...test_rack_tab.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 5904 bytes ..._sessions_tab.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 26177 bytes ..._settings_tab.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 9950 bytes tests/e2e/users/__init__.py | 2 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 322 bytes ...test_add_user.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 9207 bytes ...est_edit_user.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 11345 bytes ...est_user_card.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 12843 bytes ...est_users_tab.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 3346 bytes 38 files changed, 1673 insertions(+), 12 deletions(-) create mode 100644 docs/locators/rack_locators.md create mode 100644 docs/locators/settings_form_locators.md create mode 100644 docs/pages/current_session_tab.md create mode 100644 docs/pages/session_settings_tab.md create mode 100644 docs/tests/e2e/rack/test_rack_tab.md create mode 100644 docs/tests/e2e/sessions/test_current_sessions_tab.md create mode 100644 docs/tests/e2e/sessions/test_session_settings_tab.md create mode 100644 locators/alert_locators.py create mode 100644 locators/combobox_locators.py create mode 100644 pages/create_elements_tab/__pycache__/create_child_element_tab.cpython-313.pyc create mode 100644 pages/create_elements_tab/__pycache__/create_rack_element_tab.cpython-313.pyc create mode 100644 pages/create_elements_tab/create_child_element_tab.py create mode 100644 pages/create_elements_tab/create_rack_element_tab.py create mode 100644 pages/rack_tab/__pycache__/rack_create.cpython-313.pyc create mode 100644 pages/rack_tab/__pycache__/rack_general_info.cpython-313.pyc create mode 100644 pages/rack_tab/__pycache__/rack_tab.cpython-313.pyc rename pages/{rack_pages => rack_tab}/rack_tab.py (100%) create mode 100644 tests/e2e/create_elements/__pycache__/test_create_child_element.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/create_elements/__pycache__/test_create_rack_element.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/create_elements/test_create_child_element.py create mode 100644 tests/e2e/create_elements/test_create_rack_element.py create mode 100644 tests/e2e/rack/__pycache__/test_create_rack.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/rack/__pycache__/test_rack_general_info.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/rack/__pycache__/test_rack_general_info__.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/rack/__pycache__/test_rack_tab.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/sessions/__pycache__/test_current_sessions_tab.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/sessions/__pycache__/test_session_settings_tab.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/users/__init__.py create mode 100644 tests/e2e/users/__pycache__/__init__.cpython-313.pyc create mode 100644 tests/e2e/users/__pycache__/test_add_user.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/users/__pycache__/test_edit_user.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/users/__pycache__/test_user_card.cpython-313-pytest-8.4.1.pyc create mode 100644 tests/e2e/users/__pycache__/test_users_tab.cpython-313-pytest-8.4.1.pyc diff --git a/.env b/.env index f4ddf1d..7571ece 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -ENV=develop +ENV=test AUTH_LOGIN = admin AUTH_PASSWORD = enodemon-admin diff --git a/components/alert_component.py b/components/alert_component.py index 2bc1437..966c286 100644 --- a/components/alert_component.py +++ b/components/alert_component.py @@ -8,6 +8,7 @@ from playwright.sync_api import Page, expect from tools.logger import get_logger from elements.text_element import Text from components.base_component import BaseComponent +from locators.alert_locators import AlertLocators logger = get_logger("ALERT") @@ -19,7 +20,7 @@ class AlertComponent(BaseComponent): Позволяет проверять наличие, отсутствие и текст сообщений. """ - def __init__(self, page: Page): + def __init__(self, page: Page) -> None: """Инициализирует компонент alert-окна. Args: @@ -28,7 +29,7 @@ class AlertComponent(BaseComponent): super().__init__(page) - self.text = Text(page, "//div[contains(@class,'v-alert')]/div", "Alert message") + self.text = Text(page, AlertLocators.ALERT_MESSAGE, "Alert message") # Действия: def get_alert_type(self) -> str: @@ -41,7 +42,7 @@ class AlertComponent(BaseComponent): ValueError: Если получен неподдерживаемый тип alert-окна. """ - class_attr = self.page.get_by_role("alert").locator('>div').get_attribute('class') + class_attr = self.page.get_by_role(AlertLocators.ALERT_ROLE).locator('>div').get_attribute('class') alert_type = None if 'v-alert' in class_attr: @@ -62,8 +63,38 @@ class AlertComponent(BaseComponent): return self.text.get_text(0) + def close_alert_by_text(self, text: str) -> None: + """Закрывает alert-окно с заданным текстом с помощью кнопки закрытия. + + Args: + text: Текст alert-окна, которое нужно закрыть. + + Raises: + AssertionError: Если не удалось найти или закрыть alert-окно. + """ + # Находим alert с нужным текстом + alert_locator = self.page.get_by_role(AlertLocators.ALERT_ROLE).filter(has_text=text) + + # Проверяем, что alert видим + expect(alert_locator).to_be_visible() + + # Находим кнопку закрытия внутри alert + close_button = alert_locator.locator(AlertLocators.ALERT_DISMISS_BUTTON) + + # Проверяем, что кнопка закрытия доступна и кликаем + expect(close_button).to_be_visible() + expect(close_button).to_be_enabled() + + # Кликаем по кнопке закрытия + close_button.click() + + # Проверяем, что alert исчез после закрытия + expect(alert_locator).to_be_hidden() + + logger.info(f"Alert with text '{text}' closed successfully") + # Проверки: - def check_alert_presence(self, text: str): + def check_alert_presence(self, text: str) -> None: """Проверяет наличие alert-окна с заданным текстом. Args: @@ -76,11 +107,13 @@ class AlertComponent(BaseComponent): msg = "Alert window is missing" if text == "": - expect(self.page.get_by_role("alert")).to_be_visible(), msg + expect(self.page.get_by_role(AlertLocators.ALERT_ROLE)).to_be_visible(), msg + logger.info("Alert window successfully displayed") else: - expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_visible(), msg + expect(self.page.get_by_role(AlertLocators.ALERT_ROLE).filter(has_text=text)).to_be_visible(), msg + logger.info(f"Alert window with text '{text}' successfully displayed") - def check_alert_absence(self, text: str, timeout: int = 30000): + def check_alert_absence(self, text: str, timeout: int = 30000) -> None: """Проверяет отсутствие alert-окна с заданным текстом. Args: @@ -93,9 +126,15 @@ class AlertComponent(BaseComponent): seconds = int(timeout/1000) msg = f"Alert window should disappear after {seconds} seconds" - expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_hidden(timeout=timeout), msg - def check_text(self, alert_text: str): + if text == "": + expect(self.page.get_by_role(AlertLocators.ALERT_ROLE)).to_be_hidden(timeout=timeout), msg + logger.info("Alert window successfully disappeared") + else: + expect(self.page.get_by_role(AlertLocators.ALERT_ROLE).filter(has_text=text)).to_be_hidden(timeout=timeout), msg + logger.info(f"Alert window with text '{text}' successfully disappeared") + + def check_text(self, alert_text: str) -> None: """Проверяет точное соответствие текста в alert-окне. Args: diff --git a/components/navbar_component.py b/components/navbar_component.py index 6825859..6c9205a 100644 --- a/components/navbar_component.py +++ b/components/navbar_component.py @@ -235,3 +235,20 @@ class NavigationPanelComponent(BaseComponent): else: loc = loc.get_by_text(item_name) self.check_visibility(loc, msg) + + def is_item_visible(self, locator: str | Locator, item_name: str) -> bool: + """ + Проверяет видимость элемента с указанным текстом без выбрасывания исключения. + + Args: + locator: Локатор элемента или строка с CSS/XPath. + item_name: Текст элемента для проверки. + + Returns: + bool: True если элемент видим, False если нет. + """ + try: + self.check_item_visibility(locator, item_name) + return True + except: + return False diff --git a/components/toolbar_component.py b/components/toolbar_component.py index e7d5a39..06c509c 100644 --- a/components/toolbar_component.py +++ b/components/toolbar_component.py @@ -95,7 +95,7 @@ class ToolbarComponent(BaseComponent): raise AssertionError(f"Unsupported button name {name}") button.click() - def get_toolbar_title_text(self, locator: str = 'ToolbarLocators.TITLE', + def get_toolbar_title_text(self, locator: str = ToolbarLocators.TITLE, filter_text: str = None, timeout: int = 5000) -> str: """Получает заголовок тулбара окна. diff --git a/docs/locators/rack_locators.md b/docs/locators/rack_locators.md new file mode 100644 index 0000000..0ce8952 --- /dev/null +++ b/docs/locators/rack_locators.md @@ -0,0 +1,6 @@ +# RackLocators + +::: locators.rack_locators + handler: python + options: + show_source: true \ No newline at end of file diff --git a/docs/locators/settings_form_locators.md b/docs/locators/settings_form_locators.md new file mode 100644 index 0000000..b8a5c3b --- /dev/null +++ b/docs/locators/settings_form_locators.md @@ -0,0 +1,6 @@ +# SettingsFormLocators + +::: locators.settings_form_locators + handler: python + options: + show_source: true \ No newline at end of file diff --git a/docs/pages/current_session_tab.md b/docs/pages/current_session_tab.md new file mode 100644 index 0000000..5875023 --- /dev/null +++ b/docs/pages/current_session_tab.md @@ -0,0 +1,6 @@ +# CurrentSessionsTab + +::: pages.current_session_tab + handler: python + options: + show_source: true \ No newline at end of file diff --git a/docs/pages/session_settings_tab.md b/docs/pages/session_settings_tab.md new file mode 100644 index 0000000..908cabd --- /dev/null +++ b/docs/pages/session_settings_tab.md @@ -0,0 +1,6 @@ +# SessionSettingsTab + +::: pages.session_settings_tab + handler: python + options: + show_source: true \ No newline at end of file diff --git a/docs/tests/e2e/rack/test_rack_tab.md b/docs/tests/e2e/rack/test_rack_tab.md new file mode 100644 index 0000000..c60c137 --- /dev/null +++ b/docs/tests/e2e/rack/test_rack_tab.md @@ -0,0 +1,6 @@ +# TestRackTab + +::: tests.e2e.rack.test_rack_tab + handler: python + options: + show_source: true \ No newline at end of file diff --git a/docs/tests/e2e/sessions/test_current_sessions_tab.md b/docs/tests/e2e/sessions/test_current_sessions_tab.md new file mode 100644 index 0000000..c3578f6 --- /dev/null +++ b/docs/tests/e2e/sessions/test_current_sessions_tab.md @@ -0,0 +1,6 @@ +# TestCurrentSessionsTab + +::: tests.e2e.sessions.test_current_sessions_tab + handler: python + options: + show_source: true \ No newline at end of file diff --git a/docs/tests/e2e/sessions/test_session_settings_tab.md b/docs/tests/e2e/sessions/test_session_settings_tab.md new file mode 100644 index 0000000..b33f262 --- /dev/null +++ b/docs/tests/e2e/sessions/test_session_settings_tab.md @@ -0,0 +1,6 @@ +# TestCurrentSettingsTab + +::: tests.e2e.sessions.test_session_settings_tab + handler: python + options: + show_source: true \ No newline at end of file diff --git a/locators/alert_locators.py b/locators/alert_locators.py new file mode 100644 index 0000000..c21b587 --- /dev/null +++ b/locators/alert_locators.py @@ -0,0 +1,23 @@ +"""Модуль alert_locators содержит локаторы элементов alert-окон. + +Класс AlertLocators предоставляет XPath и CSS локаторы для взаимодействия +с alert-окнами (error, success, info, warning) в тестах. +""" + + +class AlertLocators: + """Локаторы элементов alert-окон. + + Содержит XPath и CSS локаторы для: + ALERT_ROLE (str): alert-окон по роли. + ALERT_BASE (str): базового контейнера alert-окон. + ALERT_MESSAGE (str): текстового сообщения в alert-окне. + ALERT_DISMISS_BUTTON (str): кнопки закрытия alert-окна. + ALERT_BY_TEXT (str): alert-окна с определенным текстом (шаблон). + """ + + 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 diff --git a/locators/combobox_locators.py b/locators/combobox_locators.py new file mode 100644 index 0000000..597de62 --- /dev/null +++ b/locators/combobox_locators.py @@ -0,0 +1,33 @@ +"""Модуль combobox_locators содержит локаторы элементов combobox. + +Класс ComboboxLocators предоставляет XPath и CSS локаторы для взаимодействия +с combobox элементами в тестах. +""" + + +class ComboboxLocators: + """Локаторы элементов combobox. + + Содержит XPath и CSS локаторы для: + - Основного combobox класса объекта учета + - Общих элементов combobox (label, input, иконки) + - Выпадающих списков + - Кнопок закрытия + """ + + # Основной combobox класса объекта учета + OBJECT_CLASS_COMBOBOX: str = "//div[@role='combobox' and .//label[text()='Класс объекта учета']]" + + # Общие элементы combobox + COMBOBOX_LABEL: str = "label" + COMBOBOX_INPUT: str = "input[name='entity']" + COMBOBOX_ICON: str = ".v-input__icon--append" + COMBOBOX_ICON_ARROW: str = ".v-input__icon--append .mdi-menu-down" + COMBOBOX_CLOSE_BUTTON: str = "i.mdi-close" + + # Выпадающие списки + LISTBOX_SELECTOR: str = "//div[contains(@class, 'v-menu__content')]//div[@role='list']" + OPTIONS_SELECTOR: str = "//div[contains(@class, 'v-menu__content')]//div[@role='listitem']//span" + + # Получение выбранного значения + SELECTED_VALUE_SPAN: str = "span" diff --git a/pages/create_elements_tab/__pycache__/create_child_element_tab.cpython-313.pyc b/pages/create_elements_tab/__pycache__/create_child_element_tab.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be16468c71cabae6ba3c99e535f72754b4141c64 GIT binary patch literal 19049 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 DO;XDv literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..1178e22652c10ffbe7691266570a1d740ebaf562 GIT binary patch literal 34051 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 literal 0 HcmV?d00001 diff --git a/pages/create_elements_tab/create_child_element_tab.py b/pages/create_elements_tab/create_child_element_tab.py new file mode 100644 index 0000000..178a9bc --- /dev/null +++ b/pages/create_elements_tab/create_child_element_tab.py @@ -0,0 +1,355 @@ +"""Модуль страницы создания дочернего элемента. + +Содержит класс для работы с формой создания дочернего элемента. +""" + +from playwright.sync_api import Page, expect +from elements.tooltip_button_element import TooltipButton +from components.toolbar_component import ToolbarComponent +from components.dropdown_list_component import DropdownList +from pages.base_page import BasePage +from tools.logger import get_logger + +logger = get_logger("CREATE_CHILD_ELEMENT") + +# =============== Локаторы ================================================ +PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i" +TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']" +CREATE_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button" +PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]" + +CREATE_CHILD_TITLE = "//div[contains(@class, 'v-toolbar__title') and contains(., 'Создать дочерний элемент в')]" +OBJECT_CLASS_COMBOBOX = "//div[@role='combobox' and .//label[text()='Класс объекта учета']]" +CANCEL_BUTTON = "//div[contains(@class, 'v-toolbar__title') and contains(., 'Создать дочерний элемент в')]/..//button[contains(@class, 'v-btn--icon')]" + +# Локаторы для работы с combobox +COMBOBOX_LABEL = "label" +COMBOBOX_INPUT = "input[name='entity']" +COMBOBOX_ICON = ".v-input__icon--append" +COMBOBOX_ICON_ARROW = ".v-input__icon--append .mdi-menu-down" + +# Локаторы для выпадающего списка combobox - уточненные +LISTBOX_SELECTOR = "//div[contains(@class, 'v-menu__content')]//div[@role='list']" +OPTIONS_SELECTOR = "//div[contains(@class, 'v-menu__content')]//div[@role='listitem']//span" + +# Локаторы для получения выбранного значения +SELECTED_VALUE_SPAN = "span" +#======================================================================================================== + + +class CreateChildElementTab(BasePage): + """Класс для работы с формой создания дочернего элемента.""" + + def __init__(self, page: Page) -> None: + """ + Инициализирует объект формы создания дочернего элемента. + + Args: + page: Экземпляр страницы Playwright + """ + super().__init__(page) + + # Локаторы для кнопок + panel_header_locator = self.page.locator(PANEL_HEADER) + + # Кнопка "Создать" - первая кнопка в тулбаре + create_button_locator = panel_header_locator.locator(CREATE_BUTTON_ANCESTOR_DIV3).nth(0) + + # Кнопка "Отменить" - ищем глобально на странице + cancel_button_locator = self.page.locator(CANCEL_BUTTON) + + # Инициализация кнопок + self.create_button = TooltipButton(page, create_button_locator, "add") + self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel") + + # Инициализация тулбара с обеими кнопками + self.toolbar = ToolbarComponent(page, "") + self.toolbar.add_tooltip_button(create_button_locator, "add") + self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") + + # Инициализация компонента выпадающего списка + self.dropdown = DropdownList(page) + + def get_toolbar_title(self) -> list[str]: + """ + Получает заголовок панели инструментов. + + Returns: + list[str]: Список элементов заголовка панели инструментов + """ + toolbar_title_locator = self.page.locator(PANEL_HEADER).\ + locator(PANEL_HEADER_ANCESTOR_DIV2).get_by_role("navigation").\ + locator(TOOLBAR_CONTENT) + + return self.toolbar.get_toolbar_composite_title_text(toolbar_title_locator) + + def should_be_toolbar_buttons(self) -> None: + """ + Проверяет наличие и функциональность кнопок тулбара. + + Raises: + AssertionError: Если кнопки недоступны или подсказки неверны. + """ + + self.wait_for_timeout(2000) + + self.toolbar.check_button_visibility("cancel") + self.toolbar.check_button_tooltip("cancel", "Отменить") + self.toolbar.get_button_by_name("cancel").click() + self.wait_for_timeout(2000) + + def click_create_button(self) -> None: + """ + Кликает на кнопку 'Создать'. + """ + logger.info("Клик на кнопку 'Создать'...") + self.toolbar.get_button_by_name("add").click() + + def click_cancel_button(self) -> None: + """ + Кликает на кнопку 'Отменить'. + """ + logger.info("Клик на кнопку 'Отменить'...") + self.toolbar.get_button_by_name("cancel").click() + + def check_toolbar_title(self, expected_title: str) -> None: + """ + Проверяет заголовок тулбара. + + Args: + expected_title: Ожидаемый заголовок тулбара + + Raises: + AssertionError: Если заголовок не соответствует ожидаемому + """ + # Используем метод тулбара с нашим специфичным локатором + self.toolbar.check_toolbar_presence_by_locator(CREATE_CHILD_TITLE, + f"Заголовок тулбара '{expected_title}' не найден") + + # Получаем текст и проверяем его + actual_text = self.toolbar.get_toolbar_title_text(CREATE_CHILD_TITLE) + assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'" + + logger.info(f"Заголовок тулбара корректен: '{actual_text}'") + + def check_object_class_combobox_presence(self) -> None: + """ + Проверяет наличие combobox 'Класс объекта учета'. + + Raises: + AssertionError: Если combobox не найден + """ + logger.info("Проверка наличия combobox 'Класс объекта учета'...") + + combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) + expect(combobox_locator).to_be_visible() + + logger.info("Combobox 'Класс объекта учета' найден") + + def check_object_class_combobox_content(self) -> None: + """ + Проверяет содержимое combobox 'Класс объекта учета'. + + Raises: + AssertionError: Если содержимое не соответствует ожидаемому + """ + logger.info("Проверка содержимого combobox 'Класс объекта учета'...") + + combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) + + # Проверяем что combobox видим + expect(combobox_locator).to_be_visible() + + # Проверяем наличие label + label_locator = combobox_locator.locator(COMBOBOX_LABEL) + expect(label_locator).to_have_text("Класс объекта учета") + + # Проверяем наличие input поля + input_locator = combobox_locator.locator(COMBOBOX_INPUT) + expect(input_locator).to_be_visible() + + # Для combobox нормально иметь readonly атрибут - это стандартное поведение + # Проверяем что поле доступно для выбора (не disabled) + expect(input_locator).not_to_have_attribute("disabled", "disabled") + + # Проверяем наличие иконки стрелки + icon_locator = combobox_locator.locator(COMBOBOX_ICON_ARROW) + expect(icon_locator).to_be_visible() + + logger.info("Содержимое combobox 'Класс объекта учета' корректно") + + def open_object_class_combobox(self) -> None: + """ + Открывает выпадающий список combobox 'Класс объекта учета'. + """ + logger.info("Открытие combobox 'Класс объекта учета'...") + + combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) + listbox_locator = self.page.locator(LISTBOX_SELECTOR) + icon_locator = combobox_locator.locator(COMBOBOX_ICON) + + # Проверяем, не открыт ли уже список + listbox_already_open = False + listbox_count = listbox_locator.count() + + if listbox_count > 0: + listbox_already_open = listbox_locator.first.is_visible() + + if not listbox_already_open: + # Только если список не открыт, кликаем на иконку + icon_locator.click(timeout=10000) + logger.info("Клик на иконку combobox выполнен") + self.wait_for_timeout(1000) + + # Проверяем что список открылся + listbox_count_after = listbox_locator.count() + listbox_visible = False + + if listbox_count_after > 0: + listbox_visible = listbox_locator.first.is_visible() + + if listbox_visible: + logger.info("Выпадающий список найден и открыт") + else: + logger.warning("Не удалось открыть выпадающий список") + + def get_object_class_options(self) -> list[str]: + """ + Получает список доступных опций из combobox. + + Returns: + list[str]: Список доступных классов объектов + """ + logger.info("Получение списка опций combobox 'Класс объекта учета'...") + + # Открываем combobox (если еще не открыт) + self.open_object_class_combobox() + + # Используем метод get_item_names из DropdownList + options_list = self.dropdown.get_item_names(LISTBOX_SELECTOR) + + # Закрываем combobox (кликаем вне его) + self.page.mouse.click(10, 10) + self.wait_for_timeout(500) + + logger.info(f"Найдено опций: {len(options_list)} - {options_list}") + return options_list + + def select_object_class(self, class_name: str) -> None: + """ + Выбирает класс объекта из выпадающего списка. + + Args: + class_name: Название класса объекта для выбора + + Raises: + AssertionError: Если класс не найден в списке + """ + logger.info(f"Выбор класса объекта: '{class_name}'...") + + # Открываем combobox + self.open_object_class_combobox() + + self.dropdown.click_item_with_text(class_name) + + # Проверяем что выбор произошел + self.wait_for_timeout(1000) + selected_value = self.get_selected_object_class() + + if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower(): + # Если выбор не произошел, получаем доступные опции для отладки + available_options = self.get_object_class_options() + logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}") + raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'") + + logger.info(f"Класс объекта '{class_name}' успешно выбран") + + def get_selected_object_class(self) -> str: + """ + Получает выбранный класс объекта учета. + + Returns: + str: Выбранный класс объекта или пустая строка если ничего не выбрано + """ + combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX) + + selected_value = "" + + # Ищем в span элементах + span_locator = combobox_locator.locator(SELECTED_VALUE_SPAN) + if span_locator.count() > 0: + for i in range(span_locator.count()): + span_text = span_locator.nth(i).text_content().strip() + if span_text and span_text not in ["Класс объекта учета"]: + selected_value = span_text + break + + logger.info(f"Выбранный класс объекта: '{selected_value}'") + return selected_value + + def check_object_class_selected(self, expected_class: str) -> None: + """ + Проверяет что выбран указанный класс объекта. + + Args: + expected_class: Ожидаемый выбранный класс объекта + + Raises: + AssertionError: Если выбранный класс не соответствует ожидаемому + """ + logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...") + + # Даем время на обновление значения + self.wait_for_timeout(1000) + + actual_class = self.get_selected_object_class() + + # Проверка - допускаем частичное совпадение + if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower(): + logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')") + else: + raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'") + + def check_object_class_options_content(self, expected_options: list = None) -> None: + """ + Проверяет содержимое списка опций combobox. + + Args: + expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой. + + Raises: + AssertionError: Если список опций не соответствует ожидаемому + """ + logger.info("Проверка содержимого списка опций combobox...") + + # Получаем доступные опции + available_options = self.get_object_class_options() + + if expected_options is not None: + # Проверяем соответствие ожидаемому списку + assert set(available_options) == set(expected_options), ( + f"Список опций не соответствует ожидаемому. " + f"Ожидалось: {expected_options}, Получено: {available_options}" + ) + else: + # Проверяем что список не пустой + assert len(available_options) > 0, "Список опций combobox пустой" + + logger.info(f"Содержимое списка опций корректно: {available_options}") + + def check_dropdown_item_presence(self, item_text: str) -> None: + """ + Проверяет наличие элемента в выпадающем списке. + + Args: + item_text: Текст элемента для проверки + """ + logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...") + + # Получаем все опции и проверяем наличие + available_options = self.get_object_class_options() + + if item_text not in available_options: + raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}") + + logger.info(f"Элемент '{item_text}' присутствует в списке") diff --git a/pages/create_elements_tab/create_rack_element_tab.py b/pages/create_elements_tab/create_rack_element_tab.py new file mode 100644 index 0000000..bca7985 --- /dev/null +++ b/pages/create_elements_tab/create_rack_element_tab.py @@ -0,0 +1,659 @@ +"""Модуль страницы создания дочернего элемента. + +Содержит класс для работы с формой создания дочернего элемента. +""" +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 +from pages.main_page import MainPage +from pages.base_page import BasePage +from components.base_component import BaseComponent +from components.alert_component import AlertComponent +from components.navbar_component import NavigationPanelComponent +from locators.navigation_panel_locators import NavigationPanelLocators +from locators.combobox_locators import ComboboxLocators # Новый импорт +from 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 +} +#======================================================================================================== + + +class CreateRackElementTab(BasePage): + """Класс для работы с формой создания дочернего элемента.""" + + def __init__(self, page: Page) -> None: + """ + Инициализирует объект формы создания дочернего элемента. + + Args: + page: Экземпляр страницы Playwright + """ + super().__init__(page) + + # Инициализация BaseComponent + self.base_component = BaseComponent(page) + + # Инициализация AlertComponent + self.alert = AlertComponent(page) + + # Инициализация MainPage для работы с навигацией + self.main_page = MainPage(page) + + # Инициализация NavigationPanelComponent + self.navigation_panel = NavigationPanelComponent(page) + + # Кнопка "Добавить" - первая кнопка в тулбаре + create_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(0) + + # Кнопка "Отменить" - вторая кнопка в тулбаре + cancel_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(1) + + # Инициализация кнопок + self.create_button = TooltipButton(page, create_button_locator, "add") + self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel") + + # Инициализация тулбара с обеими кнопками + self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в") + self.toolbar.add_tooltip_button(create_button_locator, "add") + self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") + + # Инициализация компонента выпадающего списка + self.dropdown = DropdownList(page) + + def should_be_toolbar_buttons(self) -> None: + """ + Проверяет наличие и функциональность кнопок тулбара. + + Raises: + AssertionError: Если кнопки недоступны или подсказки неверны. + """ + + self.wait_for_timeout(2000) + + self.toolbar.check_button_visibility("add") + self.toolbar.check_button_tooltip("add", "Добавить") + + self.toolbar.check_button_visibility("cancel") + self.toolbar.check_button_tooltip("cancel", "Отменить") + self.toolbar.click_button("cancel") + self.wait_for_timeout(2000) + + def 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: + """ + Проверяет заголовок тулбара. + + Args: + expected_title: Ожидаемый заголовок тулбара + + Raises: + AssertionError: Если заголовок не соответствует ожидаемому + """ + logger.info(f"Проверка заголовка тулбара: '{expected_title}'...") + + # Используем метод тулбара с фильтрацией по тексту + actual_text = self.toolbar.get_toolbar_title_text( + filter_text="Создать дочерний элемент в" + ) + assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'" + + logger.info(f"Заголовок тулбара корректен: '{actual_text}'") + + def check_object_class_combobox_presence(self) -> None: + """ + Проверяет наличие combobox 'Класс объекта учета'. + + Raises: + AssertionError: Если combobox не найден + """ + logger.info("Проверка наличия combobox 'Класс объекта учета'...") + + self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не найден") + logger.info("Combobox 'Класс объекта учета' найден") + + def check_object_class_combobox_content(self) -> None: + """ + Проверяет содержимое combobox 'Класс объекта учета'. + + Raises: + AssertionError: Если содержимое не соответствует ожидаемому + """ + logger.info("Проверка содержимого combobox 'Класс объекта учета'...") + + combobox_locator = self.page.locator(ComboboxLocators.OBJECT_CLASS_COMBOBOX) + + # Проверяем что combobox видим + self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не виден") + + # Проверяем наличие label + label_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_LABEL) + expect(label_locator).to_have_text("Класс объекта учета") + + # Проверяем наличие input поля + input_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_INPUT) + self.base_component.check_visibility(input_locator, "Input поле combobox не найдено") + + # Для combobox нормально иметь readonly атрибут - это стандартное поведение + # Проверяем что поле доступно для выбора (не disabled) + expect(input_locator).not_to_have_attribute("disabled", "disabled") + + # Проверяем наличие иконки стрелки + icon_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_ICON_ARROW) + self.base_component.check_visibility(icon_locator, "Иконка стрелки combobox не найдена") + + logger.info("Содержимое combobox 'Класс объекта учета' корректно") + + def 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: + """ + Проверяет что выбран указанный класс объекта. + + Args: + expected_class: Ожидаемый выбранный класс объекта + + Raises: + AssertionError: Если выбранный класс не соответствует ожидаемому + """ + logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...") + + # Даем время на обновление значения + self.wait_for_timeout(1000) + + actual_class = self.get_selected_object_class() + + # Проверка - допускаем частичное совпадение + if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower(): + logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')") + else: + raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'") + + def check_object_class_options_content(self, expected_options: list = None) -> None: + """ + Проверяет содержимое списка опций combobox. + + Args: + expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой. + + Raises: + AssertionError: Если список опций не соответствует ожидаемому + """ + logger.info("Проверка содержимого списка опций combobox...") + + # Получаем доступные опции + available_options = self.get_object_class_options() + + if expected_options is not None: + # Проверяем соответствие ожидаемому списку + assert set(available_options) == set(expected_options), ( + f"Список опций не соответствует ожидаемому. " + f"Ожидалось: {expected_options}, Получено: {available_options}" + ) + else: + # Проверяем что список не пустой + assert len(available_options) > 0, "Список опций combobox пустой" + + logger.info(f"Содержимое списка опций корректно: {available_options}") + + def check_dropdown_item_presence(self, item_text: str) -> None: + """ + Проверяет наличие элемента в выпадающем списке. + + Args: + item_text: Текст элемента для проверки + """ + logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...") + + # Получаем все опции и проверяем наличие + available_options = self.get_object_class_options() + + if item_text not in available_options: + raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}") + + logger.info(f"Элемент '{item_text}' присутствует в списке") + + # =============== МЕТОДЫ ДЛЯ РАБОТЫ СО СТОЙКОЙ ======================== + + def check_rack_fields_presence(self) -> None: + """ + Проверяет наличие полей специфичных для стойки. + + Raises: + AssertionError: Если какое-либо поле не найдено + """ + logger.info("Проверка наличия полей для стойки...") + + # Основные обязательные поля + required_fields = [ + (RACK_NAME_FIELD, "Имя"), + (RACK_HEIGHT_FIELD, "Высота в юнитах"), + (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, "Проект/Титул") + ] + + # Проверяем обязательные поля + for field_locator, field_name in required_fields: + self.base_component.check_visibility(field_locator, f"Обязательное поле '{field_name}' не найдено") + logger.info(f"Обязательное поле '{field_name}' найдено") + + # Проверяем дополнительные поля + for field_locator, field_name in optional_fields: + field = self.page.locator(field_locator) + if field.count() > 0 and field.first.is_visible(): + logger.info(f"Дополнительное поле '{field_name}' найдено") + else: + logger.info(f"Дополнительное поле '{field_name}' не найдено или не отображается") + + logger.info("Все основные поля для стойки присутствуют") + + def 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(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(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 + + # Проверяем что поле видимо + 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 = [ + (RACK_NAME_FIELD, "Имя"), + (RACK_SERIAL_FIELD, "Серийный номер"), + (RACK_INVENTORY_FIELD, "Инвентарный номер"), + (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("Все поля формы стойки очищены") diff --git a/pages/rack_tab/__pycache__/rack_create.cpython-313.pyc b/pages/rack_tab/__pycache__/rack_create.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..561470c00b5aec975956af7c89ab38eeb3578ab1 GIT binary patch literal 21880 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 literal 0 HcmV?d00001 diff --git a/pages/rack_tab/__pycache__/rack_general_info.cpython-313.pyc b/pages/rack_tab/__pycache__/rack_general_info.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94269cbc9661fda4d3e140cc82a9e5a2d9bbdf2c GIT binary patch literal 5923 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 literal 0 HcmV?d00001 diff --git a/pages/rack_pages/rack_tab.py b/pages/rack_tab/rack_tab.py similarity index 100% rename from pages/rack_pages/rack_tab.py rename to pages/rack_tab/rack_tab.py diff --git a/pages/users_tab.py b/pages/users_tab.py index 9dc4a17..d263087 100644 --- a/pages/users_tab.py +++ b/pages/users_tab.py @@ -299,7 +299,7 @@ class UsersTab(BasePage): self.toolbar.check_button_visibility("add_user") self.toolbar.click_button("add_user") self.page.wait_for_timeout(700) - + self.add_modal_window("add_local_user", "") self.get_modal_window("add_local_user").check_by_window_title() 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 new file mode 100644 index 0000000000000000000000000000000000000000..8c72922d816396ed68b2429d0eecbfc0fc3b4f99 GIT binary patch literal 9111 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
Rb65wSVkdgiBhH~r?3*rNP^Q= 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 je8sI}`3L^R*PsL=*L@q`@cf781-C None: + """Фикстура для подготовки тестового окружения. + + Выполняет: + 1. Авторизацию в системе + 2. Переход к созданию дочернего элемента через панель навигации: + - Объекты → test-zone → Создать дочерний элемент + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + # Авторизация в системе + login_page = LoginPage(browser) + login_page.do_login() + + # Мы на главной странице + main_page = MainPage(browser) + main_page.should_be_navigation_panel() + main_page.wait_for_timeout(2000) + + # Переходим к Объектам + main_page.click_main_navigation_panel_item("Объекты") + main_page.wait_for_timeout(2000) + + 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.wait_for_timeout(2000) + + @pytest.mark.develop + def test_child_element_creation_form(self, browser: Page) -> None: + """Тест проверки формы создания дочернего элемента. + + Проверяет: + 1. Корректность заголовка формы создания + 2. Наличие и функциональность кнопки отмены + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + child_element_page = CreateChildElementTab(browser) + + # Проверяем заголовок формы - используем часть текста для надежности + expected_title_part = "Создать дочерний элемент в" + child_element_page.check_toolbar_title(expected_title_part) + + # Проверяем кнопку 'Отменить' в форме создания + child_element_page.should_be_toolbar_buttons() + + child_element_page.wait_for_timeout(2000) + + def test_object_class_combobox(self, browser: Page) -> None: + """Тест проверки combobox 'Класс объекта учета' в форме создания. + + Проверяет: + 1. Наличие combobox на странице + 2. Корректность содержимого + 3. Содержимое списка опций + 4. Возможность выбора каждой опции + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + child_element_page = CreateChildElementTab(browser) + + logger.info("Комплексная проверка combobox 'Класс объекта учета'...") + + # 1. Проверяем наличие combobox + child_element_page.check_object_class_combobox_presence() + + # 2. Проверяем содержимое combobox + child_element_page.check_object_class_combobox_content() + + # 3. Проверяем содержимое списка опций + available_options = child_element_page.get_object_class_options() + + # Проверяем что список не пустой + assert len(available_options) > 0, "Список опций combobox пустой" + + # Проверяем что есть все ожидаемые опции + expected_options = ["Локация", "Стойка", "Устройство", "Модуль"] + missing_options = [opt for opt in expected_options if opt not in available_options] + assert len(missing_options) == 0, f"Отсутствуют опции: {missing_options}. Найдены: {available_options}" + + logger.info(f"Все ожидаемые опции найдены: {available_options}") + + # 4. Проверяем выбор каждой опции по очереди + logger.info("Проверка выбора каждой опции по очереди...") + + for option in expected_options: + logger.info(f"Выбор класса объекта: '{option}'...") + child_element_page.select_object_class(option) + child_element_page.check_object_class_selected(option) + child_element_page.wait_for_timeout(500) + logger.info(f"Класс объекта '{option}' успешно выбран") + + logger.info("Combobox 'Класс объекта учета' прошел все проверки") + diff --git a/tests/e2e/create_elements/test_create_rack_element.py b/tests/e2e/create_elements/test_create_rack_element.py new file mode 100644 index 0000000..711320b --- /dev/null +++ b/tests/e2e/create_elements/test_create_rack_element.py @@ -0,0 +1,362 @@ +"""Тест создания дочернего элемента 'Стойка'.""" + +import pytest +from playwright.sync_api import Page +from pages.create_elements_tab.create_rack_element_tab import CreateRackElementTab +from pages.create_elements_tab.create_child_element_tab import CreateChildElementTab +from pages.login_page import LoginPage +from pages.main_page import MainPage +from tools.logger import get_logger + +logger = get_logger("CREATE_RACK_ELEMENT_TEST") + + +class TestCreateRackElement: + """Тест создания дочернего элемента типа 'Стойка'.""" + + @pytest.fixture(scope="function", autouse=True) + def setup(self, browser: Page) -> None: + """Фикстура для подготовки тестового окружения. + + Args: + browser (Page): Экземпляр страницы Playwright для взаимодействия с UI + """ + # Авторизация в системе + login_page = LoginPage(browser) + login_page.do_login() + + # Мы на главной странице + main_page = MainPage(browser) + main_page.should_be_navigation_panel() + main_page.wait_for_timeout(2000) + + # Переходим к Объектам + main_page.click_main_navigation_panel_item("Объекты") + main_page.wait_for_timeout(2000) + + 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() + 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) + + # Проверяем заголовок формы создания + rack_element_page.check_toolbar_title('Создать дочерний элемент в') + + # Проверяем что после выбора 'Стойка' появляются специфичные поля + rack_element_page.check_rack_fields_presence() + logger.info("Специфичные поля для стойки отображаются корректно") + + 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) + + # Проверяем что после выбора 'Стойка' появляются специфичные поля + rack_element_page.check_rack_fields_presence() + logger.info("Специфичные поля для стойки отображаются корректно") + + # Заполняем данные стойки + rack_name = "Test-Rack-01" + + rack_element_page.fill_rack_data( + name=rack_name, + height="42", + depth="1000", + serial="TEST123456", + inventory="INV-001", + comment="Тестовая стойка для автоматизации" + ) + + # Нажимаем кнопку создания + rack_element_page.click_add_button() + + rack_element_page.wait_for_timeout(2000) + + logger.info("Тест создания дочернего элемента 'Стойка' завершен успешно") + + #@pytest.mark.develop + def test_create_rack_with_duplicate_name(self, browser: Page) -> None: + """ + Тест создания стойки с уже существующим именем. + + Проверяет, что система корректно обрабатывает попытку создания + стойки с именем, которое уже используется. + """ + logger.info("Запуск теста создания стойки с дублирующимся именем") + + rack_name = "Test-Rack-01" + rack_element_page = CreateRackElementTab(browser) + + # Проверяем, существует ли уже стойка с таким именем + if not rack_element_page.check_rack_exists(rack_name): + logger.info(f"Стойка с именем '{rack_name}' не найдена. Создаем первую стойку.") + + # Создаем первую стойку + main_page = MainPage(browser) + 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.wait_for_timeout(2000) + + rack_element_page = CreateRackElementTab(browser) + rack_element_page.fill_rack_data( + name=rack_name, + height="42", + depth="1000" + ) + + # Создаем первую стойку + rack_element_page.click_add_button() + rack_element_page.wait_for_timeout(3000) + logger.info(f"Первая стойка с именем '{rack_name}' успешно создана") + else: + logger.info(f"Стойка с именем '{rack_name}' уже существует, переходим к созданию второй") + + # Теперь пытаемся создать вторую стойку с тем же именем + logger.info(f"Пытаемся создать вторую стойку с именем '{rack_name}'") + + # Переходим обратно к созданию новой стойки + main_page = MainPage(browser) + 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.wait_for_timeout(2000) + + # Пытаемся создать вторую стойку с тем же именем + rack_element_page = CreateRackElementTab(browser) + rack_element_page.fill_rack_data( + name=rack_name, + height="42", + depth="1000" + ) + + # Нажимаем кнопку создания + rack_element_page.click_add_button() + rack_element_page.wait_for_timeout(2000) + + # Проверяем наличие 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('Создать дочерний элемент в') + 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: + """ + Тест проверки обязательных полей при создании стойки. + + Проверяет, что система корректно валидирует обязательные поля: + - Поле 'Имя' должно быть заполнено + - Поле 'Высота в юнитах' должно быть заполнено + - Поле 'Глубина (мм)' должно быть заполнено + """ + # Текст сообщения alert-окна + #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: Попытка создания стойки без заполнения обязательных полей") + + # Нажимаем кнопку создания без заполнения данных + rack_element_page.click_add_button() + rack_element_page.wait_for_timeout(2000) + + # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах + rack_element_page.alert.check_alert_presence(expected_alert_text_height) + # Закрываем alert-окно Высота в юнитах с помощью кнопки закрытия + rack_element_page.alert.close_alert_by_text(expected_alert_text_height) + + # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) + rack_element_page.alert.check_alert_presence(expected_alert_text_depth) + # Закрываем alert-окно Глубины (мм) с помощью кнопки закрытия + rack_element_page.alert.close_alert_by_text(expected_alert_text_depth) + + # Проверяем, что остались на той же странице + rack_element_page.check_toolbar_title('Создать дочерний элемент в') + logger.info("Система не позволила создать стойку без высоты и глубины") + rack_element_page.wait_for_timeout(2000) + + # 2. Тест: Обязательные поля не заполнены + logger.info("Тест 2: Обязательные поля не заполнены") + + rack_element_page.fill_rack_data( + name="", # не заполняем имя + height="", # не заполняем высоту + depth="" # не заполняем глубину + ) + + # Нажимаем кнопку создания без заполнения данных + 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) # Ошибка, поле не отслеживается + + # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах + rack_element_page.alert.check_alert_presence(expected_alert_text_height) + # Закрываем alert-окно Высота в юнитах с помощью кнопки закрытия + rack_element_page.alert.close_alert_by_text(expected_alert_text_height) + + # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) + rack_element_page.alert.check_alert_presence(expected_alert_text_depth) + # Закрываем alert-окно Глубины (мм) с помощью кнопки закрытия + rack_element_page.alert.close_alert_by_text(expected_alert_text_depth) + + # Проверяем, что остались на той же странице + rack_element_page.check_toolbar_title('Создать дочерний элемент в') + logger.info("Система не позволила создать стойку без имени, высоты и глубины") + + # 3. Тест: Заполняем только поле 'Высота в юнитах' + logger.info("Тест 3: Заполняем только поле 'Высота в юнитах'") + + # Очистить поля + rack_element_page.clear_combobox_field("Глубина (мм)") + rack_element_page.clear_combobox_field("Высота в юнитах") + + rack_element_page.fill_rack_data( + name="", # не заполняем имя + height="42", + depth="" # не заполняем глубину + ) + + # Нажимаем кнопку создания без заполнения данных + 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) # Ошибка, поле не отслеживается + + # Проверяем наличие alert-окна с сообщением о заполнении Глубины (мм) + rack_element_page.alert.check_alert_presence(expected_alert_text_depth) + # Закрываем alert-окно Глубины (мм) с помощью кнопки закрытия + rack_element_page.alert.close_alert_by_text(expected_alert_text_depth) + + # Проверяем, что остались на той же странице + rack_element_page.check_toolbar_title('Создать дочерний элемент в') + logger.info("Система не позволила создать стойку без имени и глубины") + + # 4. Тест: Заполняем только поле 'Глубина (мм)' + logger.info("Тест 4: Заполняем только поле 'Глубина (мм)'") + + rack_element_page.clear_combobox_field("Глубина (мм)") + rack_element_page.clear_combobox_field("Высота в юнитах") + + rack_element_page.fill_rack_data( + name="", # не заполняем имя + height="", # не заполняем высоту + depth="1000" + ) + + rack_element_page.wait_for_timeout(5000) + # Нажимаем кнопку создания без заполнения данных + 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) # Ошибка, поле не отслеживается + + # Проверяем наличие alert-окна с сообщением о заполнении Высота в юнитах + rack_element_page.alert.check_alert_presence(expected_alert_text_height) + # Закрываем alert-окно Высота в юнитах с помощью кнопки закрытия + rack_element_page.alert.close_alert_by_text(expected_alert_text_height) + + # Проверяем, что остались на той же странице + rack_element_page.check_toolbar_title('Создать дочерний элемент в') + logger.info("Система не позволила создать стойку без высоты") + rack_element_page.wait_for_timeout(2000) + + # 5. Тест: Заполняем все обязательные поля + logger.info("Тест 5: Заполняем все обязательные поля") + + # Генерируем уникальное имя для финального теста + final_rack_name = "Test-Rack-Required-Final" + + # Заполняем все обязательные поля + rack_element_page.fill_rack_data( + name=final_rack_name, + height="42", + depth="1000" + ) + + # Нажимаем кнопку создания + rack_element_page.click_add_button() + + # Ждем завершения создания (должны перейти на другую страницу) + rack_element_page.wait_for_timeout(3000) + + # Проверяем, что ушли со страницы создания (косвенная проверка успешного создания) + try: + rack_element_page.check_toolbar_title('Создать дочерний элемент в') + logger.warning("Возможно создание стойки не завершилось успешно") + except Exception as e: + 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 new file mode 100644 index 0000000000000000000000000000000000000000..ca13e96699229315c09f6145e61b58296bf65b33 GIT binary patch literal 5769 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 Vdmc*LPVL}AirxSK literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..3c66cff4229a2e3b68f30a7b12876befba4df808 GIT binary patch literal 3030 zcmb_eTWnNC7@oaPce`y7K`61DZh*Q0)v9AMmNLCA|92oV~SO<;9ci%s>Bi=Ktsa zX3N2#AHl~5T-q-{{V5jJ&s`zPBS4%*F~m^J5p(uAm{ZQKUbpU4P_Ku16Gd<_r);|gdLXqLp4od_C?j?p@V7y$d>o=X)Rab~Co+!JkWRJZA9+iG6S6~{4m%(L4a^LC<7-Fqr3tR?1S z{2ZFUgu*heDKkpfE~fEqKZNTomJC zJ=5kC_;vfP1WXTLKlbXI)TWGiT} zTF^!-SHCOjTVVprx)sP*c*0d&2{&j6x%3o4^IL+j{4DC{fl7ET0ei>8G(hN!bZ|0LR1QUJEHH%n4- z4e$Z5)||3}$zeLe@;Sn^^svFim13f1OdQvyL}?F%{Y*5Jg;RNw5n#%^V%ez==Zl#X zNl=ngN3^tRXyB`$=4i(17*#cc4CNUyv@FdR4NFO8wB#X@1^iv5PBepNt+lp>UQ9?I zlF7uwXsxeD8m4NBvPzV$IzN#&hKa7Fs7_LvUdX6pG!^z*Zk=X^n0R+BXQp5|vjwY@ zepaAKgQkd)&u0=UBM{Nhj11K+Co^<$Hbs6*KLh#ZEAfF3qJ42dXmNQ;@(qJ}-4WO; zxEJ{aQJi!9QXKMpUeqtrtQ_%o`rnQ4(|lT@>Wo-7_~ifdh~8^^9ko~*gqBcB5*3W_O;edq+T<9z%#PSo8&8LbPLAMfexTt0-l6FFdG^QIFEk|>C5c>#8 z<&!{o3u7YhmOHDmLzXW^M`$KrVB&pan_;ll5uzJV?j%tSgK3E(s1w3MLZMp>U1Gr1 zL3FIX3x;Fpfz#!|ix_!Zj}J}^E_gPWo(+r6ou1GSsYL`+$ta7d>_Xm+Wx==o30L8# z7=R13e4?mj3@xWyt#1^@NNu%|*xQb%Iy~i~@gt1FMK!3^Qe#=ZD$ftb@^-0=JtzFS z1uR%=>(@J?kLBQm1x=pZ+E{@9ae72v*ap&26E-4r*i4B=00wDlBUHQ1mLMa!qM7a7 zEYFvP_t}+to$Un`@y|lv0mGup;cz@~x*hJve&lc*MMr|aqrhKi%RRL79@=&fZF%h7 MXJ%>o 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 literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..d2ffe62a6a21f1a334775b9a84db854802b23114 GIT binary patch literal 9950 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/users/__init__.py b/tests/e2e/users/__init__.py new file mode 100644 index 0000000..0bf3729 --- /dev/null +++ b/tests/e2e/users/__init__.py @@ -0,0 +1,2 @@ +# Auto-generated by fix_python_project.py +"""Package initialization.""" diff --git a/tests/e2e/users/__pycache__/__init__.cpython-313.pyc b/tests/e2e/users/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7e74a6c34951abd7a64e2f22dbfa54f54cd9f30 GIT binary patch literal 322 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 XZ-s8_Z_j6L*={>-|3u&gKc4>uaHBNR literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..a8d6b24bcf31975aa37cfaeca2854c8c9fb55a36 GIT binary patch literal 11345 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 PZ!w)UeZ{eaE!6)4)huzi literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..543edc77e41900e0f563cd18b7fee49617e77bfc GIT binary patch literal 12843 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