Compare commits

...

3 Commits

Author SHA1 Message Date
Radislav 3680e42c86 Изменения после проверки pylint 2026-01-19 08:24:45 +03:00
Radislav a25db67097 Добавлена фикстура для автоматической очистки тестовых стоек
- В класс TestCreateRackElement добавлена фикстура rack_cleanup_fixture
- Фикстура автоматически удаляет статические тестовые стойки после каждого теста
- Список удаляемых стоек: Test-Rack-01, Test-Rack-Duplicate, Test-Rack-Required-Final, Test-Rack-Delete
- Используются существующие методы _check_rack_existance и _delete_rack_from_context_menu без изменений
- Фикстура применяется автоматически ко всем тестам (autouse=True)
- Обеспечивает чистое тестовое окружение после выполнения тестов
2026-01-16 13:39:50 +03:00
Radislav 8140769583 Разработка ттеста Стойка 2026-01-16 08:21:29 +03:00
7 changed files with 738 additions and 441 deletions

View File

@ -35,114 +35,62 @@ class RackObjectMaker(BaseComponent):
Инициализирует компонент создания стойки. Инициализирует компонент создания стойки.
Args: Args:
page: Экземпляр страницы Playwright page (Page): Экземпляр страницы Playwright
""" """
super().__init__(page) super().__init__(page)
# Действия: # Действия:
def fill_rack_data(self, rack_data: RackData) -> None: def _fill_combobox_field(self, field_name: str, value: str, fields_locators: dict) -> None:
""" """
Заполняет данные для создания стойки. Заполняет combobox поле.
Args: Args:
rack_data: Данные стойки field_name (str): Название поля
value (str): Значение для установки
fields_locators (dict): Словарь с найденными полями формы
Raises:
ValueError: Если поле не найдено в форме
""" """
logger.debug(f"Filling rack data: {rack_data.name}") # Получаем контейнер поля по его названию
self._fill_text_fields(rack_data)
self._fill_combobox_fields(rack_data)
logger.debug("Rack data filled successfully")
def _get_form_fields(self) -> dict:
"""
Получает все поля формы стойки.
Returns:
dict: Словарь {название поля: Locator контейнера поля}
"""
# Получаем контейнер формы (второй элемент)
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
if container_locator.count() == 0:
logger.error("Form container not found")
raise ValueError("Form container not found")
return self.get_input_fields_locators(container_locator)
def _fill_text_fields(self, rack_data: RackData) -> None:
"""Заполняет текстовые поля."""
logger.debug("Filling text fields...")
# Получаем все поля формы
fields_locators = self._get_form_fields()
logger.debug(f"Available text fields: {list(fields_locators.keys())}")
def clear_and_fill(field_name: str, value: str):
"""Очищает поле и заполняет его значением."""
if not value:
logger.debug(f"Skipping empty value for field '{field_name}'")
return
# Получаем контейнер поля
field_container = fields_locators.get(field_name) field_container = fields_locators.get(field_name)
if not field_container: if not field_container:
logger.warning(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}") logger.error(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}")
return raise ValueError(f"Field '{field_name}' not found in form")
# Находим input внутри контейнера logger.debug(f"Filling field '{field_name}' with value '{value}'...")
input_field = field_container.locator("input").first
if input_field.count() == 0: # Прокручиваем до поля
logger.warning(f"Input element not found in container for field '{field_name}'") field_container.scroll_into_view_if_needed()
return self.wait_for_timeout(300)
# Проверяем видимость # Проверяем видимость поля
if not input_field.is_visible(): self.check_visibility(field_container, f"Field '{field_name}' not found")
logger.debug(f"Field '{field_name}' is not visible, scrolling into view...")
input_field.scroll_into_view_if_needed()
self.wait_for_timeout(500)
# Проверяем, не disabled ли поле # Находим кнопку открытия выпадающего списка внутри контейнера поля
is_disabled = input_field.get_attribute("disabled") open_button = field_container.locator(".v-input__append-inner").first
is_readonly = input_field.get_attribute("readonly")
if is_disabled or is_readonly: # Кликаем для открытия выпадающего списка
logger.warning(f"Field '{field_name}' is disabled or readonly") open_button.click(force=True)
return self.wait_for_timeout(300)
# Очищаем поле # Вводим значение из выпадающего списка
input_field.click() dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value)
input_field.press("Control+A") element = self.page.locator(dropdown_item_locator).first
input_field.press("Backspace")
# Заполняем значение # Скроллим к элементу если нужно
input_field.fill(value) self._scroll_until_element(
logger.debug(f"Filled '{field_name}': {value}") self.page.locator(RackLocators.DROPDOWN_LIST).first,
value
)
self.wait_for_timeout(300)
element.click()
# Обязательные поля logger.debug(f"Field '{field_name}' filled successfully")
if rack_data.name:
clear_and_fill("Имя", rack_data.name)
# Опциональные поля
if rack_data.serial:
clear_and_fill("Серийный номер", rack_data.serial)
if rack_data.inventory:
clear_and_fill("Инвентарный номер", rack_data.inventory)
if rack_data.comment:
clear_and_fill("Комментарий", rack_data.comment)
logger.debug("Text fields filled successfully")
def _fill_combobox_fields(self, rack_data: RackData) -> None: def _fill_combobox_fields(self, rack_data: RackData) -> None:
"""Заполняет combobox поля.""" """Заполняет combobox поля."""
@ -180,59 +128,103 @@ class RackObjectMaker(BaseComponent):
self._fill_combobox_field("Проект/Титул", rack_data.project, fields_locators) self._fill_combobox_field("Проект/Титул", rack_data.project, fields_locators)
logger.debug(f"Selected project/title: {rack_data.project}") logger.debug(f"Selected project/title: {rack_data.project}")
def _fill_combobox_field(self, field_name: str, value: str, fields_locators: dict) -> None: def _fill_text_fields(self, rack_data: RackData) -> None:
""" """Заполняет текстовые поля."""
Заполняет combobox поле.
Args: logger.debug("Filling text fields...")
field_name: Название поля
value: Значение для установки
fields_locators: Словарь с найденными полями формы
"""
# Получаем контейнер поля по его названию # Получаем все поля формы
fields_locators = self._get_form_fields()
logger.debug(f"Available text fields: {list(fields_locators.keys())}")
def clear_and_fill(field_name: str, value: str):
"""Очищает поле и заполняет его значением."""
if not value:
logger.debug(f"Skipping empty value for field '{field_name}'")
return
# Получаем контейнер поля
field_container = fields_locators.get(field_name) field_container = fields_locators.get(field_name)
if not field_container: if not field_container:
logger.error(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}") logger.warning(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}")
raise ValueError(f"Field '{field_name}' not found in form") return
logger.debug(f"Filling field '{field_name}' with value '{value}'...") # Находим input внутри контейнера
input_field = field_container.locator("input").first
# Прокручиваем до поля if input_field.count() == 0:
field_container.scroll_into_view_if_needed() logger.warning(f"Input element not found in container for field '{field_name}'")
self.wait_for_timeout(500) return
# Проверяем видимость поля # Проверяем видимость
self.check_visibility(field_container, f"Field '{field_name}' not found") if not input_field.is_visible():
logger.debug(f"Field '{field_name}' is not visible, scrolling into view...")
input_field.scroll_into_view_if_needed()
self.wait_for_timeout(300)
# Находим кнопку открытия выпадающего списка внутри контейнера поля # Проверяем, не disabled ли поле
open_button = field_container.locator(".v-input__append-inner").first is_disabled = input_field.get_attribute("disabled")
is_readonly = input_field.get_attribute("readonly")
# Кликаем для открытия выпадающего списка if is_disabled or is_readonly:
open_button.click(force=True) logger.warning(f"Field '{field_name}' is disabled or readonly")
self.wait_for_timeout(1000) return
# Вводим значение из выпадающего списка # Очищаем поле
dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value) input_field.click()
element = self.page.locator(dropdown_item_locator).first input_field.press("Control+A")
input_field.press("Backspace")
# Скроллим к элементу если нужно # Заполняем значение
self._scroll_until_element( input_field.fill(value)
self.page.locator(RackLocators.DROPDOWN_LIST).first, logger.debug(f"Filled '{field_name}': {value}")
value
)
self.wait_for_timeout(500)
element.click()
logger.debug(f"Field '{field_name}' filled successfully") # Обязательные поля
if rack_data.name:
clear_and_fill("Имя", rack_data.name)
# Опциональные поля
if rack_data.serial:
clear_and_fill("Серийный номер", rack_data.serial)
if rack_data.inventory:
clear_and_fill("Инвентарный номер", rack_data.inventory)
if rack_data.comment:
clear_and_fill("Комментарий", rack_data.comment)
logger.debug("Text fields filled successfully")
def _get_form_fields(self) -> dict:
"""
Получает все поля формы стойки.
Returns:
dict: Словарь {название поля: Locator контейнера поля}
Raises:
ValueError: Если контейнер формы не найден
"""
# Получаем контейнер формы (второй элемент)
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
if container_locator.count() == 0:
logger.error("Form container not found")
raise ValueError("Form container not found")
return self.get_input_fields_locators(container_locator)
def _scroll_until_element(self, locator: Locator, name: str) -> None: def _scroll_until_element(self, locator: Locator, name: str) -> None:
""" """
Скроллит список до тех пор, пока не перестанут подгружаться новые элементы. Скроллит список до тех пор, пока не перестанут подгружаться новые элементы.
Args: Args:
locator: Локатор элементов или строка с CSS/XPath. locator (Locator): Локатор элементов или строка с CSS/XPath
name (str): Имя элемента для поиска
""" """
loc = self.get_locator(locator) loc = self.get_locator(locator)
@ -266,6 +258,21 @@ class RackObjectMaker(BaseComponent):
self.wait_for_timeout(300) self.wait_for_timeout(300)
def fill_rack_data(self, rack_data: RackData) -> None:
"""
Заполняет данные для создания стойки.
Args:
rack_data (RackData): Данные стойки
"""
logger.debug(f"Filling rack data: {rack_data.name}")
self._fill_text_fields(rack_data)
self._fill_combobox_fields(rack_data)
logger.debug("Rack data filled successfully")
# Проверки: # Проверки:
def check_rack_fields_presence(self) -> None: def check_rack_fields_presence(self) -> None:

View File

@ -1,7 +1,7 @@
"""Модуль фрейма создания дочернего элемента.""" """Модуль фрейма создания дочернего элемента."""
import re import re
from playwright.sync_api import expect, Page, Locator from playwright.sync_api import Page, Locator
from tools.logger import get_logger from tools.logger import get_logger
from locators.rack_locators import RackLocators from locators.rack_locators import RackLocators
from locators.selection_bar_locators import SelectionBarLocators from locators.selection_bar_locators import SelectionBarLocators
@ -23,7 +23,7 @@ class CreateChildElementFrame(BaseComponent):
Инициализирует фрейм создания дочернего элемента. Инициализирует фрейм создания дочернего элемента.
Args: Args:
page: Экземпляр страницы Playwright page (Page): Экземпляр страницы Playwright
""" """
super().__init__(page) super().__init__(page)
@ -54,7 +54,7 @@ class CreateChildElementFrame(BaseComponent):
Очищает combobox поле по его названию. Очищает combobox поле по его названию.
Args: Args:
field_name: Название поля для очистки field_name (str): Название поля для очистки
""" """
logger.debug(f"Clearing combobox field '{field_name}'...") logger.debug(f"Clearing combobox field '{field_name}'...")
@ -72,7 +72,7 @@ class CreateChildElementFrame(BaseComponent):
# Прокручиваем до поля # Прокручиваем до поля
field_container.scroll_into_view_if_needed() field_container.scroll_into_view_if_needed()
self.wait_for_timeout(500) self.wait_for_timeout(300)
# Проверяем видимость # Проверяем видимость
if not field_container.is_visible(): if not field_container.is_visible():
@ -88,7 +88,7 @@ class CreateChildElementFrame(BaseComponent):
# Если кнопка закрытия видима - кликаем на нее # Если кнопка закрытия видима - кликаем на нее
close_button.click(force=True) close_button.click(force=True)
self.wait_for_timeout(500) self.wait_for_timeout(300)
logger.debug(f"Combobox field '{field_name}' cleared using close button") logger.debug(f"Combobox field '{field_name}' cleared using close button")
else: else:
logger.debug(f"Close button (i.mdi-close) not found for field '{field_name}'") logger.debug(f"Close button (i.mdi-close) not found for field '{field_name}'")
@ -115,6 +115,61 @@ class CreateChildElementFrame(BaseComponent):
return self.selection_bar.get_selection_bar_title() return self.selection_bar.get_selection_bar_title()
def is_field_filled(self, field_name: str, container_locator: Locator = None) -> bool:
"""
Проверяет, заполнено ли combobox или текстовое поле.
Args:
field_name (str): Название поля для проверки
container_locator (Locator, optional): Локатор контейнера формы
Returns:
bool: True если поле заполнено, False в противном случае
"""
logger.debug(f"Checking if field '{field_name}' is filled...")
# Если контейнер не передан, используем контейнер по умолчанию
if container_locator is None:
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
# Получаем словарь всех полей формы
fields_locators = self.get_input_fields_locators(container_locator)
if field_name not in fields_locators:
logger.debug(f"Field '{field_name}' not found in fields_locators")
return False
# Получаем контейнер поля
field_container = fields_locators[field_name]
if not field_container.is_visible():
logger.debug(f"Field '{field_name}' not visible")
return False
# Проверяем наличие выбранного значения через v-chip (чип выбранного значения в combobox)
selected_chip = field_container.locator(".v-chip").first
# Проверяем наличие текста в поле
field_text = field_container.text_content() or ""
has_text = bool(field_text.strip())
# Проверяем наличие чипа
has_chip = selected_chip.count() > 0 and selected_chip.is_visible()
# Для текстовых полей проверяем значение input
if not has_chip:
input_field = field_container.locator("input").first
if input_field.count() > 0:
input_value = input_field.input_value() or ""
has_input_value = bool(input_value.strip())
logger.debug(f"Field '{field_name}' - has input value: {has_input_value}")
has_text = has_text or has_input_value
logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}")
return has_chip or has_text
def open_object_class_combobox(self) -> None: def open_object_class_combobox(self) -> None:
"""Открывает выпадающий список combobox.""" """Открывает выпадающий список combobox."""
@ -138,7 +193,12 @@ class CreateChildElementFrame(BaseComponent):
logger.debug("Combobox menu is already open") logger.debug("Combobox menu is already open")
def select_object_class(self, class_name: str) -> None: def select_object_class(self, class_name: str) -> None:
"""Выбирает класс объекта из выпадающего списка.""" """
Выбирает класс объекта из выпадающего списка.
Args:
class_name (str): Название класса объекта для выбора
"""
logger.debug(f"Selecting object class: '{class_name}'...") logger.debug(f"Selecting object class: '{class_name}'...")
@ -149,7 +209,7 @@ class CreateChildElementFrame(BaseComponent):
self.selection_bar.select_value(class_name) self.selection_bar.select_value(class_name)
# Даем время на применение выбора # Даем время на применение выбора
self.wait_for_timeout(3000) self.wait_for_timeout(300)
logger.debug(f"Object class '{class_name}' successfully selected") logger.debug(f"Object class '{class_name}' successfully selected")
@ -160,7 +220,11 @@ class CreateChildElementFrame(BaseComponent):
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
Args: Args:
field_name: Название поля для проверки field_name (str): Название поля для проверки
Raises:
ValueError: Если поле не найдено в форме
AssertionError: Если поле не подсвечено ошибкой
""" """
logger.debug(f"Checking field '{field_name}' for error highlighting...") logger.debug(f"Checking field '{field_name}' for error highlighting...")
@ -193,7 +257,11 @@ class CreateChildElementFrame(BaseComponent):
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args: Args:
field_name: Название поля для проверки field_name (str): Название поля для проверки
Raises:
ValueError: Если поле не найдено в форме
AssertionError: Если поле подсвечено ошибкой
""" """
logger.debug(f"Checking field '{field_name}' for absence of error highlighting...") logger.debug(f"Checking field '{field_name}' for absence of error highlighting...")
@ -226,12 +294,15 @@ class CreateChildElementFrame(BaseComponent):
Проверяет что выбран указанный класс объекта. Проверяет что выбран указанный класс объекта.
Args: Args:
expected_class: Ожидаемый выбранный класс объекта expected_class (str): Ожидаемый выбранный класс объекта
Raises:
AssertionError: Если выбранный класс не соответствует ожидаемому
""" """
logger.debug(f"Checking selected object class: '{expected_class}'...") logger.debug(f"Checking selected object class: '{expected_class}'...")
self.wait_for_timeout(1000) self.wait_for_timeout(500)
actual_class = self.get_selected_object_class() actual_class = self.get_selected_object_class()
is_match = (expected_class.lower() in actual_class.lower() or is_match = (expected_class.lower() in actual_class.lower() or
@ -252,7 +323,10 @@ class CreateChildElementFrame(BaseComponent):
Проверяет заголовок тулбара. Проверяет заголовок тулбара.
Args: Args:
expected_title: Ожидаемый заголовок тулбара expected_title (str): Ожидаемый заголовок тулбара
Raises:
AssertionError: Если заголовок не соответствует ожидаемому
""" """
logger.debug(f"Checking toolbar title: '{expected_title}'...") logger.debug(f"Checking toolbar title: '{expected_title}'...")
@ -274,11 +348,9 @@ class CreateChildElementFrame(BaseComponent):
Проверяет наличие и функциональность кнопок тулбара. Проверяет наличие и функциональность кнопок тулбара.
""" """
self.wait_for_timeout(2000)
self.toolbar.check_button_visibility("add") self.toolbar.check_button_visibility("add")
self.toolbar.check_button_tooltip("add", "Добавить") self.toolbar.check_button_tooltip("add", "Добавить")
self.toolbar.check_button_visibility("cancel") self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("cancel", "Отменить") self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.click_button("cancel") self.toolbar.click_button("cancel")
self.wait_for_timeout(2000) self.wait_for_timeout(500)

View File

@ -20,8 +20,8 @@ class Environment:
DEVELOP: str = 'develop' DEVELOP: str = 'develop'
URLS: Dict[str, str] = { URLS: Dict[str, str] = {
TEST: 'http://192.168.2.76/', TEST: 'https://192.168.2.76/',
DEVELOP: 'http://192.168.2.69/' DEVELOP: 'https://192.168.2.69/'
} }
def __init__(self) -> None: def __init__(self) -> None:

View File

@ -164,6 +164,7 @@ def get_context(browser: Browser, request: FixtureRequest, start: str) -> Browse
context = browser.new_context( context = browser.new_context(
# no_viewport=True, # no_viewport=True,
ignore_https_errors=True,
viewport= ast.literal_eval(request.config.getoption('--s')), viewport= ast.literal_eval(request.config.getoption('--s')),
locale=request.config.getoption('l') locale=request.config.getoption('l')
) )

View File

@ -113,3 +113,16 @@ class RackLocators:
# Кнопка "Показать стойку" # Кнопка "Показать стойку"
SHOW_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and " SHOW_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and "
"contains(@class, 'cabinet_hide_button_trigger_hide')]") "contains(@class, 'cabinet_hide_button_trigger_hide')]")
# Кнопки тулбара стойки
TOOLBAR_REPLACE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__replace']"
TOOLBAR_DONE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__done']"
TOOLBAR_CLOSE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__close']"
TOOLBAR_REMOVE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__remove']"
# Диалог удаления
REMOVE_DIALOG = "[data-testid='cabinet-bar__toolbar__dialog-remove']"
# Кнопки подтверждения удаления
CONFIRM_REMOVE_YES_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__yes']"
CONFIRM_REMOVE_NO_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__no']"

View File

@ -31,7 +31,7 @@ class RackPage(BasePage):
Инициализирует объект вкладки стойки. Инициализирует объект вкладки стойки.
Args: Args:
page: Экземпляр страницы Playwright page (Page): Экземпляр страницы Playwright
""" """
super().__init__(page) super().__init__(page)
@ -48,13 +48,103 @@ class RackPage(BasePage):
show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON) show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON)
self.show_button = TooltipButton(page, show_button_locator, "show_rack") self.show_button = TooltipButton(page, show_button_locator, "show_rack")
# Кнопка "Переместить"
replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON)
self.replace_button = TooltipButton(page, replace_button_locator, "replace")
# Кнопка "Сохранить"
done_button_locator = self.page.locator(RackLocators.TOOLBAR_DONE_BUTTON)
self.done_button = TooltipButton(page, done_button_locator, "done")
# Кнопка "Отменить"
close_button_locator = self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON)
self.close_button = TooltipButton(page, close_button_locator, "close")
# Кнопка "Удалить"
remove_button_locator = self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON)
self.remove_button = TooltipButton(page, remove_button_locator, "remove")
self.toolbar = ToolbarComponent(page, "") self.toolbar = ToolbarComponent(page, "")
self.toolbar.add_tooltip_button(locator_button, "edit") self.toolbar.add_tooltip_button(locator_button, "edit")
self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack") self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack")
self.toolbar.add_tooltip_button(show_button_locator, "show_rack") self.toolbar.add_tooltip_button(show_button_locator, "show_rack")
self.toolbar.add_tooltip_button(replace_button_locator, "replace")
self.toolbar.add_tooltip_button(done_button_locator, "done")
self.toolbar.add_tooltip_button(close_button_locator, "close")
self.toolbar.add_tooltip_button(remove_button_locator, "remove")
# Действия # Действия
def click_remove_button(self) -> None:
"""
Кликает на кнопку 'Удалить' и обрабатывает диалог подтверждения.
"""
logger.debug("Clicking on 'Remove' button...")
# Проверяем видимость кнопки
self.toolbar.check_button_visibility("remove")
# Проверяем тултип кнопки (может быть "Удалить" или "Remove")
try:
self.toolbar.check_button_tooltip("remove", "Удалить")
except AssertionError:
try:
self.toolbar.check_button_tooltip("remove", "Remove")
except AssertionError:
logger.debug("Could not verify tooltip text for remove button")
# Кликаем на кнопку удаления
self.toolbar.get_button_by_name("remove").click()
self.wait_for_timeout(1000)
# Ожидаем появления диалога подтверждения
self._handle_remove_confirmation_dialog()
def confirm_remove_dialog(self, confirm: bool = True) -> None:
"""
Подтверждает или отклоняет удаление в диалоговом окне.
Args:
confirm (bool): Если True - подтвердить удаление, если False - отменить
"""
logger.debug(f"Confirming remove dialog with: {'Да' if confirm else 'Нет'}")
# Ждем немного перед поиском диалога
self.wait_for_timeout(1500)
# Ищем активный диалог
dialog = self.page.locator("div.v-dialog--active")
# Проверяем, что диалог найден и содержит нужный текст
assert dialog.count() > 0, "No active dialog found"
# Проверяем текст диалога
dialog_text = dialog.first.text_content()
logger.debug("Dialog text: %s", dialog_text)
# Должен содержать "Запрос подтверждения" и "Удалить"
assert "Запрос подтверждения" in dialog_text, "Not a confirmation dialog"
# Ищем кнопку по data-testid
if confirm:
button = self.page.locator(RackLocators.CONFIRM_REMOVE_YES_BUTTON)
else:
button = self.page.locator(RackLocators.CONFIRM_REMOVE_NO_BUTTON)
# Проверяем, что кнопка найдена
assert button.count() > 0, "Button not found with selector"
# Кликаем на кнопку
button_text = button.first.text_content()
logger.debug("Clicking button with text: %s", button_text)
button.first.click()
self.wait_for_timeout(2000)
# Проверяем, что диалог закрылся
self.wait_for_timeout(1000)
logger.debug("Remove confirmation completed")
def get_available_tabs(self) -> list[str]: def get_available_tabs(self) -> list[str]:
""" """
Возвращает список доступных вкладок. Возвращает список доступных вкладок.
@ -72,7 +162,7 @@ class RackPage(BasePage):
tab_elements.first.wait_for(state="visible", timeout=5000) tab_elements.first.wait_for(state="visible", timeout=5000)
total_count = tab_elements.count() total_count = tab_elements.count()
logger.debug(f"Total top tab elements found: {total_count}") logger.debug("Total top tab elements found: %d", total_count)
for i in range(total_count): for i in range(total_count):
element = tab_elements.nth(i) element = tab_elements.nth(i)
@ -84,9 +174,9 @@ class RackPage(BasePage):
tab_text = tab_text.strip() tab_text = tab_text.strip()
if tab_text and tab_text not in tabs: if tab_text and tab_text not in tabs:
tabs.append(tab_text) tabs.append(tab_text)
logger.debug(f"Top tab found: '{tab_text}'") logger.debug("Top tab found: '%s'", tab_text)
logger.debug(f"Available top tabs found: {tabs}") logger.debug("Available top tabs found: %s", tabs)
return tabs return tabs
def get_current_active_side(self) -> Optional[str]: def get_current_active_side(self) -> Optional[str]:
@ -158,13 +248,13 @@ class RackPage(BasePage):
Переключается на указанную вкладку. Переключается на указанную вкладку.
Args: Args:
tab_name: Название вкладки для переключения tab_name (str): Название вкладки для переключения
Raises: Raises:
AssertionError: Если вкладка не найдена или недоступна AssertionError: Если вкладка не найдена или недоступна
""" """
logger.debug(f"Switching to tab '{tab_name}'...") logger.debug("Switching to tab '%s'...", tab_name)
tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name))
@ -172,7 +262,7 @@ class RackPage(BasePage):
# Проверяем активность ДО клика # Проверяем активность ДО клика
if self.is_tab_active(tab_name): if self.is_tab_active(tab_name):
logger.debug(f"Tab '{tab_name}' is already active") logger.debug("Tab '%s' is already active", tab_name)
return return
# Находим первую видимую вкладку с нужным именем # Находим первую видимую вкладку с нужным именем
@ -186,7 +276,7 @@ class RackPage(BasePage):
assert target_tab is not None, f"No visible/available tab '{tab_name}' found" assert target_tab is not None, f"No visible/available tab '{tab_name}' found"
# Кликаем на вкладку # Кликаем на вкладку
logger.debug(f"Clicking on tab '{tab_name}'...") logger.debug("Clicking on tab '%s'...", tab_name)
target_tab.click() target_tab.click()
# Ждем изменения активной вкладки # Ждем изменения активной вкладки
@ -220,21 +310,21 @@ class RackPage(BasePage):
if main_container.count() == 0: if main_container.count() == 0:
logger.warning("Main rack container not found") logger.warning("Main rack container not found")
else: else:
logger.debug(f"Main rack container found (count: {main_container.count()})") logger.debug("Main rack container found (count: %d)", main_container.count())
expect(main_container.first).to_be_attached() expect(main_container.first).to_be_attached()
# Проверяем наличие позиций юнитов # Проверяем наличие позиций юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
if unit_positions.count() > 0: if unit_positions.count() > 0:
logger.debug(f"Unit positions found: {unit_positions.count()}") logger.debug("Unit positions found: %d", unit_positions.count())
if unit_positions.first.text_content(): if unit_positions.first.text_content():
content = unit_positions.first.text_content().strip() content = unit_positions.first.text_content().strip()
logger.debug(f"First position: {content}") logger.debug("First position: %s", content)
# Проверяем наличие кнопок добавления # Проверяем наличие кнопок добавления
open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON) open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON)
if open_buttons.count() > 0: if open_buttons.count() > 0:
logger.debug(f"'add_circle' buttons found: {open_buttons.count()}") logger.debug("'add_circle' buttons found: %d", open_buttons.count())
logger.debug("Rack interface loaded") logger.debug("Rack interface loaded")
@ -254,8 +344,8 @@ class RackPage(BasePage):
device_title = first_device.get_attribute("title") or "No title" device_title = first_device.get_attribute("title") or "No title"
logger.debug( logger.debug(
f"Devices found: {device_count} " "Devices found: %d (first: ID=%s, Title=%s)",
f"(first: ID={device_id}, Title={device_title})" device_count, device_id, device_title
) )
else: else:
logger.debug("No devices detected") logger.debug("No devices detected")
@ -263,6 +353,9 @@ class RackPage(BasePage):
def check_tab_switching(self) -> None: def check_tab_switching(self) -> None:
""" """
Проверяет переключение между вкладками стойки. Проверяет переключение между вкладками стойки.
Raises:
AssertionError: Если не удалось переключиться на все вкладки
""" """
logger.debug("Testing rack tab switching functionality...") logger.debug("Testing rack tab switching functionality...")
@ -276,14 +369,14 @@ class RackPage(BasePage):
"Сервисы" "Сервисы"
] ]
logger.debug(f"Defined tabs to test: {defined_tabs}") logger.debug("Defined tabs to test: %s", defined_tabs)
successful_switches = 0 successful_switches = 0
failed_switches = [] failed_switches = []
# Тестируем переключение на каждую определенную вкладку # Тестируем переключение на каждую определенную вкладку
for tab_name in defined_tabs: for tab_name in defined_tabs:
logger.debug(f"Testing switch to tab '{tab_name}'...") logger.debug("Testing switch to tab '%s'...", tab_name)
try: try:
# Переключаемся на вкладку # Переключаемся на вкладку
@ -291,15 +384,15 @@ class RackPage(BasePage):
# Проверяем, что вкладка активна # Проверяем, что вкладка активна
if self.is_tab_active(tab_name): if self.is_tab_active(tab_name):
logger.debug(f"Successfully switched to tab '{tab_name}'") logger.debug("Successfully switched to tab '%s'", tab_name)
successful_switches += 1 successful_switches += 1
else: else:
logger.warning(f"Tab '{tab_name}' not active after switching") logger.warning("Tab '%s' not active after switching", tab_name)
failed_switches.append(f"Tab '{tab_name}' is not active after click") failed_switches.append(f"Tab '{tab_name}' is not active after click")
except (AssertionError, TimeoutError) as e: except (AssertionError, TimeoutError) as e:
# Ловим только конкретные исключения, которые могут возникнуть при переключении вкладок # Ловим только конкретные исключения, которые могут возникнуть при переключении вкладок
logger.error(f"Error switching to tab '{tab_name}': {e}") logger.error("Error switching to tab '%s': %s", tab_name, e)
failed_switches.append(f"Tab '{tab_name}' error: {str(e)}") failed_switches.append(f"Tab '{tab_name}' error: {str(e)}")
# Небольшая пауза между переключениями # Небольшая пауза между переключениями
@ -307,12 +400,12 @@ class RackPage(BasePage):
# Формируем итоговый отчет # Формируем итоговый отчет
logger.debug("=== TAB SWITCHING RESULTS ===") logger.debug("=== TAB SWITCHING RESULTS ===")
logger.debug(f"Successful switches: {successful_switches}/{len(defined_tabs)}") logger.debug("Successful switches: %d/%d", successful_switches, len(defined_tabs))
if failed_switches: if failed_switches:
logger.debug("Failed switches:") logger.debug("Failed switches:")
for failure in failed_switches: for failure in failed_switches:
logger.debug(f" - {failure}") logger.debug(" - %s", failure)
# Требуем успешного переключения на все определенные вкладки # Требуем успешного переключения на все определенные вкладки
assert successful_switches == len(defined_tabs), ( assert successful_switches == len(defined_tabs), (
@ -322,14 +415,14 @@ class RackPage(BasePage):
f"Errors: {', '.join(failed_switches)}" f"Errors: {', '.join(failed_switches)}"
) )
logger.debug(f"All {successful_switches} defined tabs successfully switched!") logger.debug("All %d defined tabs successfully switched!", successful_switches)
def is_tab_active(self, tab_name: str) -> bool: def is_tab_active(self, tab_name: str) -> bool:
""" """
Проверяет, активна ли указанная вкладка. Проверяет, активна ли указанная вкладка.
Args: Args:
tab_name: Название вкладки для проверки tab_name (str): Название вкладки для проверки
Returns: Returns:
bool: True если вкладка активна, False в противном случае bool: True если вкладка активна, False в противном случае
@ -341,10 +434,10 @@ class RackPage(BasePage):
if active_tab.count() > 0 and active_tab.first.is_visible(): if active_tab.count() > 0 and active_tab.first.is_visible():
active_text = active_tab.first.text_content() active_text = active_tab.first.text_content()
if active_text and active_text.strip() == tab_name: if active_text and active_text.strip() == tab_name:
logger.debug(f"Tab '{tab_name}' is active (via active tab class)") logger.debug("Tab '%s' is active (via active tab class)", tab_name)
return True return True
logger.debug(f"Tab '{tab_name}' is not active") logger.debug("Tab '%s' is not active", tab_name)
return False return False
def should_be_panel_header(self, expected_toolbar_title_items: list[str]) -> None: def should_be_panel_header(self, expected_toolbar_title_items: list[str]) -> None:
@ -352,7 +445,7 @@ class RackPage(BasePage):
Проверяет наличие и корректность заголовка панели. Проверяет наличие и корректность заголовка панели.
Args: Args:
expected_toolbar_title_items: Ожидаемые элементы заголовка expected_toolbar_title_items (list[str]): Ожидаемые элементы заголовка
Raises: Raises:
AssertionError: Если заголовок панели не соответствует ожиданиям AssertionError: Если заголовок панели не соответствует ожиданиям
@ -374,7 +467,12 @@ class RackPage(BasePage):
) )
def should_be_rack_sides_displayed(self) -> None: def should_be_rack_sides_displayed(self) -> None:
"""Проверка отображения и структуры сторон стойки.""" """
Проверка отображения и структуры сторон стойки.
Raises:
AssertionError: Если стороны стойки не отображаются корректно
"""
logger.debug("Checking rack sides display and structure...") logger.debug("Checking rack sides display and structure...")
@ -396,7 +494,7 @@ class RackPage(BasePage):
# Проверяем, какая сторона активна по умолчанию # Проверяем, какая сторона активна по умолчанию
current_active = self.get_current_active_side() current_active = self.get_current_active_side()
logger.debug(f"Current active side: {current_active}") logger.debug("Current active side: %s", current_active)
# Дополнительная проверка устройств # Дополнительная проверка устройств
self.check_physical_devices_presence() self.check_physical_devices_presence()
@ -409,7 +507,7 @@ class RackPage(BasePage):
# Возвращаемся на исходную активную сторону # Возвращаемся на исходную активную сторону
if current_active: if current_active:
logger.debug(f"Returning to original active side: {current_active}") logger.debug("Returning to original active side: %s", current_active)
if current_active == "Лицевая сторона": if current_active == "Лицевая сторона":
front_side_button.click() front_side_button.click()
else: else:
@ -417,7 +515,7 @@ class RackPage(BasePage):
self.wait_for_timeout(1000) self.wait_for_timeout(1000)
final_active = self.get_current_active_side() final_active = self.get_current_active_side()
logger.debug(f"Final active side: {final_active}") logger.debug("Final active side: %s", final_active)
logger.debug("All rack sides checks passed successfully") logger.debug("All rack sides checks passed successfully")
@ -426,19 +524,37 @@ class RackPage(BasePage):
Проверяет наличие и функциональность кнопок тулбара. Проверяет наличие и функциональность кнопок тулбара.
Raises: Raises:
AssertionError: Если кнопки недоступны или подсказки неверны. AssertionError: Если кнопки недоступны или подсказки неверны
""" """
logger.debug("Checking toolbar buttons...") logger.debug("Checking toolbar buttons...")
# Проверяем основные кнопки
self.toolbar.check_button_visibility("edit") self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Изменить") self.toolbar.check_button_tooltip("edit", "Изменить")
# Кликаем на кнопку "Изменить" для проверки функциональности
self.toolbar.get_button_by_name("edit").click() self.toolbar.get_button_by_name("edit").click()
# Проверяем новые кнопки тулбара
self.toolbar.check_button_visibility("replace")
self.toolbar.check_button_tooltip("replace", "Переместить")
self.toolbar.check_button_visibility("done")
self.toolbar.check_button_tooltip("done", "Сохранить")
self.toolbar.check_button_visibility("close")
self.toolbar.check_button_tooltip("close", "Отменить")
self.toolbar.check_button_visibility("remove")
self.toolbar.check_button_tooltip("remove", "Удалить")
def should_have_hide_rack_button(self) -> None: def should_have_hide_rack_button(self) -> None:
""" """
Проверка кнопки "Скрыть стойку". Проверка кнопки "Скрыть стойку".
Проверяет видимость, тултип, кликабельность и эффект скрытия стойки.
Raises:
AssertionError: Если кнопка не отображается или не работает
""" """
logger.debug("Checking 'Hide rack' button...") logger.debug("Checking 'Hide rack' button...")
@ -465,7 +581,9 @@ class RackPage(BasePage):
def should_have_show_rack_button(self) -> None: def should_have_show_rack_button(self) -> None:
""" """
Проверка кнопки "Показать стойку". Проверка кнопки "Показать стойку".
Проверяет наличие, тултип, кликабельность и эффект показа стойки.
Raises:
AssertionError: Если кнопка не отображается или не работает
""" """
logger.debug("Checking 'Show rack' button...") logger.debug("Checking 'Show rack' button...")
@ -496,56 +614,56 @@ class RackPage(BasePage):
Проверка структуры конкретной стороны стойки. Проверка структуры конкретной стороны стойки.
Args: Args:
side_name: Название стороны для логов side_name (str): Название стороны для логов
side_button: Локатор кнопки стороны side_button: Локатор кнопки стороны
Raises: Raises:
AssertionError: Если структура стороны некорректна AssertionError: Если структура стороны некорректна
""" """
logger.debug(f"Checking {side_name}...") logger.debug("Checking %s...", side_name)
# Логируем текущее состояние кнопки перед кликом # Логируем текущее состояние кнопки перед кликом
button_classes = side_button.get_attribute("class") or "" button_classes = side_button.get_attribute("class") or ""
logger.debug(f"Button classes before click: {button_classes}") logger.debug("Button classes before click: %s", button_classes)
# Проверяем, активна ли уже эта сторона # Проверяем, активна ли уже эта сторона
current_active = self.get_current_active_side() current_active = self.get_current_active_side()
logger.debug(f"Current active side (before click): '{current_active}'") logger.debug("Current active side (before click): '%s'", current_active)
if current_active == side_name: if current_active == side_name:
logger.debug(f"{side_name} is already active") logger.debug("%s is already active", side_name)
else: else:
# Если не активна, кликаем для переключения # Если не активна, кликаем для переключения
logger.debug(f"Switching to {side_name}...") logger.debug("Switching to %s...", side_name)
side_button.click() side_button.click()
# Даем время на перерисовку классов (увеличиваем время) # Даем время на перерисовку классов (увеличиваем время)
self.wait_for_timeout(2500) self.wait_for_timeout(2500)
# Проверяем классы после клика # Проверяем классы после клика
button_classes_after = side_button.get_attribute("class") or "" button_classes_after = side_button.get_attribute("class") or ""
logger.debug(f"Button classes after click: {button_classes_after}") logger.debug("Button classes after click: %s", button_classes_after)
# Проверяем, что нужная сторона стала активной # Проверяем, что нужная сторона стала активной
active_side = self.get_current_active_side() active_side = self.get_current_active_side()
logger.debug(f"Active side after switching: '{active_side}'") logger.debug("Active side after switching: '%s'", active_side)
assert active_side == side_name, \ assert active_side == side_name, \
f"Wrong side is active: '{active_side}', expected: '{side_name}'" f"Wrong side is active: '{active_side}', expected: '{side_name}'"
logger.debug(f"{side_name} successfully activated") logger.debug("%s successfully activated", side_name)
# Проверяем позиции юнитов # Проверяем позиции юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
total_positions = unit_positions.count() total_positions = unit_positions.count()
logger.debug(f"Total unit positions: {total_positions}") logger.debug("Total unit positions: %d", total_positions)
assert total_positions > 0, f"No unit positions found on {side_name}" assert total_positions > 0, f"No unit positions found on {side_name}"
# Проверяем юниты # Проверяем юниты
all_units = self.page.locator(RackLocators.ALL_UNITS) all_units = self.page.locator(RackLocators.ALL_UNITS)
all_units_count = all_units.count() all_units_count = all_units.count()
units_per_side = all_units_count // 2 units_per_side = all_units_count // 2
logger.debug(f"Units on {side_name}: {units_per_side}") logger.debug("Units on %s: %d", side_name, units_per_side)
# Проверяем устройства # Проверяем устройства
devices = self.page.locator(RackLocators.DEVICE_ELEMENTS) devices = self.page.locator(RackLocators.DEVICE_ELEMENTS)
@ -562,34 +680,39 @@ class RackPage(BasePage):
slots = first_device.locator(RackLocators.DEVICE_SLOTS) slots = first_device.locator(RackLocators.DEVICE_SLOTS)
slot_count = slots.count() slot_count = slots.count()
logger.debug(f"Devices found: {device_count} (showing first)") logger.debug("Devices found: %d (showing first)", device_count)
logger.debug(f" Device: ID={device_id}") logger.debug(" Device: ID=%s", device_id)
logger.debug(f" Title: {device_title}") logger.debug(" Title: %s", device_title)
logger.debug(f" Classes: {device_classes}") logger.debug(" Classes: %s", device_classes)
logger.debug(f" Slots: {slot_count}") logger.debug(" Slots: %d", slot_count)
else: else:
logger.debug("No devices detected") logger.debug("No devices detected")
logger.debug(f"{side_name} check completed successfully") logger.debug("%s check completed successfully", side_name)
def _handle_remove_confirmation_dialog(self) -> None:
"""Обрабатывает диалог подтверждения удаления."""
logger.debug("Handling remove confirmation dialog...")
self.confirm_remove_dialog(confirm=True)
def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None: def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None:
""" """
Ожидает активации вкладки. Ожидает активации вкладки.
Args: Args:
tab_name: Название вкладки для ожидания tab_name (str): Название вкладки для ожидания
timeout: Время ожидания в миллисекундах timeout (int, optional): Время ожидания в миллисекундах, по умолчанию 5000
Raises: Raises:
AssertionError: Если вкладка не активирована в течение таймаута AssertionError: Если вкладка не активирована в течение таймаута
""" """
logger.debug(f"Waiting for tab '{tab_name}' activation...") logger.debug("Waiting for tab '%s' activation...", tab_name)
start_time = self.page.evaluate("Date.now()") start_time = self.page.evaluate("Date.now()")
while self.page.evaluate("Date.now()") - start_time < timeout: while self.page.evaluate("Date.now()") - start_time < timeout:
if self.is_tab_active(tab_name): if self.is_tab_active(tab_name):
logger.debug(f"Tab '{tab_name}' successfully activated") logger.debug("Tab '%s' successfully activated", tab_name)
return return
self.wait_for_timeout(100) self.wait_for_timeout(100)

View File

@ -10,6 +10,7 @@ from components_derived.frames.create_child_element_frame import CreateChildElem
from pages.location_page import LocationPage from pages.location_page import LocationPage
from pages.login_page import LoginPage from pages.login_page import LoginPage
from pages.main_page import MainPage from pages.main_page import MainPage
from pages.rack_page import RackPage
logger = get_logger("CREATE_RACK_ELEMENT_TEST") logger = get_logger("CREATE_RACK_ELEMENT_TEST")
@ -46,24 +47,66 @@ class TestCreateRackElement:
# Мы на главной странице # Мы на главной странице
self.main_page = MainPage(browser) self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel() 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.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(2000) self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone") self.main_page.click_main_navigation_panel_item("test-zone")
self.main_page.wait_for_timeout(2000)
# Создаем экземпляр страницы локации # Создаем экземпляр страницы локации
self.location_page = LocationPage(browser) self.location_page = LocationPage(browser)
#@pytest.mark.develop @pytest.fixture
def test_create_rack_content(self, browser: Page) -> None: def cleanup_racks(self, browser: Page):
"""Тест создания дочернего элемента типа 'Стойка'.""" """Фикстура для очистки созданных стоек."""
# Список для хранения созданных в тесте стоек
created_racks = []
# Проверяем что кнопка "Создать" доступна yield created_racks
self.location_page.should_be_toolbar_buttons()
# После завершения теста удаляем созданные стойки
if created_racks:
logger.debug(f"Cleaning up racks: {created_racks}")
self.main_page.wait_for_timeout(500)
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(1000)
# Удаляем каждую стойку если она существует
for rack_name in created_racks:
# Проверяем существование стойки
if self._check_rack_existance(browser, rack_name):
logger.debug(f"Deleting rack '{rack_name}'...")
# Переходим на страницу стойки для удаления
self.main_page.click_subpanel_item(rack_name, parent="test-zone")
self.main_page.wait_for_timeout(1000)
# Удаляем стойку
self._delete_rack_from_context_menu(browser, rack_name)
# Проверяем удаление
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(500)
# Дополнительная проверка удаления
rack_still_exists = self._check_rack_existance(browser, rack_name)
if rack_still_exists:
logger.error(f"Rack '{rack_name}' still exists after deletion attempt")
logger.debug("Racks cleanup completed")
else:
logger.debug("No racks to cleanup")
def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку.
Args:
browser: Страница Playwright
rack_name: Имя стойки для создания
"""
logger.debug(f"Creating rack with name '{rack_name}'")
# Нажимаем кнопку "Создать" на тулбаре # Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button() self.location_page.click_create_button()
@ -80,19 +123,167 @@ class TestCreateRackElement:
# Открывается набор плашек для задания параметров стойки # Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser) 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()
def _delete_rack_from_context_menu(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
logger.debug(f"Deleting rack '{rack_name}' from context menu...")
# 1. Находим элемент стойки в навигационной панели
rack_element = browser.locator(NavigationPanelLocators.TREEVIEW).get_by_text(rack_name, exact=True).first
# Прокручиваем до элемента если нужно
rack_element.scroll_into_view_if_needed()
self.main_page.wait_for_timeout(500)
# 2. Проверяем и нажимаем кнопку "Изменить"
logger.debug("Step 1: Clicking 'Edit' button...")
rack_page = RackPage(browser)
# Проверяем видимость кнопки
rack_page.toolbar.check_button_visibility("edit")
# Проверяем тултип кнопки
rack_page.toolbar.check_button_tooltip("edit", "Изменить")
# Кликаем на кнопку "Изменить"
rack_page.toolbar.get_button_by_name("edit").click()
logger.debug("Edit button clicked, waiting for edit form...")
# 3. Используем метод click_remove_button, который обрабатывает весь процесс удаления
# включая диалог подтверждения
logger.debug("Clicking remove button...")
rack_page.click_remove_button()
# 4. Проверяем уведомление об успешном удалении - требуется создать разработчику (заведена задача)
# Создаем экземпляр фрейма для доступа к alert компоненту
# create_child_frame = CreateChildElementFrame(browser)
# Проверяем наличие любого alert-окна (не обязательно точного текста)
# create_child_frame.alert.check_alert_presence("")
# Получаем текст alert, чтобы убедиться что удаление прошло успешно
# alert_text = create_child_frame.alert.get_text()
# logger.debug(f"Alert text after deletion: {alert_text}")
# Проверяем что в тексте есть указание на успешное удаление
# assert "удален" in alert_text.lower() or "успешно" in alert_text.lower()
# Закрываем alert
# create_child_frame.alert.close_alert()
logger.debug("Rack deletion completed")
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"]
# Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
logger.debug(f"Available fields:\
{list(create_child_frame.get_input_fields_locators(container_locator).keys())}")
# Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено
logger.debug("Checking field: Глубина (мм)")
if create_child_frame.is_field_filled("Глубина (мм)", container_locator):
logger.debug("Field 'Глубина (мм)' is filled, performing clearing")
create_child_frame.clear_combobox_field("Глубина (мм)")
logger.debug("Clearing completed for 'Глубина (мм)'")
else:
logger.debug("Field 'Глубина (мм)' is already empty, skipping clearing")
# Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено
logger.debug("Checking field: Высота в юнитах")
if create_child_frame.is_field_filled("Высота в юнитах", container_locator):
logger.debug("Field 'Высота в юнитах' is filled, performing clearing")
create_child_frame.clear_combobox_field("Высота в юнитах")
logger.debug("Clearing completed for 'Высота в юнитах'")
else:
logger.debug("Field 'Высота в юнитах' is already empty, skipping clearing")
# Создаем объект данных стойки
rack_data = RackData(
name=name_value,
height=height_value,
depth=depth_value
)
# Заполняем данные стойки
logger.debug(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'")
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
logger.debug("Submitting form for validation")
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(500)
# Проверяем валидацию полей
logger.debug("Checking validation results")
# Обрабатываем alert-окна
if not height_value:
logger.debug("Expecting height validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_height)
create_child_frame.alert.close_alert_by_text(expected_alert_height)
logger.debug("Height alert handled")
if not depth_value:
logger.debug("Expecting depth validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_depth)
create_child_frame.alert.close_alert_by_text(expected_alert_depth)
logger.debug("Depth alert handled")
# Проверяем подсветку обязательных полей
if height_value:
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
logger.debug("Height field validation passed")
else:
create_child_frame.check_field_error_highlighted("Высота в юнитах")
logger.debug("Height field validation failed as expected")
if depth_value:
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.debug("Depth field validation passed")
else:
create_child_frame.check_field_error_highlighted("Глубина (мм)")
logger.debug("Depth field validation failed as expected")
# Проверяем, что остались на странице создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в') create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.debug("Test completed successfully")
# Проверяем что после выбора 'Стойка' появляются специфичные поля
rack_maker.check_rack_fields_presence()
logger.debug("Rack-specific fields are displayed correctly")
create_child_frame.should_be_toolbar_buttons() def test_create_rack_child_element(self, browser: Page, cleanup_racks) -> None:
#@pytest.mark.develop
def test_create_rack_child_element(self, browser: Page) -> None:
"""Тест создания дочернего элемента типа 'Стойка'.""" """Тест создания дочернего элемента типа 'Стойка'."""
# Нажимаем кнопку "Создать" на тулбаре # Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button() self.location_page.click_create_button()
@ -120,33 +311,91 @@ class TestCreateRackElement:
cable_entry="сверху" cable_entry="сверху"
) )
# Сохраняем имя стойки в переменную
rack_name = rack_data.name
cleanup_racks.append(rack_name)
# Заполняем данные стойки # Заполняем данные стойки
rack_maker.fill_rack_data(rack_data) rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку "Добавить" # Нажимаем кнопку "Добавить"
create_child_frame.click_add_button() create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000)
# 1. Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача)
# Проверяем наличие alert-окна
# create_child_frame.alert.check_alert_presence("")
# Получаем текст alert
# alert_text = create_child_frame.alert.get_text()
# logger.debug(f"Alert text after creation: {alert_text}")
# Проверяем, что в тексте есть указание на успешное создание
# assert "создан" in alert_text.lower() or "успешно" in alert_text.lower()
# assert final_rack_name in alert_text
# Закрываем alert
# create_child_frame.alert.close_alert()
# 2. Проверяем, что стойка создана и отображается
logger.debug(f"Verifying that rack '{rack_name}' was created...")
# Обновляем навигационную панель
self.main_page.click_main_navigation_panel_item("test-zone")
# Проверяем существование стойки в навигационной панели
rack_exists = self._check_rack_existance(browser, rack_name)
assert rack_exists, f"Rack '{rack_name}' should be visible in navigation panel after creation"
logger.debug(f"Rack '{rack_name}' is visible in navigation panel")
logger.debug("Test for creating 'Rack' child element completed successfully") logger.debug("Test for creating 'Rack' child element completed successfully")
#@pytest.mark.develop def test_create_rack_content(self, browser: Page) -> None:
def test_create_rack_with_duplicate_name(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.debug("Rack-specific fields are displayed correctly")
create_child_frame.should_be_toolbar_buttons()
def test_create_rack_with_duplicate_name(self, browser: Page, cleanup_racks) -> None:
"""Тест создания стойки с уже существующим именем.
Проверяет, что система корректно обрабатывает попытку создания Проверяет, что система корректно обрабатывает попытку создания
стойки с именем, которое уже используется. стойки с именем, которое уже используется.
""" """
logger.debug("Starting test for creating rack with duplicate name") logger.debug("Starting test for creating rack with duplicate name")
rack_name = "Test-Rack-01" rack_name = "Test-Rack-Duplicate"
# Проверяем, существует ли уже стойка с таким именем # Проверяем, существует ли уже стойка с таким именем
if not self._check_rack_existance(browser, rack_name): if not self._check_rack_existance(browser, rack_name):
logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.") logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.")
self._create_rack(browser, rack_name) self._create_rack(browser, rack_name)
logger.debug(f"First rack with name '{rack_name}' created successfully") logger.debug(f"First rack with name '{rack_name}' created successfully")
# Добавляем стойку в список для очистки
cleanup_racks.append(rack_name)
else: else:
logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one") logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one")
@ -195,136 +444,13 @@ class TestCreateRackElement:
logger.debug("System prevented creating rack with duplicate name") logger.debug("System prevented creating rack with duplicate name")
def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data): def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None:
"""Выполняет один тест валидации обязательных полей. """Тест проверки обязательных полей при создании стойки.
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"]
# Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = create_child_frame.get_input_fields_locators(container_locator)
logger.debug(f"Available fields: {list(fields_locators.keys())}")
# Функция для проверки заполненности combobox поля
def is_field_filled(field_name: str) -> bool:
"""Проверяет, заполнено ли combobox поле."""
if field_name not in fields_locators:
logger.debug(f"Field '{field_name}' not found in fields_locators")
return False
# Получаем контейнер поля
field_container = fields_locators[field_name]
if not field_container.is_visible():
logger.debug(f"Field '{field_name}' not visible")
return False
# Проверяем наличие выбранного значения через v-chip (чип выбранного значения в combobox)
selected_chip = field_container.locator(".v-chip").first
# Проверяем наличие текста в поле
field_text = field_container.text_content() or ""
has_text = bool(field_text.strip())
# Проверяем наличие чипа
has_chip = selected_chip.count() > 0 and selected_chip.is_visible()
logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}")
return has_chip or has_text
# Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено
logger.debug("Checking field: Глубина (мм)")
if is_field_filled("Глубина (мм)"):
logger.debug("Field 'Глубина (мм)' is filled, performing clearing")
create_child_frame.clear_combobox_field("Глубина (мм)")
logger.debug("Clearing completed for 'Глубина (мм)'")
else:
logger.debug("Field 'Глубина (мм)' is already empty, skipping clearing")
# Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено
logger.debug("Checking field: Высота в юнитах")
if is_field_filled("Высота в юнитах"):
logger.debug("Field 'Высота в юнитах' is filled, performing clearing")
create_child_frame.clear_combobox_field("Высота в юнитах")
logger.debug("Clearing completed for 'Высота в юнитах'")
else:
logger.debug("Field 'Высота в юнитах' is already empty, skipping clearing")
# Создаем объект данных стойки
rack_data = RackData(
name=name_value,
height=height_value,
depth=depth_value
)
# Заполняем данные стойки
logger.debug(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'")
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
logger.debug("Submitting form for validation")
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(1000)
# Проверяем валидацию полей
logger.debug("Checking validation results")
# Обрабатываем alert-окна
if not height_value:
logger.debug("Expecting height validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_height)
create_child_frame.alert.close_alert_by_text(expected_alert_height)
logger.debug("Height alert handled")
if not depth_value:
logger.debug("Expecting depth validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_depth)
create_child_frame.alert.close_alert_by_text(expected_alert_depth)
logger.debug("Depth alert handled")
# Проверяем подсветку обязательных полей
if height_value:
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
logger.debug("Height field validation passed")
else:
create_child_frame.check_field_error_highlighted("Высота в юнитах")
logger.debug("Height field validation failed as expected")
if depth_value:
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.debug("Depth field validation passed")
else:
create_child_frame.check_field_error_highlighted("Глубина (мм)")
logger.debug("Depth field validation failed as expected")
# Проверяем, что остались на странице создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.debug("Test completed successfully")
@pytest.mark.develop
def test_required_fields_validation(self, browser: Page) -> None:
"""
Тест проверки обязательных полей при создании стойки.
Проверяет, что система корректно валидирует обязательные поля: Проверяет, что система корректно валидирует обязательные поля:
- Поле 'Высота в юнитах' должно быть заполнено - Поле 'Высота в юнитах' должно быть заполнено
- Поле 'Глубина (мм)' должно быть заполнено - Поле 'Глубина (мм)' должно быть заполнено
""" """
# Текст сообщения alert-окна # Текст сообщения alert-окна
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено" expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено" expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
@ -347,9 +473,9 @@ class TestCreateRackElement:
# Тестовые данные # Тестовые данные
test_cases = [ test_cases = [
{ {
"name": "Test 1: Creating rack with default field values", "name": "Test 1: Required fields are not filled",
"data": { "data": {
"name": "", "name": "Test-Rack-Required-01",
"height": "", "height": "",
"depth": "", "depth": "",
"expected_alert_height": expected_alert_text_height, "expected_alert_height": expected_alert_text_height,
@ -357,19 +483,9 @@ class TestCreateRackElement:
} }
}, },
{ {
"name": "Test 2: Required fields are not filled", "name": "Test 2: Only 'Height in units' field is filled",
"data": { "data": {
"name": "", "name": "Test-Rack-Required-02",
"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", "height": "42",
"depth": "", "depth": "",
"expected_alert_height": expected_alert_text_height, "expected_alert_height": expected_alert_text_height,
@ -377,9 +493,9 @@ class TestCreateRackElement:
} }
}, },
{ {
"name": "Test 4: Only 'Depth (mm)' field is filled", "name": "Test 3: Only 'Depth (mm)' field is filled",
"data": { "data": {
"name": "", "name": "Test-Rack-Required-03",
"height": "", "height": "",
"depth": "1000", "depth": "1000",
"expected_alert_height": expected_alert_text_height, "expected_alert_height": expected_alert_text_height,
@ -392,15 +508,15 @@ class TestCreateRackElement:
for test_case in test_cases: for test_case in test_cases:
logger.debug(test_case["name"]) logger.debug(test_case["name"])
self._perform_required_fields_test( self._perform_required_fields_test(
create_child_frame, rack_maker, test_case["data"] create_child_frame, rack_maker, test_case["data"])
)
logger.debug("System prevented creating rack with invalid required fields") logger.debug("System prevented creating rack with invalid required fields")
# 5. Тест: Заполняем все обязательные поля # 4. Тест: Заполняем все обязательные поля
logger.debug("Test 5: All required fields are filled") logger.debug("Test 4: All required fields are filled")
# Генерируем уникальное имя для финального теста # Генерируем уникальное имя для финального теста
final_rack_name = "Test-Rack-Required-Final" final_rack_name = "Test-Rack-Required-04"
cleanup_racks.append(final_rack_name)
# **ВАЖНО: Очищаем поля перед заполнением** # **ВАЖНО: Очищаем поля перед заполнением**
logger.debug("Clearing fields before filling...") logger.debug("Clearing fields before filling...")
@ -411,33 +527,26 @@ class TestCreateRackElement:
# Очищаем поле "Высота в юнитах" если оно заполнено # Очищаем поле "Высота в юнитах" если оно заполнено
if "Высота в юнитах" in fields_locators: if "Высота в юнитах" in fields_locators:
field_container = fields_locators["Высота в юнитах"] if create_child_frame.is_field_filled("Высота в юнитах", container_locator):
# Проверяем наличие текста в поле
field_text = field_container.inner_text() or ""
if field_text.strip():
logger.debug("Clearing 'Высота в юнитах' field...") logger.debug("Clearing 'Высота в юнитах' field...")
create_child_frame.clear_combobox_field("Высота в юнитах") create_child_frame.clear_combobox_field("Высота в юнитах")
create_child_frame.wait_for_timeout(500) create_child_frame.wait_for_timeout(500)
# Очищаем поле "Глубина (мм)" если оно заполнено # Очищаем поле "Глубина (мм)" если оно заполнено
if "Глубина (мм)" in fields_locators: if "Глубина (мм)" in fields_locators:
field_container = fields_locators["Глубина (мм)"] if create_child_frame.is_field_filled("Глубина (мм)", container_locator):
# Проверяем наличие текста в поле
field_text = field_container.inner_text() or ""
if field_text.strip():
logger.debug("Clearing 'Глубина (мм)' field...") logger.debug("Clearing 'Глубина (мм)' field...")
create_child_frame.clear_combobox_field("Глубина (мм)") create_child_frame.clear_combobox_field("Глубина (мм)")
create_child_frame.wait_for_timeout(500) create_child_frame.wait_for_timeout(500)
# Очищаем поле "Имя" если оно заполнено # Очищаем поле "Имя" если оно заполнено
if "Имя" in fields_locators: if "Имя" in fields_locators:
if create_child_frame.is_field_filled("Имя", container_locator):
logger.debug("Clearing 'Имя' field...")
# Специальная обработка для текстового поля
field_container = fields_locators["Имя"] field_container = fields_locators["Имя"]
# Находим input внутри контейнера
input_field = field_container.locator("input").first input_field = field_container.locator("input").first
if input_field.count() > 0: if input_field.count() > 0:
current_value = input_field.input_value()
if current_value.strip():
logger.debug("Clearing 'Имя' field...")
input_field.click() input_field.click()
input_field.press("Control+A") input_field.press("Control+A")
input_field.press("Backspace") input_field.press("Backspace")
@ -461,38 +570,48 @@ class TestCreateRackElement:
# Нажимаем кнопку создания # Нажимаем кнопку создания
create_child_frame.click_add_button() create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(1000) create_child_frame.wait_for_timeout(500)
# Проверяем, что НЕТ alert-окон для всех обязательных полей # Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача)
#create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000) # Проверяем наличие alert-окна
#create_child_frame.alert.check_alert_absence(expected_alert_text_depth, 1000) # create_child_frame.alert.check_alert_presence("")
#logger.debug("No alert windows for required fields appeared - all fields filled correctly")
# Проверяем, что ушли со страницы создания # Получаем текст alert
try: # alert_text = create_child_frame.alert.get_text()
create_child_frame.check_toolbar_title('Создать дочерний элемент в') # logger.debug(f"Alert text after creation: {alert_text}")
logger.warning("Rack creation may not have completed successfully")
except AssertionError: # Проверяем что в тексте есть указание на успешное создание
logger.debug("Creation page closed - rack successfully created") # assert "создан" in alert_text.lower() or "успешно" in alert_text.lower()
# assert final_rack_name in alert_text
# Закрываем alert
# create_child_frame.alert.close_alert()
logger.debug("Required fields validation test completed successfully") logger.debug("Required fields validation test completed successfully")
def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: # Вспомогательные методы проверки
"""Проверяет существование стойки."""
def _check_rack_existance(self, browser: Page, rack_name: str) -> bool:
"""Проверяет существование стойки.
Args:
browser: Страница Playwright
rack_name: Имя стойки для проверки
Returns:
bool: True если стойка существует, False в противном случае
"""
logger.debug(f"Checking existence of rack with name '{rack_name}'") logger.debug(f"Checking existence of rack with name '{rack_name}'")
# Обновляем навигационную панель # Обновляем навигационную панель
self.main_page.click_main_navigation_panel_item("Объекты") self.main_page.click_main_navigation_panel_item("Объекты")
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.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(3000)
nav_panel_locator = NavigationPanelLocators.TREEVIEW nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента # Проверяем видимость элемента
element = browser.locator(nav_panel_locator).get_by_text(rack_name).first element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible(): if element.is_visible():
logger.debug(f"Rack with name '{rack_name}' found") logger.debug(f"Rack with name '{rack_name}' found")
@ -500,41 +619,3 @@ class TestCreateRackElement:
logger.debug(f"Rack with name '{rack_name}' not found") logger.debug(f"Rack with name '{rack_name}' not found")
return False return False
def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку."""
logger.debug(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)