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