Тест создания Стойки
parent
440426aca1
commit
e4aba34421
2
.env
2
.env
|
|
@ -1,3 +1,3 @@
|
||||||
ENV=develop
|
ENV=test
|
||||||
AUTH_LOGIN = admin
|
AUTH_LOGIN = admin
|
||||||
AUTH_PASSWORD = enodemon-admin
|
AUTH_PASSWORD = enodemon-admin
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from playwright.sync_api import Page, expect
|
||||||
from tools.logger import get_logger
|
from tools.logger import get_logger
|
||||||
from elements.text_element import Text
|
from elements.text_element import Text
|
||||||
from components.base_component import BaseComponent
|
from components.base_component import BaseComponent
|
||||||
|
from locators.alert_locators import AlertLocators
|
||||||
|
|
||||||
logger = get_logger("ALERT")
|
logger = get_logger("ALERT")
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ class AlertComponent(BaseComponent):
|
||||||
Позволяет проверять наличие, отсутствие и текст сообщений.
|
Позволяет проверять наличие, отсутствие и текст сообщений.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, page: Page):
|
def __init__(self, page: Page) -> None:
|
||||||
"""Инициализирует компонент alert-окна.
|
"""Инициализирует компонент alert-окна.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -28,7 +29,7 @@ class AlertComponent(BaseComponent):
|
||||||
|
|
||||||
super().__init__(page)
|
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:
|
def get_alert_type(self) -> str:
|
||||||
|
|
@ -41,7 +42,7 @@ class AlertComponent(BaseComponent):
|
||||||
ValueError: Если получен неподдерживаемый тип alert-окна.
|
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
|
alert_type = None
|
||||||
if 'v-alert' in class_attr:
|
if 'v-alert' in class_attr:
|
||||||
|
|
@ -62,8 +63,38 @@ class AlertComponent(BaseComponent):
|
||||||
|
|
||||||
return self.text.get_text(0)
|
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-окна с заданным текстом.
|
"""Проверяет наличие alert-окна с заданным текстом.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -76,11 +107,13 @@ class AlertComponent(BaseComponent):
|
||||||
|
|
||||||
msg = "Alert window is missing"
|
msg = "Alert window is missing"
|
||||||
if text == "":
|
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:
|
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-окна с заданным текстом.
|
"""Проверяет отсутствие alert-окна с заданным текстом.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -93,9 +126,15 @@ class AlertComponent(BaseComponent):
|
||||||
|
|
||||||
seconds = int(timeout/1000)
|
seconds = int(timeout/1000)
|
||||||
msg = f"Alert window should disappear after {seconds} seconds"
|
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-окне.
|
"""Проверяет точное соответствие текста в alert-окне.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
||||||
|
|
@ -235,3 +235,20 @@ class NavigationPanelComponent(BaseComponent):
|
||||||
else:
|
else:
|
||||||
loc = loc.get_by_text(item_name)
|
loc = loc.get_by_text(item_name)
|
||||||
self.check_visibility(loc, msg)
|
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
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ class ToolbarComponent(BaseComponent):
|
||||||
raise AssertionError(f"Unsupported button name {name}")
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
button.click()
|
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:
|
filter_text: str = None, timeout: int = 5000) -> str:
|
||||||
"""Получает заголовок тулбара окна.
|
"""Получает заголовок тулбара окна.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# RackLocators
|
||||||
|
|
||||||
|
::: locators.rack_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# SettingsFormLocators
|
||||||
|
|
||||||
|
::: locators.settings_form_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# CurrentSessionsTab
|
||||||
|
|
||||||
|
::: pages.current_session_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# SessionSettingsTab
|
||||||
|
|
||||||
|
::: pages.session_settings_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestRackTab
|
||||||
|
|
||||||
|
::: tests.e2e.rack.test_rack_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestCurrentSessionsTab
|
||||||
|
|
||||||
|
::: tests.e2e.sessions.test_current_sessions_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestCurrentSettingsTab
|
||||||
|
|
||||||
|
::: tests.e2e.sessions.test_session_settings_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -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}}')]"
|
||||||
|
|
@ -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"
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -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}' присутствует в списке")
|
||||||
|
|
@ -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("Все поля формы стойки очищены")
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -299,7 +299,7 @@ class UsersTab(BasePage):
|
||||||
self.toolbar.check_button_visibility("add_user")
|
self.toolbar.check_button_visibility("add_user")
|
||||||
self.toolbar.click_button("add_user")
|
self.toolbar.click_button("add_user")
|
||||||
self.page.wait_for_timeout(700)
|
self.page.wait_for_timeout(700)
|
||||||
|
|
||||||
self.add_modal_window("add_local_user", "")
|
self.add_modal_window("add_local_user", "")
|
||||||
self.get_modal_window("add_local_user").check_by_window_title()
|
self.get_modal_window("add_local_user").check_by_window_title()
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,129 @@
|
||||||
|
"""Модуль тестов создания дочернего элемента в модуле Объекты.
|
||||||
|
|
||||||
|
Содержит тесты для проверки функциональности
|
||||||
|
создания дочерних элементов оборудования.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
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_CHILD_ELEMENT_TESTS")
|
||||||
|
|
||||||
|
|
||||||
|
# @pytest.mark.smoke
|
||||||
|
class TestCreateChildElement:
|
||||||
|
"""Набор тестов для создания дочернего элемента в модуле Объекты.
|
||||||
|
|
||||||
|
Проверяет корректность отображения и функциональность элементов интерфейса
|
||||||
|
при создании дочерних элементов оборудования.
|
||||||
|
|
||||||
|
Тесты покрывают следующие функциональные области:
|
||||||
|
1. test_child_element_creation_form - Проверка формы создания дочернего элемента
|
||||||
|
2. test_object_class_combobox - Проверка combobox 'Класс объекта учета'
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function", autouse=True)
|
||||||
|
def setup(self, browser: Page) -> 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 'Класс объекта учета' прошел все проверки")
|
||||||
|
|
||||||
|
|
@ -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("Тест проверки обязательных полей завершен успешно")
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue