Исправление тестов создания стойки и локаторов

Radislav 2025-12-19 11:43:11 +03:00
parent 07906b9f76
commit a3dc0a037c
4 changed files with 228 additions and 128 deletions

View File

@ -1,13 +1,14 @@
"""Модуль создания объекта 'Стойка'.""" """Модуль создания объекта 'Стойка'."""
from dataclasses import dataclass from dataclasses import dataclass
from playwright.sync_api import Page from playwright.sync_api import Page, Locator
from tools.logger import get_logger from tools.logger import get_logger
from locators.rack_locators import RackLocators from locators.rack_locators import RackLocators
from components.base_component import BaseComponent from components.base_component import BaseComponent
logger = get_logger("RACK_MAKER") logger = get_logger("RACK_MAKER")
logger.setLevel("INFO")
@dataclass @dataclass
class RackData: class RackData:
@ -49,12 +50,12 @@ class RackObjectMaker(BaseComponent):
rack_data: Данные стойки 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_text_fields(rack_data)
self._fill_combobox_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: def _fill_text_fields(self, rack_data: RackData) -> None:
"""Заполняет текстовые поля.""" """Заполняет текстовые поля."""
@ -69,7 +70,7 @@ class RackObjectMaker(BaseComponent):
field.press("Backspace") field.press("Backspace")
# Заполняем значение # Заполняем значение
field.fill(value) field.fill(value)
logger.info(f"Filled '{field_name}': {value}") logger.debug(f"Filled '{field_name}': {value}")
# Обязательные поля. # Обязательные поля.
if rack_data.name: if rack_data.name:
@ -90,55 +91,48 @@ class RackObjectMaker(BaseComponent):
# Обязательные поля. # Обязательные поля.
if rack_data.height: if rack_data.height:
self._fill_combobox_field("Height in units", rack_data.height, self._fill_combobox_field("Высота в юнитах", rack_data.height)
RackLocators.RACK_HEIGHT_FIELD) logger.debug(f"Selected height: {rack_data.height} units")
logger.info(f"Selected height: {rack_data.height} units")
if rack_data.depth: if rack_data.depth:
self._fill_combobox_field("Depth (mm)", rack_data.depth, self._fill_combobox_field("Глубина (мм)", rack_data.depth)
RackLocators.RACK_DEPTH_FIELD) logger.debug(f"Selected depth: {rack_data.depth} mm")
logger.info(f"Selected depth: {rack_data.depth} mm")
# Опциональные поля. # Опциональные поля.
if rack_data.cable_entry: if rack_data.cable_entry:
self._fill_combobox_field("Cable entry", rack_data.cable_entry, self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry)
RackLocators.RACK_CABLE_ENTRY_FIELD) logger.debug(f"Selected cable entry: {rack_data.cable_entry}")
logger.info(f"Selected cable entry: {rack_data.cable_entry}")
if rack_data.state: if rack_data.state:
self._fill_combobox_field("State", rack_data.state, self._fill_combobox_field("Состояние", rack_data.state)
RackLocators.RACK_STATE_FIELD) logger.debug(f"Selected state: {rack_data.state}")
logger.info(f"Selected state: {rack_data.state}")
if rack_data.owner: if rack_data.owner:
self._fill_combobox_field("Owner", rack_data.owner, self._fill_combobox_field("Владелец", rack_data.owner)
RackLocators.RACK_OWNER_FIELD) logger.debug(f"Selected owner: {rack_data.owner}")
logger.info(f"Selected owner: {rack_data.owner}")
if rack_data.service_org: if rack_data.service_org:
self._fill_combobox_field("Service organization", rack_data.service_org, self._fill_combobox_field("Обслуживающая организация", rack_data.service_org)
RackLocators.RACK_SERVICE_ORG_FIELD) logger.debug(f"Selected service organization: {rack_data.service_org}")
logger.info(f"Selected service organization: {rack_data.service_org}")
if rack_data.project: if rack_data.project:
self._fill_combobox_field("Project/Title", rack_data.project, self._fill_combobox_field("Проект/Титул", rack_data.project)
RackLocators.RACK_PROJECT_FIELD) logger.debug(f"Selected project/title: {rack_data.project}")
logger.info(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 поле. Заполняет combobox поле.
Args: Args:
field_name: Название поля field_name: Название поля
value: Значение для установки 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 # Используем универсальный локатор для combobox по имени поля
field_container = self.page.locator(field_locator).first 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() field_container.scroll_into_view_if_needed()
@ -151,12 +145,58 @@ class RackObjectMaker(BaseComponent):
field_container.click(force=True) field_container.click(force=True)
self.wait_for_timeout(1000) self.wait_for_timeout(1000)
# Вводим значение # Вводим значение из выпадающего списка
self.page.keyboard.type(value) dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value)
self.wait_for_timeout(500) element = self.page.locator(dropdown_item_locator).first
self.page.keyboard.press("Enter")
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: def _get_field_locator(self, field_name: str) -> str:
""" """
@ -190,7 +230,7 @@ class RackObjectMaker(BaseComponent):
AssertionError: Если какое-либо поле не найдено AssertionError: Если какое-либо поле не найдено
""" """
logger.info("Checking rack fields presence...") logger.debug("Checking rack fields presence...")
# Основные обязательные поля # Основные обязательные поля
required_fields = [ required_fields = [
@ -215,14 +255,14 @@ class RackObjectMaker(BaseComponent):
for field_locator, field_name in required_fields: for field_locator, field_name in required_fields:
field = self.page.locator(field_locator).first field = self.page.locator(field_locator).first
self.check_visibility(field, f"Required field '{field_name}' not found") 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: for field_locator, field_name in optional_fields:
field = self.page.locator(field_locator).first field = self.page.locator(field_locator).first
if field.count() > 0 and field.is_visible(): 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: 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")

View File

@ -12,6 +12,7 @@ from components_derived.selection_bar_component import SelectionBarComponent
logger = get_logger("CREATE_CHILD_ELEMENT_FRAME") logger = get_logger("CREATE_CHILD_ELEMENT_FRAME")
logger.setLevel("INFO")
class CreateChildElementFrame(BaseComponent): class CreateChildElementFrame(BaseComponent):
"""Фрейм создания дочернего элемента.""" """Фрейм создания дочернего элемента."""
@ -55,10 +56,10 @@ class CreateChildElementFrame(BaseComponent):
field_name: Название поля для очистки 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 # Используем метод из SelectionBarComponent
self.selection_bar.clear_combobox_field(field_name, field_locator) self.selection_bar.clear_combobox_field(field_name, field_locator)
@ -66,15 +67,28 @@ class CreateChildElementFrame(BaseComponent):
def click_add_button(self) -> None: def click_add_button(self) -> None:
"""Кликает на кнопку 'Добавить'.""" """Кликает на кнопку 'Добавить'."""
logger.info("Clicking on 'Add' button...") logger.debug("Clicking on 'Add' button...")
self.toolbar.click_button("add") self.toolbar.click_button("add")
def click_cancel_button(self) -> None: def click_cancel_button(self) -> None:
"""Кликает на кнопку 'Отменить'.""" """Кликает на кнопку 'Отменить'."""
logger.info("Clicking on 'Cancel' button...") logger.debug("Clicking on 'Cancel' button...")
self.toolbar.click_button("cancel") 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]: def get_object_class_options(self) -> list[str]:
""" """
Получает список доступных опций из combobox. Получает список доступных опций из combobox.
@ -83,11 +97,11 @@ class CreateChildElementFrame(BaseComponent):
list[str]: Список доступных классов объектов 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() 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 return available_options
def get_selected_object_class(self) -> str: def get_selected_object_class(self) -> str:
@ -103,7 +117,7 @@ class CreateChildElementFrame(BaseComponent):
def open_object_class_combobox(self) -> None: def open_object_class_combobox(self) -> None:
"""Открывает выпадающий список combobox 'Класс объекта учета'.""" """Открывает выпадающий список combobox 'Класс объекта учета'."""
logger.info("Opening combobox 'Accounting object class'...") logger.debug("Opening combobox 'Accounting object class'...")
# Ждем стабильности combobox # Ждем стабильности combobox
expect(self.selection_bar.selection_bar_locator).to_be_visible() expect(self.selection_bar.selection_bar_locator).to_be_visible()
@ -113,11 +127,11 @@ class CreateChildElementFrame(BaseComponent):
"class" "class"
) )
if is_menu_active and "v-select--is-menu-active" in is_menu_active: 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 return
# Используем force click для обхода перекрывающих элементов # Используем 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) 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: 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 # Открываем combobox
self.open_object_class_combobox() self.open_object_class_combobox()
@ -139,12 +153,12 @@ class CreateChildElementFrame(BaseComponent):
# Логируем текущее состояние без строгой проверки # Логируем текущее состояние без строгой проверки
selected_value = self.get_selected_object_class() 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_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) self.selection_bar.check_field_error_highlighted(field_name, field_locator)
def check_field_error_not_highlighted(self, field_name: str) -> None: def check_field_error_not_highlighted(self, field_name: str) -> None:
@ -167,7 +181,7 @@ class CreateChildElementFrame(BaseComponent):
field_name: Название поля для проверки 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) self.selection_bar.check_field_error_not_highlighted(field_name, field_locator)
def check_object_class_selected(self, expected_class: str) -> None: def check_object_class_selected(self, expected_class: str) -> None:
@ -178,7 +192,7 @@ class CreateChildElementFrame(BaseComponent):
expected_class: Ожидаемый выбранный класс объекта 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) self.wait_for_timeout(1000)
actual_class = self.get_selected_object_class() actual_class = self.get_selected_object_class()
@ -191,7 +205,7 @@ class CreateChildElementFrame(BaseComponent):
f"Expected: '{expected_class}', Got: '{actual_class}'" f"Expected: '{expected_class}', Got: '{actual_class}'"
) )
logger.info( logger.debug(
f"Object class '{expected_class}' successfully selected " f"Object class '{expected_class}' successfully selected "
f"(actual: '{actual_class}')" f"(actual: '{actual_class}')"
) )
@ -204,7 +218,7 @@ class CreateChildElementFrame(BaseComponent):
expected_title: Ожидаемый заголовок тулбара 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( actual_text = self.toolbar.get_toolbar_title_text(
@ -216,7 +230,7 @@ class CreateChildElementFrame(BaseComponent):
f"Got: '{actual_text}'" 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: def should_be_toolbar_buttons(self) -> None:
""" """

View File

@ -27,26 +27,36 @@ class RackLocators:
EDIT_BUTTON ="//button[@data-testid='CABINET_SHOW__btn__edit']" 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')]" COMPOSITION_TAB = ("//div[@data-testid='CABINET_SHOW__composition_tab']"
GENERAL_INFO_TAB = "//div[@data-testid='CABINET_SHOW__main_tab']//a[contains(@class, 'v-tabs__item')]" "//a[contains(@class, 'v-tabs__item')]")
MAINTENANCE_TAB = "//div[@data-testid='CABINET_SHOW__service_tab']//a[contains(@class, 'v-tabs__item')]" GENERAL_INFO_TAB = ("//div[@data-testid='CABINET_SHOW__main_tab']"
EVENTS_TAB = "//div[@data-testid='CABINET_SHOW__events_tab']//a[contains(@class, 'v-tabs__item')]" "//a[contains(@class, 'v-tabs__item')]")
SERVICES_TAB = "//div[@data-testid='CABINET_SHOW__services_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_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')]" FORM_CONTAINER = "//div[contains(@class, 'container')]"
@ -63,18 +73,49 @@ class RackLocators:
PROJECT_FIELD = "//input[@aria-label='Проект/Титул']" PROJECT_FIELD = "//input[@aria-label='Проект/Титул']"
# Локаторы полей формы создания стойки # Локаторы полей формы создания стойки
RACK_NAME_FIELD = "//div[contains(@class, 'container')]//label[text()='Имя']/following-sibling::input" RACK_NAME_FIELD = ("//div[contains(@class, 'container')]"
RACK_HEIGHT_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Высота в юнитах']]" "//label[text()='Имя']/following-sibling::input")
RACK_DEPTH_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Глубина (мм)']]" RACK_HEIGHT_FIELD = ("//div[contains(@class, 'container')]"
RACK_SERIAL_FIELD = "//div[contains(@class, 'container')]//label[text()='Серийный номер']/following-sibling::input" "//div[contains(@class, 'v-input__slot') and "
RACK_INVENTORY_FIELD = "//div[contains(@class, 'container')]//label[text()='Инвентарный номер']/following-sibling::input" ".//label[text()='Высота в юнитах']]")
RACK_COMMENT_FIELD = "//div[contains(@class, 'container')]//label[text()='Комментарий']/following-sibling::input" RACK_DEPTH_FIELD = ("//div[contains(@class, 'container')]"
RACK_CABLE_ENTRY_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Ввод кабеля']]" "//div[contains(@class, 'v-input__slot') and "
RACK_STATE_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Состояние']]" ".//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_OWNER_FIELD = ("//div[contains(@class, 'container')]"
RACK_SERVICE_ORG_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Обслуживающая организация']]" "//div[contains(@class, 'v-input__slot') and "
RACK_PROJECT_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Проект/Титул']]" ".//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')]" INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]"
@ -85,7 +126,8 @@ class RackLocators:
# ================ ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОЙКИ =================== # ================ ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОЙКИ ===================
# Общий контейнер стойки (включает кнопки переключения сторон и MAIN_CONTAINER) # Общий контейнер стойки (включает кнопки переключения сторон и 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')]" MAIN_CONTAINER = "//div[contains(@class, 'layout cabinet')]"
@ -99,7 +141,8 @@ class RackLocators:
INACTIVE_SIDE_BUTTON = "//button[contains(@class, 'secondary--text')]" 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)
ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]" ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]"
@ -108,11 +151,12 @@ class RackLocators:
ALL_UNITS = "//div[contains(@class, 'unit')]" 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_ELEMENTS = "//div[contains(@class, 'parent-class')]"
# Локатор для слотов в устройствах # Локатор для слотов в устройствах
DEVICE_SLOTS = "//div[contains(@class, 'slot')]" DEVICE_SLOTS = "//div[contains(@class, 'slot')]"

View File

@ -13,6 +13,8 @@ from pages.main_page import MainPage
logger = get_logger("CREATE_RACK_ELEMENT_TEST") logger = get_logger("CREATE_RACK_ELEMENT_TEST")
logger.setLevel("INFO")
# @pytest.mark.smoke # @pytest.mark.smoke
class TestCreateRackElement: class TestCreateRackElement:
"""Тест создания дочернего элемента типа 'Стойка'. """Тест создания дочернего элемента типа 'Стойка'.
@ -55,7 +57,7 @@ class TestCreateRackElement:
# Создаем экземпляр страницы локации # Создаем экземпляр страницы локации
self.location_page = LocationPage(browser) self.location_page = LocationPage(browser)
@pytest.mark.develop #@pytest.mark.develop
def test_create_rack_content(self, browser: Page) -> None: def test_create_rack_content(self, browser: Page) -> None:
"""Тест создания дочернего элемента типа 'Стойка'.""" """Тест создания дочернего элемента типа 'Стойка'."""
@ -82,7 +84,7 @@ class TestCreateRackElement:
# Проверяем что после выбора 'Стойка' появляются специфичные поля # Проверяем что после выбора 'Стойка' появляются специфичные поля
rack_maker.check_rack_fields_presence() 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() create_child_frame.should_be_toolbar_buttons()
@ -112,8 +114,8 @@ class TestCreateRackElement:
serial="TEST123456", serial="TEST123456",
inventory="INV-001", inventory="INV-001",
comment="Тестовая стойка для автоматизации", comment="Тестовая стойка для автоматизации",
cable_entry="Сверху", state="Введен в эксплуатацию",
state="В эксплуатации" cable_entry="сверху"
) )
# Заполняем данные стойки # Заполняем данные стойки
@ -123,7 +125,7 @@ class TestCreateRackElement:
create_child_frame.click_add_button() create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000) 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: 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" rack_name = "Test-Rack-01"
# Проверяем, существует ли уже стойка с таким именем # Проверяем, существует ли уже стойка с таким именем
if not self._check_rack_existance(browser, rack_name): 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) 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: 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") 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.wait_for_timeout(2000)
create_child_frame.alert.close_alert_by_text(expected_alert_text) 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): def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data):
"""Выполняет один тест валидации обязательных полей. """Выполняет один тест валидации обязательных полей.
@ -215,13 +217,13 @@ class TestCreateRackElement:
"""Проверяет, заполнено ли combobox поле.""" """Проверяет, заполнено ли 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 field_element = create_child_frame.page.locator(field_locator).first
if not field_element.is_visible(): if not field_element.is_visible():
logger.info(f"Field '{field_name}' not visible") logger.debug(f"Field '{field_name}' not visible")
return False return False
# Проверяем наличие кнопки закрытия (крестика) - признак заполненного поля # Проверяем наличие кнопки закрытия (крестика) - признак заполненного поля
@ -236,27 +238,27 @@ class TestCreateRackElement:
field_text = field_element.text_content() or "" field_text = field_element.text_content() or ""
has_text = bool(field_text.strip()) 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 return has_close_button or has_text
# Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено # Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено
logger.info("Checking field: Depth (mm)") logger.debug("Checking field: Depth (mm)")
if is_field_filled("Глубина (мм)"): 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("Глубина (мм)") create_child_frame.clear_combobox_field("Глубина (мм)")
logger.info("Clearing completed for 'Depth (mm)'") logger.debug("Clearing completed for 'Depth (mm)'")
else: 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("Высота в юнитах"): 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("Высота в юнитах") create_child_frame.clear_combobox_field("Высота в юнитах")
logger.info("Clearing completed for 'Height in units'") logger.debug("Clearing completed for 'Height in units'")
else: 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( 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) 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.click_add_button()
create_child_frame.wait_for_timeout(3000) create_child_frame.wait_for_timeout(3000)
# Проверяем валидацию полей # Проверяем валидацию полей
logger.info("Checking validation results") logger.debug("Checking validation results")
if height_value: if height_value:
create_child_frame.check_field_error_not_highlighted("Высота в юнитах") create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
logger.info("Height field validation passed") logger.debug("Height field validation passed")
else: else:
create_child_frame.check_field_error_highlighted("Высота в юнитах") 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: if depth_value:
create_child_frame.check_field_error_not_highlighted("Глубина (мм)") create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.info("Depth field validation passed") logger.debug("Depth field validation passed")
else: else:
create_child_frame.check_field_error_highlighted("Глубина (мм)") create_child_frame.check_field_error_highlighted("Глубина (мм)")
logger.info("Depth field validation failed as expected") logger.debug("Depth field validation failed as expected")
# Обрабатываем alert-окна # Обрабатываем alert-окна
if not height_value: 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.check_alert_presence(expected_alert_height)
create_child_frame.alert.close_alert_by_text(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: 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.check_alert_presence(expected_alert_depth)
create_child_frame.alert.close_alert_by_text(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('Создать дочерний элемент в') 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: def test_required_fields_validation(self, browser: Page) -> None:
""" """
@ -382,14 +384,14 @@ class TestCreateRackElement:
# Выполняем тестовые случаи # Выполняем тестовые случаи
for test_case in test_cases: for test_case in test_cases:
logger.info(test_case["name"]) logger.debug(test_case["name"])
self._perform_required_fields_test( self._perform_required_fields_test(
create_child_frame, rack_maker, test_case["data"] 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. Тест: Заполняем все обязательные поля # 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" 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("Высота в юнитах") 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() create_child_frame.click_add_button()
@ -417,21 +419,21 @@ class TestCreateRackElement:
# Проверяем, что НЕТ alert-окон для всех обязательных полей # Проверяем, что НЕТ alert-окон для всех обязательных полей
create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000) create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000)
create_child_frame.alert.check_alert_absence(expected_alert_text_depth, 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: try:
create_child_frame.check_toolbar_title('Создать дочерний элемент в') create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.warning("Rack creation may not have completed successfully") logger.warning("Rack creation may not have completed successfully")
except AssertionError: 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: 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("Объекты") 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 element = browser.locator(nav_panel_locator).get_by_text(rack_name).first
if element.is_visible(): if element.is_visible():
logger.info(f"Rack with name '{rack_name}' found") logger.debug(f"Rack with name '{rack_name}' found")
return True return True
logger.info(f"Rack with name '{rack_name}' not found") logger.debug(f"Rack with name '{rack_name}' not found")
return False return False
def _create_rack(self, browser: Page, rack_name: str) -> None: 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") self.main_page.click_main_navigation_panel_item("test-zone")