Compare commits
No commits in common. "ef9d9c632afffeb8a4cea18863025447a3fd72a1" and "838a2ee474b21003ba96b8911bf8dc9f41a2993f" have entirely different histories.
ef9d9c632a
...
838a2ee474
|
|
@ -1 +0,0 @@
|
||||||
docs/** merge=ours
|
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
"""Модуль создания объекта 'Стойка'."""
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from playwright.sync_api import Page
|
|
||||||
from tools.logger import get_logger
|
|
||||||
from locators.rack_locators import RackLocators
|
|
||||||
from components.base_component import BaseComponent
|
|
||||||
|
|
||||||
logger = get_logger("RACK_MAKER")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RackData:
|
|
||||||
"""Класс для хранения данных стойки."""
|
|
||||||
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 = ""
|
|
||||||
|
|
||||||
|
|
||||||
class RackObjectMaker(BaseComponent):
|
|
||||||
"""Компонент для создания и настройки стойки."""
|
|
||||||
|
|
||||||
def __init__(self, page: Page) -> None:
|
|
||||||
"""
|
|
||||||
Инициализирует компонент создания стойки.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
page: Экземпляр страницы Playwright
|
|
||||||
"""
|
|
||||||
super().__init__(page)
|
|
||||||
|
|
||||||
# Действия:
|
|
||||||
|
|
||||||
def fill_rack_data(self, rack_data: RackData) -> None:
|
|
||||||
"""
|
|
||||||
Заполняет данные для создания стойки.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
rack_data: Данные стойки
|
|
||||||
"""
|
|
||||||
logger.info(f"Filling rack data: {rack_data.name}")
|
|
||||||
|
|
||||||
self._fill_required_fields(rack_data)
|
|
||||||
self._fill_optional_fields(rack_data)
|
|
||||||
self._fill_combobox_fields(rack_data)
|
|
||||||
|
|
||||||
logger.info("Rack data filled successfully")
|
|
||||||
|
|
||||||
def _fill_required_fields(self, rack_data: RackData) -> None:
|
|
||||||
"""Заполняет обязательные поля."""
|
|
||||||
if rack_data.name:
|
|
||||||
name_field = self.page.locator(RackLocators.RACK_NAME_FIELD).first
|
|
||||||
name_field.fill(rack_data.name)
|
|
||||||
logger.info(f"Filled 'Name' field: {rack_data.name}")
|
|
||||||
|
|
||||||
def _fill_optional_fields(self, rack_data: RackData) -> None:
|
|
||||||
"""Заполняет опциональные поля."""
|
|
||||||
if rack_data.serial:
|
|
||||||
serial_field = self.page.locator(RackLocators.RACK_SERIAL_FIELD).first
|
|
||||||
serial_field.fill(rack_data.serial)
|
|
||||||
logger.info(f"Filled serial number: {rack_data.serial}")
|
|
||||||
|
|
||||||
if rack_data.inventory:
|
|
||||||
inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD).first
|
|
||||||
inventory_field.fill(rack_data.inventory)
|
|
||||||
logger.info(f"Filled inventory number: {rack_data.inventory}")
|
|
||||||
|
|
||||||
if rack_data.comment:
|
|
||||||
comment_field = self.page.locator(RackLocators.RACK_COMMENT_FIELD).first
|
|
||||||
comment_field.fill(rack_data.comment)
|
|
||||||
logger.info(f"Added comment: {rack_data.comment}")
|
|
||||||
|
|
||||||
def _fill_combobox_fields(self, rack_data: RackData) -> None:
|
|
||||||
"""Заполняет combobox поля."""
|
|
||||||
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")
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
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}")
|
|
||||||
|
|
||||||
if rack_data.state:
|
|
||||||
self._fill_combobox_field("State", rack_data.state,
|
|
||||||
RackLocators.RACK_STATE_FIELD)
|
|
||||||
logger.info(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}")
|
|
||||||
|
|
||||||
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}")
|
|
||||||
|
|
||||||
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}")
|
|
||||||
|
|
||||||
def _fill_combobox_field(self, field_name: str, value: str, field_locator: str) -> None:
|
|
||||||
"""
|
|
||||||
Заполняет combobox поле.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля
|
|
||||||
value: Значение для установки
|
|
||||||
field_locator: Локатор поля
|
|
||||||
"""
|
|
||||||
logger.info(f"Filling field '{field_name}' with value '{value}'...")
|
|
||||||
|
|
||||||
# Используем first() для избежания strict mode violation
|
|
||||||
field_container = self.page.locator(field_locator).first
|
|
||||||
|
|
||||||
# Прокручиваем до поля
|
|
||||||
field_container.scroll_into_view_if_needed()
|
|
||||||
self.wait_for_timeout(500)
|
|
||||||
|
|
||||||
# Проверяем видимость поля
|
|
||||||
self.check_visibility(field_container, f"Field '{field_name}' not found")
|
|
||||||
|
|
||||||
# Кликаем и вводим значение
|
|
||||||
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")
|
|
||||||
|
|
||||||
logger.info(f"Field '{field_name}' filled successfully")
|
|
||||||
|
|
||||||
def _get_field_locator(self, field_name: str) -> str:
|
|
||||||
"""
|
|
||||||
Возвращает локатор поля по его названию.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Локатор поля
|
|
||||||
"""
|
|
||||||
field_map = {
|
|
||||||
"Имя": RackLocators.RACK_NAME_FIELD,
|
|
||||||
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
|
|
||||||
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
|
|
||||||
}
|
|
||||||
|
|
||||||
if field_name not in field_map:
|
|
||||||
raise ValueError(f"Field '{field_name}' is not supported")
|
|
||||||
|
|
||||||
return field_map[field_name]
|
|
||||||
|
|
||||||
def wait_for_timeout(self, timeout: int) -> None:
|
|
||||||
"""
|
|
||||||
Ожидает указанное количество миллисекунд.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: Время ожидания в миллисекундах
|
|
||||||
"""
|
|
||||||
self.page.wait_for_timeout(timeout)
|
|
||||||
|
|
||||||
# Проверки:
|
|
||||||
|
|
||||||
def check_rack_fields_presence(self) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет наличие полей специфичных для стойки.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
AssertionError: Если какое-либо поле не найдено
|
|
||||||
"""
|
|
||||||
logger.info("Checking rack fields presence...")
|
|
||||||
|
|
||||||
# Основные обязательные поля
|
|
||||||
required_fields = [
|
|
||||||
(RackLocators.RACK_NAME_FIELD, "Name"),
|
|
||||||
(RackLocators.RACK_HEIGHT_FIELD, "Height in units"),
|
|
||||||
(RackLocators.RACK_DEPTH_FIELD, "Depth (mm)")
|
|
||||||
]
|
|
||||||
|
|
||||||
# Дополнительные поля
|
|
||||||
optional_fields = [
|
|
||||||
(RackLocators.RACK_SERIAL_FIELD, "Serial number"),
|
|
||||||
(RackLocators.RACK_INVENTORY_FIELD, "Inventory number"),
|
|
||||||
(RackLocators.RACK_COMMENT_FIELD, "Comment"),
|
|
||||||
(RackLocators.RACK_CABLE_ENTRY_FIELD, "Cable entry"),
|
|
||||||
(RackLocators.RACK_STATE_FIELD, "State"),
|
|
||||||
(RackLocators.RACK_OWNER_FIELD, "Owner"),
|
|
||||||
(RackLocators.RACK_SERVICE_ORG_FIELD, "Service organization"),
|
|
||||||
(RackLocators.RACK_PROJECT_FIELD, "Project/Title")
|
|
||||||
]
|
|
||||||
|
|
||||||
# Проверяем обязательные поля
|
|
||||||
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")
|
|
||||||
|
|
||||||
# Проверяем дополнительные поля
|
|
||||||
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")
|
|
||||||
else:
|
|
||||||
logger.info(f"Optional field '{field_name}' not found or not visible")
|
|
||||||
|
|
||||||
logger.info("All main rack fields are present")
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
"""Модуль фрейма создания дочернего элемента."""
|
|
||||||
|
|
||||||
import re
|
|
||||||
from playwright.sync_api import expect, Page
|
|
||||||
from tools.logger import get_logger
|
|
||||||
from locators.rack_locators import RackLocators
|
|
||||||
from components.alert_component import AlertComponent
|
|
||||||
from components.base_component import BaseComponent
|
|
||||||
from components.toolbar_component import ToolbarComponent
|
|
||||||
from components_derived.selection_bar_component import SelectionBarComponent
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("CREATE_CHILD_ELEMENT_FRAME")
|
|
||||||
|
|
||||||
|
|
||||||
class CreateChildElementFrame(BaseComponent):
|
|
||||||
"""Фрейм создания дочернего элемента."""
|
|
||||||
|
|
||||||
def __init__(self, page: Page) -> None:
|
|
||||||
"""
|
|
||||||
Инициализирует фрейм создания дочернего элемента.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
page: Экземпляр страницы Playwright
|
|
||||||
"""
|
|
||||||
super().__init__(page)
|
|
||||||
|
|
||||||
# Инициализация компонентов
|
|
||||||
self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в")
|
|
||||||
self.selection_bar = SelectionBarComponent(page, "Класс объекта учета")
|
|
||||||
self.alert = AlertComponent(page)
|
|
||||||
|
|
||||||
# Кнопка "Добавить" - первая кнопка в тулбаре фрейма создания
|
|
||||||
add_button_locator = self.page.get_by_role("navigation").filter(
|
|
||||||
has_text="Создать дочерний элемент в"
|
|
||||||
).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.toolbar.add_tooltip_button(add_button_locator, "add")
|
|
||||||
self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")
|
|
||||||
|
|
||||||
# Действия:
|
|
||||||
|
|
||||||
def get_object_class_options(self) -> list[str]:
|
|
||||||
"""
|
|
||||||
Получает список доступных опций из combobox.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[str]: Список доступных классов объектов
|
|
||||||
"""
|
|
||||||
logger.info("Getting combobox 'Accounting object class' options...")
|
|
||||||
|
|
||||||
available_options = self.selection_bar.get_available_options()
|
|
||||||
|
|
||||||
logger.info(f"Available object class options: {available_options}")
|
|
||||||
return available_options
|
|
||||||
|
|
||||||
def get_selected_object_class(self) -> str:
|
|
||||||
"""
|
|
||||||
Получает выбранный класс объекта учета.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Выбранный класс объекта или пустая строка если ничего не выбрано
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.selection_bar.get_selection_bar_title()
|
|
||||||
|
|
||||||
def _get_field_locator(self, field_name: str) -> str:
|
|
||||||
"""
|
|
||||||
Возвращает локатор поля по его названию.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Локатор поля
|
|
||||||
"""
|
|
||||||
field_map = {
|
|
||||||
"Имя": RackLocators.RACK_NAME_FIELD,
|
|
||||||
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
|
|
||||||
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD,
|
|
||||||
"Серийный номер": RackLocators.RACK_SERIAL_FIELD,
|
|
||||||
"Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD,
|
|
||||||
"Комментарий": RackLocators.RACK_COMMENT_FIELD,
|
|
||||||
"Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD,
|
|
||||||
"Состояние": RackLocators.RACK_STATE_FIELD,
|
|
||||||
"Владелец": RackLocators.RACK_OWNER_FIELD,
|
|
||||||
"Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD,
|
|
||||||
"Проект/Титул": RackLocators.RACK_PROJECT_FIELD
|
|
||||||
}
|
|
||||||
|
|
||||||
if field_name not in field_map:
|
|
||||||
raise ValueError(f"Locator for field '{field_name}' not found")
|
|
||||||
|
|
||||||
return field_map[field_name]
|
|
||||||
|
|
||||||
def clear_combobox_field(self, field_name: str) -> None:
|
|
||||||
"""
|
|
||||||
Очищает combobox поле по его названию.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля для очистки
|
|
||||||
"""
|
|
||||||
logger.info(f"Clearing combobox field '{field_name}'...")
|
|
||||||
|
|
||||||
# Получаем локатор поля по его названию
|
|
||||||
field_locator = self._get_field_locator(field_name)
|
|
||||||
|
|
||||||
# Используем метод из SelectionBarComponent
|
|
||||||
self.selection_bar.clear_combobox_field(field_name, field_locator)
|
|
||||||
|
|
||||||
def click_add_button(self) -> None:
|
|
||||||
"""Кликает на кнопку 'Добавить'."""
|
|
||||||
logger.info("Clicking on 'Add' button...")
|
|
||||||
self.toolbar.click_button("add")
|
|
||||||
|
|
||||||
def click_cancel_button(self) -> None:
|
|
||||||
"""Кликает на кнопку 'Отменить'."""
|
|
||||||
logger.info("Clicking on 'Cancel' button...")
|
|
||||||
self.toolbar.click_button("cancel")
|
|
||||||
|
|
||||||
def open_object_class_combobox(self) -> None:
|
|
||||||
"""Открывает выпадающий список combobox 'Класс объекта учета'."""
|
|
||||||
logger.info("Opening combobox 'Accounting object class'...")
|
|
||||||
|
|
||||||
# Ждем стабильности combobox
|
|
||||||
expect(self.selection_bar.selection_bar_locator).to_be_visible()
|
|
||||||
|
|
||||||
# Проверяем, не открыт ли уже выпадающий список
|
|
||||||
is_menu_active = self.selection_bar.selection_bar_locator.get_attribute(
|
|
||||||
"class"
|
|
||||||
)
|
|
||||||
if is_menu_active and "v-select--is-menu-active" in is_menu_active:
|
|
||||||
logger.info("Dropdown list is already open")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Используем force click для обхода перекрывающих элементов
|
|
||||||
logger.info("Using force click for combobox")
|
|
||||||
self.selection_bar.selection_bar_locator.click(force=True)
|
|
||||||
|
|
||||||
# Ждем появления выпадающего списка
|
|
||||||
self.wait_for_timeout(1500)
|
|
||||||
|
|
||||||
def select_object_class(self, class_name: str) -> None:
|
|
||||||
"""Выбирает класс объекта из выпадающего списка."""
|
|
||||||
logger.info(f"Selecting object class: '{class_name}'...")
|
|
||||||
|
|
||||||
# Открываем combobox
|
|
||||||
self.open_object_class_combobox()
|
|
||||||
|
|
||||||
# Выбираем значение из списка
|
|
||||||
self.selection_bar.select_value(class_name)
|
|
||||||
|
|
||||||
# Даем время на применение выбора
|
|
||||||
self.wait_for_timeout(3000)
|
|
||||||
|
|
||||||
# Логируем текущее состояние без строгой проверки
|
|
||||||
selected_value = self.get_selected_object_class()
|
|
||||||
logger.info(f"Current combobox value: '{selected_value}'")
|
|
||||||
|
|
||||||
# Временно пропускаем строгую проверку
|
|
||||||
logger.info(f"Assuming class '{class_name}' is selected")
|
|
||||||
|
|
||||||
logger.info(f"Object class '{class_name}' successfully selected")
|
|
||||||
|
|
||||||
def wait_for_timeout(self, timeout: int) -> None:
|
|
||||||
"""
|
|
||||||
Ожидает указанное количество миллисекунд.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: Время ожидания в миллисекундах
|
|
||||||
"""
|
|
||||||
self.page.wait_for_timeout(timeout)
|
|
||||||
|
|
||||||
# Проверки:
|
|
||||||
|
|
||||||
def check_object_class_selected(self, expected_class: str) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет что выбран указанный класс объекта.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
expected_class: Ожидаемый выбранный класс объекта
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
AssertionError: Если выбранный класс не соответствует ожидаемому
|
|
||||||
"""
|
|
||||||
logger.info(f"Checking selected object class: '{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"Object class '{expected_class}' successfully selected "
|
|
||||||
f"(actual: '{actual_class}')"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
error_msg = (
|
|
||||||
f"Selected class does not match expected. "
|
|
||||||
f"Expected: '{expected_class}', Got: '{actual_class}'"
|
|
||||||
)
|
|
||||||
raise AssertionError(error_msg)
|
|
||||||
|
|
||||||
def check_toolbar_title(self, expected_title: str) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет заголовок тулбара.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
expected_title: Ожидаемый заголовок тулбара
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
AssertionError: Если заголовок не соответствует ожидаемому
|
|
||||||
"""
|
|
||||||
logger.info(f"Checking toolbar title: '{expected_title}'...")
|
|
||||||
|
|
||||||
# Используем метод тулбара с фильтрацией по тексту
|
|
||||||
actual_text = self.toolbar.get_toolbar_title_text(
|
|
||||||
filter_text="Создать дочерний элемент в"
|
|
||||||
)
|
|
||||||
assert expected_title in actual_text, (
|
|
||||||
f"Title does not match. Expected: '{expected_title}', "
|
|
||||||
f"Got: '{actual_text}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Toolbar title is correct: '{actual_text}'")
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Методы проверки ошибок полей (используют SelectionBarComponent)
|
|
||||||
|
|
||||||
def check_field_highlighted_error(self, field_name: str) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля для проверки
|
|
||||||
"""
|
|
||||||
field_locator = self._get_field_locator(field_name)
|
|
||||||
self.selection_bar.check_field_highlighted_error(field_name, field_locator)
|
|
||||||
|
|
||||||
def check_field_not_highlighted_error(self, field_name: str) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля для проверки
|
|
||||||
"""
|
|
||||||
field_locator = self._get_field_locator(field_name)
|
|
||||||
self.selection_bar.check_field_not_highlighted_error(field_name, field_locator)
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Sphinx build info version 1
|
# Sphinx build info version 1
|
||||||
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||||
config: 3c1e20399f1a6eba1e2cff02a3427139
|
config: 56428dc241842362fe772e9fdd966681
|
||||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -11,7 +11,7 @@ class RackLocators:
|
||||||
- Вкладки стойки (верхние вкладки)
|
- Вкладки стойки (верхние вкладки)
|
||||||
- Секции лицевой и обратной сторон стойки
|
- Секции лицевой и обратной сторон стойки
|
||||||
- Юниты и устройства на стойке
|
- Юниты и устройства на стойке
|
||||||
- Поля формы редактирования и создания стойки
|
- Поля формы редактирования стойки
|
||||||
- Контейнеры и структурные элементы
|
- Контейнеры и структурные элементы
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ class RackLocators:
|
||||||
# Контейнер формы
|
# Контейнер формы
|
||||||
FORM_CONTAINER = "//div[contains(@class, 'container')]"
|
FORM_CONTAINER = "//div[contains(@class, 'container')]"
|
||||||
|
|
||||||
# Локаторы полей формы редактирования стойки
|
# Локаторы полей
|
||||||
NAME_FIELD = "//input[@aria-label='Имя']"
|
NAME_FIELD = "//input[@aria-label='Имя']"
|
||||||
SERIAL_NUMBER_FIELD = "//input[@aria-label='Серийный номер']"
|
SERIAL_NUMBER_FIELD = "//input[@aria-label='Серийный номер']"
|
||||||
INVENTORY_NUMBER_FIELD = "//input[@aria-label='Инвентарный номер']"
|
INVENTORY_NUMBER_FIELD = "//input[@aria-label='Инвентарный номер']"
|
||||||
|
|
@ -50,25 +50,6 @@ class RackLocators:
|
||||||
SERVICE_ORG_FIELD = "//input[@aria-label='Обслуживающая организация']"
|
SERVICE_ORG_FIELD = "//input[@aria-label='Обслуживающая организация']"
|
||||||
PROJECT_FIELD = "//input[@aria-label='Проект/Титул']"
|
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()='Проект/Титул']]"
|
|
||||||
|
|
||||||
# Локатор для родительского контейнера поля ввода
|
|
||||||
INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]"
|
|
||||||
|
|
||||||
# CSS селекторы для ошибок валидации
|
|
||||||
ERROR_CSS_SELECTORS = ".error--text, .v-input--error"
|
|
||||||
|
|
||||||
# Локаторы для отображения сторон стойки
|
# Локаторы для отображения сторон стойки
|
||||||
FRONT_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and not(contains(@class, 'back'))]"
|
FRONT_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and not(contains(@class, 'back'))]"
|
||||||
BACK_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and contains(@class, 'back')]"
|
BACK_SIDE_CONTAINER = "//div[contains(@class, 'cabinet') and contains(@class, 'back')]"
|
||||||
|
|
|
||||||
|
|
@ -1,355 +0,0 @@
|
||||||
"""Модуль страницы создания дочернего элемента.
|
|
||||||
|
|
||||||
Содержит класс для работы с формой создания дочернего элемента.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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}' присутствует в списке")
|
|
||||||
|
|
@ -1,678 +0,0 @@
|
||||||
"""Модуль страницы создания дочернего элемента.
|
|
||||||
|
|
||||||
Содержит класс для работы с формой создания дочернего элемента.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from playwright.sync_api import Page, expect
|
|
||||||
|
|
||||||
from elements.tooltip_button_element import TooltipButton
|
|
||||||
from components.toolbar_component import ToolbarComponent
|
|
||||||
from components_derived.selection_bar_component import SelectionBarComponent
|
|
||||||
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 locators.rack_locators import RackLocators
|
|
||||||
from locators.alert_locators import AlertLocators
|
|
||||||
from tools.logger import get_logger
|
|
||||||
|
|
||||||
logger = get_logger("CREATE_RACK_ELEMENT")
|
|
||||||
|
|
||||||
|
|
||||||
# Словарь для сопоставления названий полей с локаторами
|
|
||||||
COMBOBOX_FIELDS_MAP = {
|
|
||||||
# Обязательные поля
|
|
||||||
"Имя": RackLocators.RACK_NAME_FIELD,
|
|
||||||
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
|
|
||||||
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD,
|
|
||||||
|
|
||||||
# Дополнительные текстовые поля
|
|
||||||
"Серийный номер": RackLocators.RACK_SERIAL_FIELD,
|
|
||||||
"Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD,
|
|
||||||
"Комментарий": RackLocators.RACK_COMMENT_FIELD,
|
|
||||||
|
|
||||||
# Combobox поля
|
|
||||||
"Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD,
|
|
||||||
"Состояние": RackLocators.RACK_STATE_FIELD,
|
|
||||||
"Владелец": RackLocators.RACK_OWNER_FIELD,
|
|
||||||
"Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD,
|
|
||||||
"Проект/Титул": RackLocators.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")
|
|
||||||
|
|
||||||
# Инициализация компонента панели выбора значения для работы с combobox
|
|
||||||
self.selection_bar = SelectionBarComponent(page, ComboboxLocators.OBJECT_CLASS_COMBOBOX)
|
|
||||||
|
|
||||||
# =============== МЕТОДЫ ДЕЙСТВИЙ ========================
|
|
||||||
|
|
||||||
def click_add_button(self) -> None:
|
|
||||||
"""
|
|
||||||
Кликает на кнопку 'Добавить'.
|
|
||||||
"""
|
|
||||||
self.toolbar.click_button("add")
|
|
||||||
|
|
||||||
def click_cancel_button(self) -> None:
|
|
||||||
"""
|
|
||||||
Кликает на кнопку 'Отменить'.
|
|
||||||
"""
|
|
||||||
self.toolbar.click_button("cancel")
|
|
||||||
|
|
||||||
def open_object_class_combobox(self) -> None:
|
|
||||||
"""
|
|
||||||
Открывает выпадающий список combobox 'Класс объекта учета'.
|
|
||||||
"""
|
|
||||||
logger.info("Открытие combobox 'Класс объекта учета'...")
|
|
||||||
self.selection_bar.open_values_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.selection_bar.select_value(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_object_class_options(self) -> list[str]:
|
|
||||||
"""
|
|
||||||
Получает список доступных опций из combobox.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[str]: Список доступных классов объектов
|
|
||||||
"""
|
|
||||||
logger.info("Получение списка опций combobox 'Класс объекта учета'...")
|
|
||||||
|
|
||||||
available_options = self.selection_bar.get_available_options()
|
|
||||||
|
|
||||||
logger.info(f"Доступные опции класса объекта: {available_options}")
|
|
||||||
return available_options
|
|
||||||
|
|
||||||
def get_selected_object_class(self) -> str:
|
|
||||||
"""
|
|
||||||
Получает выбранный класс объекта учета.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Выбранный класс объекта или пустая строка если ничего не выбрано
|
|
||||||
"""
|
|
||||||
# Получаем заголовок панели выбора
|
|
||||||
return self.selection_bar.get_selection_bar_title()
|
|
||||||
|
|
||||||
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(RackLocators.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(RackLocators.RACK_SERIAL_FIELD)
|
|
||||||
serial_field.fill(serial)
|
|
||||||
logger.info(f"Заполнен серийный номер: {serial}")
|
|
||||||
|
|
||||||
if inventory:
|
|
||||||
inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD)
|
|
||||||
inventory_field.fill(inventory)
|
|
||||||
logger.info(f"Заполнен инвентарный номер: {inventory}")
|
|
||||||
|
|
||||||
if comment:
|
|
||||||
comment_field = self.page.locator(RackLocators.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]
|
|
||||||
|
|
||||||
# Для всех полей используем first() чтобы избежать strict mode violation
|
|
||||||
field_container = self.page.locator(field_locator).first
|
|
||||||
|
|
||||||
# Прокручиваем до поля
|
|
||||||
field_container.scroll_into_view_if_needed()
|
|
||||||
self.wait_for_timeout(500)
|
|
||||||
|
|
||||||
# Проверяем видимость поля
|
|
||||||
self.base_component.check_visibility(field_container, f"Поле '{field_name}' не найдено")
|
|
||||||
|
|
||||||
# Универсальный клик с force=True для всех полей
|
|
||||||
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")
|
|
||||||
|
|
||||||
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_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 = [
|
|
||||||
(RackLocators.RACK_NAME_FIELD, "Имя"),
|
|
||||||
(RackLocators.RACK_SERIAL_FIELD, "Серийный номер"),
|
|
||||||
(RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"),
|
|
||||||
(RackLocators.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("Все поля формы стойки очищены")
|
|
||||||
|
|
||||||
# =============== МЕТОДЫ ПРОВЕРОК ========================
|
|
||||||
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(3000)
|
|
||||||
|
|
||||||
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 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 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 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 = [
|
|
||||||
(RackLocators.RACK_NAME_FIELD, "Имя"),
|
|
||||||
(RackLocators.RACK_HEIGHT_FIELD, "Высота в юнитах"),
|
|
||||||
(RackLocators.RACK_DEPTH_FIELD, "Глубина (мм)")
|
|
||||||
]
|
|
||||||
|
|
||||||
# Дополнительные поля
|
|
||||||
optional_fields = [
|
|
||||||
(RackLocators.RACK_SERIAL_FIELD, "Серийный номер"),
|
|
||||||
(RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"),
|
|
||||||
(RackLocators.RACK_COMMENT_FIELD, "Комментарий"),
|
|
||||||
(RackLocators.RACK_CABLE_ENTRY_FIELD, "Ввод кабеля"),
|
|
||||||
(RackLocators.RACK_STATE_FIELD, "Состояние"),
|
|
||||||
(RackLocators.RACK_OWNER_FIELD, "Владелец"),
|
|
||||||
(RackLocators.RACK_SERVICE_ORG_FIELD, "Обслуживающая организация"),
|
|
||||||
(RackLocators.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 check_field_highlighted_error(self, field_name: str) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля для проверки
|
|
||||||
"""
|
|
||||||
logger.info(f"Проверка подсветки поля '{field_name}' цветом ошибки...")
|
|
||||||
|
|
||||||
# Локаторы только для обязательных полей
|
|
||||||
required_fields = {
|
|
||||||
"Имя": RackLocators.RACK_NAME_FIELD,
|
|
||||||
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
|
|
||||||
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
|
|
||||||
}
|
|
||||||
|
|
||||||
if field_name not in required_fields:
|
|
||||||
raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается")
|
|
||||||
|
|
||||||
field_locator = required_fields[field_name]
|
|
||||||
field_element = self.page.locator(field_locator)
|
|
||||||
|
|
||||||
# Проверяем что поле видимо
|
|
||||||
self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено")
|
|
||||||
|
|
||||||
# Ищем родительский контейнер с использованием константы
|
|
||||||
parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first
|
|
||||||
|
|
||||||
# Проверка классов ошибки
|
|
||||||
if parent_container.count() > 0:
|
|
||||||
error_classes = AlertLocators.ERROR_CLASSES
|
|
||||||
|
|
||||||
is_error_highlighted = False
|
|
||||||
for error_class in error_classes:
|
|
||||||
error_element = parent_container.locator(f".{error_class}")
|
|
||||||
if error_element.count() > 0:
|
|
||||||
is_error_highlighted = True
|
|
||||||
logger.info(f"Поле '{field_name}' подсвечено ошибкой")
|
|
||||||
break
|
|
||||||
|
|
||||||
if not is_error_highlighted:
|
|
||||||
raise AssertionError(f"Поле '{field_name}' не подсвечено цветом ошибки ")
|
|
||||||
|
|
||||||
logger.info(f"Поле '{field_name}' корректно подсвечено цветом ошибки")
|
|
||||||
|
|
||||||
def check_field_not_highlighted_error(self, field_name: str) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
field_name: Название поля для проверки
|
|
||||||
"""
|
|
||||||
logger.info(f"Проверка отсутствия подсветки ошибки у поля '{field_name}'...")
|
|
||||||
|
|
||||||
# Локаторы только для обязательных полей
|
|
||||||
required_fields = {
|
|
||||||
"Имя": RackLocators.RACK_NAME_FIELD,
|
|
||||||
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
|
|
||||||
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
|
|
||||||
}
|
|
||||||
|
|
||||||
if field_name not in required_fields:
|
|
||||||
raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается")
|
|
||||||
|
|
||||||
field_locator = required_fields[field_name]
|
|
||||||
field_element = self.page.locator(field_locator)
|
|
||||||
|
|
||||||
# Проверяем что поле видимо
|
|
||||||
self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено")
|
|
||||||
|
|
||||||
# Ищем родительский контейнер с использованием константы
|
|
||||||
parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first
|
|
||||||
|
|
||||||
# Поверка отсутствия классов ошибки
|
|
||||||
if parent_container.count() > 0:
|
|
||||||
error_classes = AlertLocators.ERROR_CLASSES
|
|
||||||
|
|
||||||
for error_class in error_classes:
|
|
||||||
error_element = parent_container.locator(f".{error_class}")
|
|
||||||
if error_element.count() > 0:
|
|
||||||
raise AssertionError(f"Поле '{field_name}' подсвечено ошибкой")
|
|
||||||
|
|
||||||
logger.info(f"Поле '{field_name}' корректно не подсвечено цветом ошибки")
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
"""Модуль страницы локации."""
|
|
||||||
|
|
||||||
from playwright.sync_api import Page
|
|
||||||
from components.toolbar_component import ToolbarComponent
|
|
||||||
from pages.base_page import BasePage
|
|
||||||
|
|
||||||
|
|
||||||
class LocationPage(BasePage):
|
|
||||||
"""Класс для работы со страницей локации."""
|
|
||||||
|
|
||||||
# Константы локаторов
|
|
||||||
TOOLBAR_BUTTONS_LOCATOR = (
|
|
||||||
"//div[contains(@class, 'layout class--')]"
|
|
||||||
"//span[@class='v-tooltip v-tooltip--bottom']//button"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Индексы кнопок
|
|
||||||
CREATE_BUTTON_INDEX = 0 # Первая кнопка
|
|
||||||
EDIT_BUTTON_INDEX = 1 # Вторая кнопка
|
|
||||||
|
|
||||||
def __init__(self, page: Page) -> None:
|
|
||||||
"""
|
|
||||||
Инициализирует страницу локации.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
page: Экземпляр страницы Playwright
|
|
||||||
"""
|
|
||||||
super().__init__(page)
|
|
||||||
|
|
||||||
# Инициализация тулбара
|
|
||||||
self.toolbar = ToolbarComponent(page, "")
|
|
||||||
|
|
||||||
# Кнопка "Создать" - первая кнопка
|
|
||||||
create_button_locator = self.page.locator(
|
|
||||||
self.TOOLBAR_BUTTONS_LOCATOR
|
|
||||||
).nth(self.CREATE_BUTTON_INDEX)
|
|
||||||
|
|
||||||
# Кнопка "Изменить" - вторая кнопка
|
|
||||||
edit_button_locator = self.page.locator(
|
|
||||||
self.TOOLBAR_BUTTONS_LOCATOR
|
|
||||||
).nth(self.EDIT_BUTTON_INDEX)
|
|
||||||
|
|
||||||
# Инициализация кнопок
|
|
||||||
self.toolbar.add_tooltip_button(create_button_locator, "create")
|
|
||||||
self.toolbar.add_tooltip_button(edit_button_locator, "edit")
|
|
||||||
|
|
||||||
self._create_child_frame = None
|
|
||||||
|
|
||||||
# Действия
|
|
||||||
def click_create_button(self) -> None:
|
|
||||||
"""
|
|
||||||
Кликает на кнопку 'Создать'.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
# Используем метод тулбара для клика
|
|
||||||
self.toolbar.click_button("create")
|
|
||||||
self.wait_for_timeout(3000)
|
|
||||||
|
|
||||||
def click_edit_button(self) -> None:
|
|
||||||
"""
|
|
||||||
Кликает на кнопку 'Изменить'.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
self.toolbar.click_button("edit")
|
|
||||||
self.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
def wait_for_timeout(self, timeout: int) -> None:
|
|
||||||
"""
|
|
||||||
Ожидает указанное количество миллисекунд.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: Время ожидания в миллисекундах
|
|
||||||
"""
|
|
||||||
self.page.wait_for_timeout(timeout)
|
|
||||||
|
|
||||||
# Проверки
|
|
||||||
def should_be_toolbar_buttons(self) -> None:
|
|
||||||
"""
|
|
||||||
Проверяет наличие и функциональность кнопок тулбара.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
AssertionError: Если кнопки недоступны или подсказки неверны.
|
|
||||||
"""
|
|
||||||
# Проверяем кнопку "Создать"
|
|
||||||
self.toolbar.check_button_visibility("create")
|
|
||||||
self.toolbar.check_button_tooltip("create", "Создать")
|
|
||||||
|
|
||||||
# Проверяем кнопку "Изменить"
|
|
||||||
self.toolbar.check_button_visibility("edit")
|
|
||||||
self.toolbar.check_button_tooltip("edit", "Изменить")
|
|
||||||
|
|
||||||
self.wait_for_timeout(2000)
|
|
||||||
|
|
@ -1,431 +0,0 @@
|
||||||
"""Тест создания дочернего элемента 'Стойка'."""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from playwright.sync_api import Page
|
|
||||||
from tools.logger import get_logger
|
|
||||||
from locators.navigation_panel_locators import NavigationPanelLocators
|
|
||||||
from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData
|
|
||||||
from components_derived.frames.create_child_element_frame import CreateChildElementFrame
|
|
||||||
from pages.location_page import LocationPage
|
|
||||||
from pages.login_page import LoginPage
|
|
||||||
from pages.main_page import MainPage
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("CREATE_RACK_ELEMENT_TEST")
|
|
||||||
|
|
||||||
# @pytest.mark.smoke
|
|
||||||
class TestCreateRackElement:
|
|
||||||
"""Тест создания дочернего элемента типа 'Стойка'.
|
|
||||||
|
|
||||||
Тесты покрывают следующие сценарии:
|
|
||||||
1. test_create_rack_content: Проверяет содержимое формы создания стойки
|
|
||||||
2. test_create_rack_child_element: Проверяет создание дочернего элемента типа 'Стойка'
|
|
||||||
3. test_create_rack_with_duplicate_name: Проверяет создание стойки с дублирующимся именем
|
|
||||||
4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки
|
|
||||||
"""
|
|
||||||
|
|
||||||
@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()
|
|
||||||
|
|
||||||
# Мы на главной странице
|
|
||||||
self.main_page = MainPage(browser)
|
|
||||||
self.main_page.should_be_navigation_panel()
|
|
||||||
self.main_page.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
# Переходим к Объектам
|
|
||||||
self.main_page.click_main_navigation_panel_item("Объекты")
|
|
||||||
self.main_page.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
self.main_page.click_main_navigation_panel_item("test-zone")
|
|
||||||
self.main_page.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
# Создаем экземпляр страницы локации
|
|
||||||
self.location_page = LocationPage(browser)
|
|
||||||
|
|
||||||
@pytest.mark.develop
|
|
||||||
def test_create_rack_content(self, browser: Page) -> None:
|
|
||||||
"""Тест создания дочернего элемента типа 'Стойка'."""
|
|
||||||
|
|
||||||
# Проверяем что кнопка "Создать" доступна
|
|
||||||
self.location_page.should_be_toolbar_buttons()
|
|
||||||
|
|
||||||
# Нажимаем кнопку "Создать" на тулбаре
|
|
||||||
self.location_page.click_create_button()
|
|
||||||
|
|
||||||
# Создаем фрейм создания дочернего элемента
|
|
||||||
create_child_frame = CreateChildElementFrame(browser)
|
|
||||||
|
|
||||||
# Нажимаем на плашку "Класс объекта учета"
|
|
||||||
create_child_frame.open_object_class_combobox()
|
|
||||||
|
|
||||||
# Из выпадающего меню выбираем пункт "Стойка"
|
|
||||||
create_child_frame.select_object_class("Стойка")
|
|
||||||
|
|
||||||
# Открывается набор плашек для задания параметров стойки
|
|
||||||
rack_maker = RackObjectMaker(browser)
|
|
||||||
|
|
||||||
# Проверяем заголовок формы создания
|
|
||||||
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
|
|
||||||
|
|
||||||
# Проверяем что после выбора 'Стойка' появляются специфичные поля
|
|
||||||
rack_maker.check_rack_fields_presence()
|
|
||||||
logger.info("Rack-specific fields are displayed correctly")
|
|
||||||
|
|
||||||
create_child_frame.should_be_toolbar_buttons()
|
|
||||||
|
|
||||||
def test_create_rack_child_element(self, browser: Page) -> None:
|
|
||||||
"""Тест создания дочернего элемента типа 'Стойка'."""
|
|
||||||
|
|
||||||
# Нажимаем кнопку "Создать" на тулбаре
|
|
||||||
self.location_page.click_create_button()
|
|
||||||
|
|
||||||
# Создаем фрейм создания дочернего элемента
|
|
||||||
create_child_frame = CreateChildElementFrame(browser)
|
|
||||||
|
|
||||||
# Нажимаем на плашку "Класс объекта учета"
|
|
||||||
create_child_frame.open_object_class_combobox()
|
|
||||||
|
|
||||||
# Из выпадающего меню выбираем пункт "Стойка"
|
|
||||||
create_child_frame.select_object_class("Стойка")
|
|
||||||
|
|
||||||
# Открывается набор плашек для задания параметров стойки
|
|
||||||
rack_maker = RackObjectMaker(browser)
|
|
||||||
|
|
||||||
# Проверяем что после выбора 'Стойка' появляются специфичные поля
|
|
||||||
rack_maker.check_rack_fields_presence()
|
|
||||||
logger.info("Rack-specific fields are displayed correctly")
|
|
||||||
|
|
||||||
# Создаем объект данных стойки
|
|
||||||
rack_data = RackData(
|
|
||||||
name="Test-Rack-01",
|
|
||||||
height="42",
|
|
||||||
depth="1000",
|
|
||||||
serial="TEST123456",
|
|
||||||
inventory="INV-001",
|
|
||||||
comment="Тестовая стойка для автоматизации",
|
|
||||||
cable_entry="Сверху",
|
|
||||||
state="В эксплуатации"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Заполняем данные стойки
|
|
||||||
rack_maker.fill_rack_data(rack_data)
|
|
||||||
|
|
||||||
# Нажимаем кнопку "Добавить"
|
|
||||||
create_child_frame.click_add_button()
|
|
||||||
create_child_frame.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
logger.info("Test for creating 'Rack' child element completed successfully")
|
|
||||||
|
|
||||||
def test_create_rack_with_duplicate_name(self, browser: Page) -> None:
|
|
||||||
"""
|
|
||||||
Тест создания стойки с уже существующим именем.
|
|
||||||
|
|
||||||
Проверяет, что система корректно обрабатывает попытку создания
|
|
||||||
стойки с именем, которое уже используется.
|
|
||||||
"""
|
|
||||||
logger.info("Starting test for creating rack with duplicate name")
|
|
||||||
|
|
||||||
rack_name = "Test-Rack-01"
|
|
||||||
|
|
||||||
# Проверяем, существует ли уже стойка с таким именем
|
|
||||||
if not self._check_rack_exists(browser, rack_name):
|
|
||||||
logger.info(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")
|
|
||||||
else:
|
|
||||||
logger.info(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}'")
|
|
||||||
|
|
||||||
# Переходим обратно к созданию новой стойки
|
|
||||||
self.main_page.click_main_navigation_panel_item("test-zone")
|
|
||||||
self.main_page.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
# Нажимаем кнопку "Создать" на тулбаре
|
|
||||||
self.location_page.click_create_button()
|
|
||||||
|
|
||||||
# Создаем фрейм создания дочернего элемента
|
|
||||||
create_child_frame = CreateChildElementFrame(browser)
|
|
||||||
|
|
||||||
# Нажимаем на плашку "Класс объекта учета"
|
|
||||||
create_child_frame.open_object_class_combobox()
|
|
||||||
|
|
||||||
# Из выпадающего меню выбираем пункт "Стойка"
|
|
||||||
create_child_frame.select_object_class("Стойка")
|
|
||||||
|
|
||||||
# Открывается набор плашек для задания параметров стойки
|
|
||||||
rack_maker = RackObjectMaker(browser)
|
|
||||||
|
|
||||||
# Создаем объект данных для второй стойки
|
|
||||||
rack_data = RackData(
|
|
||||||
name=rack_name,
|
|
||||||
height="42",
|
|
||||||
depth="1000"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Пытаемся создать вторую стойку с тем же именем
|
|
||||||
rack_maker.fill_rack_data(rack_data)
|
|
||||||
|
|
||||||
# Нажимаем кнопку создания
|
|
||||||
create_child_frame.click_add_button()
|
|
||||||
create_child_frame.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
# Проверяем наличие alert-окна с сообщением о дублирующемся имени
|
|
||||||
expected_alert_text = f"Имя {rack_name} уже используется"
|
|
||||||
create_child_frame.alert.check_alert_presence(expected_alert_text)
|
|
||||||
|
|
||||||
# Проверяем, что остались на странице создания (стойка не создана)
|
|
||||||
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
|
|
||||||
|
|
||||||
# Закрываем alert-окно с помощью кнопки закрытия
|
|
||||||
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")
|
|
||||||
|
|
||||||
def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data):
|
|
||||||
"""Выполняет один тест валидации обязательных полей.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
create_child_frame: Фрейм создания дочернего элемента
|
|
||||||
rack_maker: Объект для работы со стойкой
|
|
||||||
test_data: Словарь с данными теста
|
|
||||||
"""
|
|
||||||
# Распаковываем данные теста
|
|
||||||
name_value = test_data["name"]
|
|
||||||
height_value = test_data["height"]
|
|
||||||
depth_value = test_data["depth"]
|
|
||||||
expected_alert_height = test_data["expected_alert_height"]
|
|
||||||
expected_alert_depth = test_data["expected_alert_depth"]
|
|
||||||
|
|
||||||
# Очистить поля
|
|
||||||
create_child_frame.clear_combobox_field("Глубина (мм)")
|
|
||||||
create_child_frame.clear_combobox_field("Высота в юнитах")
|
|
||||||
|
|
||||||
# Создаем объект данных стойки
|
|
||||||
rack_data = RackData(
|
|
||||||
name=name_value,
|
|
||||||
height=height_value,
|
|
||||||
depth=depth_value
|
|
||||||
)
|
|
||||||
|
|
||||||
# Заполняем данные
|
|
||||||
rack_maker.fill_rack_data(rack_data)
|
|
||||||
|
|
||||||
# Нажимаем кнопку создания
|
|
||||||
create_child_frame.click_add_button()
|
|
||||||
create_child_frame.wait_for_timeout(3000)
|
|
||||||
|
|
||||||
# Проверяем подсветку полей
|
|
||||||
if height_value:
|
|
||||||
create_child_frame.check_field_not_highlighted_error("Высота в юнитах")
|
|
||||||
else:
|
|
||||||
create_child_frame.check_field_highlighted_error("Высота в юнитах")
|
|
||||||
|
|
||||||
if depth_value:
|
|
||||||
create_child_frame.check_field_not_highlighted_error("Глубина (мм)")
|
|
||||||
else:
|
|
||||||
create_child_frame.check_field_highlighted_error("Глубина (мм)")
|
|
||||||
|
|
||||||
# Обрабатываем alert-окна
|
|
||||||
if not height_value:
|
|
||||||
create_child_frame.alert.check_alert_presence(expected_alert_height)
|
|
||||||
create_child_frame.alert.close_alert_by_text(expected_alert_height)
|
|
||||||
|
|
||||||
if not depth_value:
|
|
||||||
create_child_frame.alert.check_alert_presence(expected_alert_depth)
|
|
||||||
create_child_frame.alert.close_alert_by_text(expected_alert_depth)
|
|
||||||
|
|
||||||
# Проверяем, что остались на той же странице
|
|
||||||
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
|
|
||||||
|
|
||||||
def test_required_fields_validation(self, browser: Page) -> None:
|
|
||||||
"""
|
|
||||||
Тест проверки обязательных полей при создании стойки.
|
|
||||||
|
|
||||||
Проверяет, что система корректно валидирует обязательные поля:
|
|
||||||
- Поле 'Высота в юнитах' должно быть заполнено
|
|
||||||
- Поле 'Глубина (мм)' должно быть заполнено
|
|
||||||
"""
|
|
||||||
# Текст сообщения alert-окна
|
|
||||||
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
|
|
||||||
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
|
|
||||||
|
|
||||||
# Нажимаем кнопку "Создать" на тулбаре
|
|
||||||
self.location_page.click_create_button()
|
|
||||||
|
|
||||||
# Создаем фрейм создания дочернего элемента
|
|
||||||
create_child_frame = CreateChildElementFrame(browser)
|
|
||||||
|
|
||||||
# Нажимаем на плашку "Класс объекта учета"
|
|
||||||
create_child_frame.open_object_class_combobox()
|
|
||||||
|
|
||||||
# Из выпадающего меню выбираем пункт "Стойка"
|
|
||||||
create_child_frame.select_object_class("Стойка")
|
|
||||||
|
|
||||||
# Открывается набор плашек для задания параметров стойки
|
|
||||||
rack_maker = RackObjectMaker(browser)
|
|
||||||
|
|
||||||
# Проверяем наличие полей стойки
|
|
||||||
rack_maker.check_rack_fields_presence()
|
|
||||||
|
|
||||||
# Тестовые данные
|
|
||||||
test_cases = [
|
|
||||||
{
|
|
||||||
"name": "Test 1: Creating rack with default field values",
|
|
||||||
"data": {
|
|
||||||
"name": "",
|
|
||||||
"height": "",
|
|
||||||
"depth": "",
|
|
||||||
"expected_alert_height": expected_alert_text_height,
|
|
||||||
"expected_alert_depth": expected_alert_text_depth
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Test 2: Required fields are not filled",
|
|
||||||
"data": {
|
|
||||||
"name": "",
|
|
||||||
"height": "",
|
|
||||||
"depth": "",
|
|
||||||
"expected_alert_height": expected_alert_text_height,
|
|
||||||
"expected_alert_depth": expected_alert_text_depth
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Test 3: Only 'Height in units' field is filled",
|
|
||||||
"data": {
|
|
||||||
"name": "",
|
|
||||||
"height": "42",
|
|
||||||
"depth": "",
|
|
||||||
"expected_alert_height": expected_alert_text_height,
|
|
||||||
"expected_alert_depth": expected_alert_text_depth
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Test 4: Only 'Depth (mm)' field is filled",
|
|
||||||
"data": {
|
|
||||||
"name": "",
|
|
||||||
"height": "",
|
|
||||||
"depth": "1000",
|
|
||||||
"expected_alert_height": expected_alert_text_height,
|
|
||||||
"expected_alert_depth": expected_alert_text_depth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Выполняем тестовые случаи
|
|
||||||
for test_case in test_cases:
|
|
||||||
logger.info(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")
|
|
||||||
|
|
||||||
# 5. Тест: Заполняем все обязательные поля
|
|
||||||
logger.info("Test 5: All required fields are filled")
|
|
||||||
|
|
||||||
# Генерируем уникальное имя для финального теста
|
|
||||||
final_rack_name = "Test-Rack-Required-Final"
|
|
||||||
|
|
||||||
# Создаем объект данных стойки
|
|
||||||
rack_data = RackData(
|
|
||||||
name=final_rack_name,
|
|
||||||
height="42",
|
|
||||||
depth="1000"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Заполняем все обязательные поля
|
|
||||||
rack_maker.fill_rack_data(rack_data)
|
|
||||||
|
|
||||||
# Проверяем, что ни одно поле не подсвечено цветом ошибки
|
|
||||||
create_child_frame.check_field_not_highlighted_error("Имя")
|
|
||||||
create_child_frame.check_field_not_highlighted_error("Высота в юнитах")
|
|
||||||
create_child_frame.check_field_not_highlighted_error("Глубина (мм)")
|
|
||||||
logger.info("No required fields are highlighted with error color - all fields filled correctly")
|
|
||||||
|
|
||||||
# Нажимаем кнопку создания
|
|
||||||
create_child_frame.click_add_button()
|
|
||||||
create_child_frame.wait_for_timeout(3000)
|
|
||||||
|
|
||||||
# Проверяем, что НЕТ 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")
|
|
||||||
|
|
||||||
# Проверяем, что ушли со страницы создания
|
|
||||||
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.info("Required fields validation test completed successfully")
|
|
||||||
|
|
||||||
def _check_rack_exists(self, browser: Page, rack_name: str) -> bool:
|
|
||||||
"""Проверяет существование стойки."""
|
|
||||||
logger.info(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("Объекты")
|
|
||||||
self.main_page.wait_for_timeout(1000)
|
|
||||||
self.main_page.click_subpanel_item("test-zone")
|
|
||||||
self.main_page.wait_for_timeout(3000)
|
|
||||||
|
|
||||||
nav_panel_locator = NavigationPanelLocators.TREEVIEW
|
|
||||||
|
|
||||||
# Проверяем видимость элемента
|
|
||||||
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")
|
|
||||||
return True
|
|
||||||
|
|
||||||
logger.info(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}'")
|
|
||||||
|
|
||||||
# Переходим обратно к созданию новой стойки
|
|
||||||
self.main_page.click_main_navigation_panel_item("test-zone")
|
|
||||||
self.main_page.wait_for_timeout(2000)
|
|
||||||
|
|
||||||
# Нажимаем кнопку "Создать" на тулбаре
|
|
||||||
self.location_page.click_create_button()
|
|
||||||
|
|
||||||
# Создаем фрейм создания дочернего элемента
|
|
||||||
create_child_frame = CreateChildElementFrame(browser)
|
|
||||||
|
|
||||||
# Нажимаем на плашку "Класс объекта учета"
|
|
||||||
create_child_frame.open_object_class_combobox()
|
|
||||||
|
|
||||||
# Из выпадающего меню выбираем пункт "Стойка"
|
|
||||||
create_child_frame.select_object_class("Стойка")
|
|
||||||
|
|
||||||
# Открывается набор плашек для задания параметров стойки
|
|
||||||
rack_maker = RackObjectMaker(browser)
|
|
||||||
|
|
||||||
# Создаем объект данных стойки
|
|
||||||
rack_data = RackData(
|
|
||||||
name=rack_name,
|
|
||||||
height="42",
|
|
||||||
depth="1000"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Заполняем данные стойки
|
|
||||||
rack_maker.fill_rack_data(rack_data)
|
|
||||||
|
|
||||||
# Нажимаем кнопку создания
|
|
||||||
create_child_frame.click_add_button()
|
|
||||||
create_child_frame.wait_for_timeout(2000)
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
|
||||||
|
|
||||||
|
Содержит тесты для проверки функциональности
|
||||||
|
работы со стойкой оборудования.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
from pages.rack_pages.rack_tab import RackTab
|
||||||
|
from pages.login_page import LoginPage
|
||||||
|
from pages.main_page import MainPage
|
||||||
|
|
||||||
|
|
||||||
|
# @pytest.mark.smoke
|
||||||
|
class TestRackTab:
|
||||||
|
"""Набор тестов для вкладки 'Стойка' в модуле Объекты.
|
||||||
|
|
||||||
|
Проверяет корректность отображения, функциональность элементов интерфейса
|
||||||
|
и переключение между вкладками стойки оборудования.
|
||||||
|
|
||||||
|
Тесты покрывают следующие функциональные области:
|
||||||
|
1. test_rack_tab_content - Базовая структура и содержимое вкладки стойки
|
||||||
|
2. test_rack_tab_switching - Функциональность переключения между вкладками стойки
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function", autouse=True)
|
||||||
|
def setup(self, browser: Page) -> None:
|
||||||
|
"""Фикстура для подготовки тестового окружения.
|
||||||
|
|
||||||
|
Выполняет:
|
||||||
|
1. Авторизацию в системе
|
||||||
|
2. Переход к стойке оборудования через панель навигации:
|
||||||
|
- Объекты → Физические устройства с опросом → Здание ЦОД 4 → Стойка КСПД
|
||||||
|
|
||||||
|
Args:
|
||||||
|
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
|
||||||
|
"""
|
||||||
|
# Авторизация в системе
|
||||||
|
lp = LoginPage(browser)
|
||||||
|
lp.do_login()
|
||||||
|
|
||||||
|
# Мы на главной странице
|
||||||
|
mp = MainPage(browser)
|
||||||
|
mp.should_be_navigation_panel()
|
||||||
|
mp.wait_for_timeout(3000)
|
||||||
|
|
||||||
|
# Переходим к Объектам
|
||||||
|
mp.click_main_navigation_panel_item("Объекты")
|
||||||
|
mp.wait_for_timeout(3000)
|
||||||
|
|
||||||
|
mp.click_subpanel_item("Физические устройства с опросом")
|
||||||
|
mp.wait_for_timeout(3000)
|
||||||
|
|
||||||
|
# Переходим Здание ЦОД 4
|
||||||
|
mp.click_subpanel_item("Здание ЦОД 4")
|
||||||
|
mp.wait_for_timeout(3000)
|
||||||
|
|
||||||
|
# Переходим к Стойка КСПД с указанием родителя
|
||||||
|
mp.click_subpanel_item("Стойка КСПД", parent="Здание ЦОД 4")
|
||||||
|
mp.wait_for_timeout(10000)
|
||||||
|
|
||||||
|
@pytest.mark.develop
|
||||||
|
def test_rack_tab_content(self, browser: Page) -> None:
|
||||||
|
"""Тест содержимого вкладки 'Стойка'.
|
||||||
|
|
||||||
|
Проверяет:
|
||||||
|
1. Наличие и корректность заголовка панели с навигационной цепочкой
|
||||||
|
2. Отображение и структуру обеих сторон стойки (лицевой и обратной)
|
||||||
|
3. Наличие и функциональность кнопок панели инструментов
|
||||||
|
4. Корректность отображения юнитов и устройств на стойке
|
||||||
|
|
||||||
|
Args:
|
||||||
|
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
|
||||||
|
"""
|
||||||
|
expected_toolbar_subtitles = [
|
||||||
|
"Мониторинг и инвентаризация",
|
||||||
|
'chevron_right',
|
||||||
|
"Физические устройства с опросом",
|
||||||
|
'chevron_right',
|
||||||
|
"Здание ЦОД 4",
|
||||||
|
'chevron_right',
|
||||||
|
"Стойка КСПД"
|
||||||
|
]
|
||||||
|
|
||||||
|
rt = RackTab(browser)
|
||||||
|
rt.should_be_header_panel(expected_toolbar_subtitles)
|
||||||
|
|
||||||
|
# Комплексная проверка отображения обеих сторон стойки с детальной информацией
|
||||||
|
rt.should_be_rack_sides_displayed()
|
||||||
|
|
||||||
|
# Переход в режим редактирования
|
||||||
|
rt.should_be_toolbar_buttons()
|
||||||
|
rt.wait_for_timeout(2000)
|
||||||
|
|
||||||
|
def test_rack_tab_switching(self, browser: Page) -> None:
|
||||||
|
"""Тест переключения между вкладками стойки оборудования.
|
||||||
|
|
||||||
|
Проверяет функциональность переключения на все доступные вкладки:
|
||||||
|
1. Общая информация
|
||||||
|
2. Обслуживание
|
||||||
|
3. События
|
||||||
|
4. Сервисы
|
||||||
|
|
||||||
|
Проверяет:
|
||||||
|
1. Наличие и доступность всех вкладок
|
||||||
|
2. Корректность активации вкладок после переключения
|
||||||
|
3. Отсутствие ошибок при последовательном переключении
|
||||||
|
|
||||||
|
Args:
|
||||||
|
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
|
||||||
|
"""
|
||||||
|
rt = RackTab(browser)
|
||||||
|
|
||||||
|
# Проверяем переключение между всеми вкладками стойки
|
||||||
|
rt.check_tab_switching()
|
||||||
|
|
@ -100,7 +100,7 @@ class TestNavigationPanel:
|
||||||
mp.wait_for_timeout(5000)
|
mp.wait_for_timeout(5000)
|
||||||
|
|
||||||
|
|
||||||
mp.click_subpanel_item("test-zone")
|
mp.click_subpanel_item("Физические устройства с опросом")
|
||||||
mp.wait_for_timeout(3000)
|
mp.wait_for_timeout(3000)
|
||||||
|
|
||||||
# Переходим Здание ЦОД 4
|
# Переходим Здание ЦОД 4
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# Auto-generated by fix_python_project.py
|
|
||||||
"""Package initialization."""
|
|
||||||
Loading…
Reference in New Issue