Compare commits

...

3 Commits

Author SHA1 Message Date
Radislav b1fffac812 Рефакторинг кода: стандартизация форматирования и использование assert
- Добавлены пустые строки после docstrings
- Заменены raise AssertionError на assert
2025-12-10 11:30:28 +03:00
Radislav fd9af2c711 Учтены замечания code-review 2025-12-08 07:22:50 +03:00
Radislav 575b92a869 Перенос метода wait_for_timeout в BaseComponent
- Добавлен метод wait_for_timeout в базовый класс BaseComponent
- Удален дублирующий метод из CreateChildElementFrame
- Удален дублирующий метод из RackObjectMaker
- Теперь все наследники BaseComponent имеют доступ к общему методу ожидания
2025-12-05 13:19:29 +03:00
5 changed files with 230 additions and 161 deletions

View File

@ -48,6 +48,15 @@ class BaseComponent:
else: else:
raise TypeError("locator value should be string type or Locator type") raise TypeError("locator value should be string type or Locator type")
def wait_for_timeout(self, timeout: int) -> None:
"""
Ожидает указанное количество миллисекунд.
Args:
timeout: Время ожидания в миллисекундах
"""
self.page.wait_for_timeout(timeout)
# Закомментированный код сохранен без изменений # Закомментированный код сохранен без изменений
# def wait_for_all_elements(self, locator: Locator, timeout=5000): # def wait_for_all_elements(self, locator: Locator, timeout=5000):
# loc = self.get_locator(locator) # loc = self.get_locator(locator)

View File

@ -12,6 +12,7 @@ logger = get_logger("RACK_MAKER")
@dataclass @dataclass
class RackData: class RackData:
"""Класс для хранения данных стойки.""" """Класс для хранения данных стойки."""
name: str name: str
height: str = "42" height: str = "42"
depth: str = "1000" depth: str = "1000"
@ -35,6 +36,7 @@ class RackObjectMaker(BaseComponent):
Args: Args:
page: Экземпляр страницы Playwright page: Экземпляр страницы Playwright
""" """
super().__init__(page) super().__init__(page)
# Действия: # Действия:
@ -46,40 +48,47 @@ class RackObjectMaker(BaseComponent):
Args: Args:
rack_data: Данные стойки rack_data: Данные стойки
""" """
logger.info(f"Filling rack data: {rack_data.name}") logger.info(f"Filling rack data: {rack_data.name}")
self._fill_required_fields(rack_data) self._fill_text_fields(rack_data)
self._fill_optional_fields(rack_data)
self._fill_combobox_fields(rack_data) self._fill_combobox_fields(rack_data)
logger.info("Rack data filled successfully") logger.info("Rack data filled successfully")
def _fill_required_fields(self, rack_data: RackData) -> None: def _fill_text_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: def clear_and_fill(locator, value: str, field_name: str):
"""Заполняет опциональные поля.""" """Очищает поле и заполняет его значением."""
field = self.page.locator(locator).first
# Очищаем поле
field.click()
field.press("Control+A")
field.press("Backspace")
# Заполняем значение
field.fill(value)
logger.info(f"Filled '{field_name}': {value}")
# Обязательные поля.
if rack_data.name:
clear_and_fill(RackLocators.RACK_NAME_FIELD, rack_data.name, "Name")
# Опциональные поля.
if rack_data.serial: if rack_data.serial:
serial_field = self.page.locator(RackLocators.RACK_SERIAL_FIELD).first clear_and_fill(RackLocators.RACK_SERIAL_FIELD, rack_data.serial, "Serial number")
serial_field.fill(rack_data.serial)
logger.info(f"Filled serial number: {rack_data.serial}")
if rack_data.inventory: if rack_data.inventory:
inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD).first clear_and_fill(RackLocators.RACK_INVENTORY_FIELD, rack_data.inventory, "Inventory number")
inventory_field.fill(rack_data.inventory)
logger.info(f"Filled inventory number: {rack_data.inventory}")
if rack_data.comment: if rack_data.comment:
comment_field = self.page.locator(RackLocators.RACK_COMMENT_FIELD).first clear_and_fill(RackLocators.RACK_COMMENT_FIELD, rack_data.comment, "Comment")
comment_field.fill(rack_data.comment)
logger.info(f"Added comment: {rack_data.comment}")
def _fill_combobox_fields(self, rack_data: RackData) -> None: def _fill_combobox_fields(self, rack_data: RackData) -> None:
"""Заполняет combobox поля.""" """Заполняет combobox поля."""
# Обязательные поля.
if rack_data.height: if rack_data.height:
self._fill_combobox_field("Height in units", rack_data.height, self._fill_combobox_field("Height in units", rack_data.height,
RackLocators.RACK_HEIGHT_FIELD) RackLocators.RACK_HEIGHT_FIELD)
@ -90,6 +99,7 @@ class RackObjectMaker(BaseComponent):
RackLocators.RACK_DEPTH_FIELD) RackLocators.RACK_DEPTH_FIELD)
logger.info(f"Selected depth: {rack_data.depth} mm") logger.info(f"Selected depth: {rack_data.depth} mm")
# Опциональные поля.
if rack_data.cable_entry: if rack_data.cable_entry:
self._fill_combobox_field("Cable entry", rack_data.cable_entry, self._fill_combobox_field("Cable entry", rack_data.cable_entry,
RackLocators.RACK_CABLE_ENTRY_FIELD) RackLocators.RACK_CABLE_ENTRY_FIELD)
@ -124,6 +134,7 @@ class RackObjectMaker(BaseComponent):
value: Значение для установки value: Значение для установки
field_locator: Локатор поля field_locator: Локатор поля
""" """
logger.info(f"Filling field '{field_name}' with value '{value}'...") logger.info(f"Filling field '{field_name}' with value '{value}'...")
# Используем first() для избежания strict mode violation # Используем first() для избежания strict mode violation
@ -157,6 +168,7 @@ class RackObjectMaker(BaseComponent):
Returns: Returns:
str: Локатор поля str: Локатор поля
""" """
field_map = { field_map = {
"Имя": RackLocators.RACK_NAME_FIELD, "Имя": RackLocators.RACK_NAME_FIELD,
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD, "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
@ -168,15 +180,6 @@ class RackObjectMaker(BaseComponent):
return field_map[field_name] 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: def check_rack_fields_presence(self) -> None:
@ -186,6 +189,7 @@ class RackObjectMaker(BaseComponent):
Raises: Raises:
AssertionError: Если какое-либо поле не найдено AssertionError: Если какое-либо поле не найдено
""" """
logger.info("Checking rack fields presence...") logger.info("Checking rack fields presence...")
# Основные обязательные поля # Основные обязательные поля

View File

@ -23,6 +23,7 @@ class CreateChildElementFrame(BaseComponent):
Args: Args:
page: Экземпляр страницы Playwright page: Экземпляр страницы Playwright
""" """
super().__init__(page) super().__init__(page)
# Инициализация компонентов # Инициализация компонентов
@ -46,6 +47,34 @@ class CreateChildElementFrame(BaseComponent):
# Действия: # Действия:
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 get_object_class_options(self) -> list[str]: def get_object_class_options(self) -> list[str]:
""" """
Получает список доступных опций из combobox. Получает список доступных опций из combobox.
@ -53,6 +82,7 @@ class CreateChildElementFrame(BaseComponent):
Returns: Returns:
list[str]: Список доступных классов объектов list[str]: Список доступных классов объектов
""" """
logger.info("Getting combobox 'Accounting object class' options...") logger.info("Getting combobox 'Accounting object class' options...")
available_options = self.selection_bar.get_available_options() available_options = self.selection_bar.get_available_options()
@ -70,62 +100,9 @@ class CreateChildElementFrame(BaseComponent):
return self.selection_bar.get_selection_bar_title() 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: def open_object_class_combobox(self) -> None:
"""Открывает выпадающий список combobox 'Класс объекта учета'.""" """Открывает выпадающий список combobox 'Класс объекта учета'."""
logger.info("Opening combobox 'Accounting object class'...") logger.info("Opening combobox 'Accounting object class'...")
# Ждем стабильности combobox # Ждем стабильности combobox
@ -148,6 +125,7 @@ class CreateChildElementFrame(BaseComponent):
def select_object_class(self, class_name: str) -> None: def select_object_class(self, class_name: str) -> None:
"""Выбирает класс объекта из выпадающего списка.""" """Выбирает класс объекта из выпадающего списка."""
logger.info(f"Selecting object class: '{class_name}'...") logger.info(f"Selecting object class: '{class_name}'...")
# Открываем combobox # Открываем combobox
@ -168,16 +146,29 @@ class CreateChildElementFrame(BaseComponent):
logger.info(f"Object class '{class_name}' successfully selected") logger.info(f"Object class '{class_name}' successfully selected")
def wait_for_timeout(self, timeout: int) -> None: # Проверки:
def check_field_error_highlighted(self, field_name: str) -> None:
""" """
Ожидает указанное количество миллисекунд. Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
Args: Args:
timeout: Время ожидания в миллисекундах field_name: Название поля для проверки
""" """
self.page.wait_for_timeout(timeout)
# Проверки: field_locator = self._get_field_locator(field_name)
self.selection_bar.check_field_error_highlighted(field_name, field_locator)
def check_field_error_not_highlighted(self, field_name: str) -> None:
"""
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args:
field_name: Название поля для проверки
"""
field_locator = self._get_field_locator(field_name)
self.selection_bar.check_field_error_not_highlighted(field_name, field_locator)
def check_object_class_selected(self, expected_class: str) -> None: def check_object_class_selected(self, expected_class: str) -> None:
""" """
@ -185,27 +176,25 @@ class CreateChildElementFrame(BaseComponent):
Args: Args:
expected_class: Ожидаемый выбранный класс объекта expected_class: Ожидаемый выбранный класс объекта
Raises:
AssertionError: Если выбранный класс не соответствует ожидаемому
""" """
logger.info(f"Checking selected object class: '{expected_class}'...") logger.info(f"Checking selected object class: '{expected_class}'...")
self.wait_for_timeout(1000) self.wait_for_timeout(1000)
actual_class = self.get_selected_object_class() actual_class = self.get_selected_object_class()
if (expected_class.lower() in actual_class.lower() or is_match = (expected_class.lower() in actual_class.lower() or
actual_class.lower() in expected_class.lower()): actual_class.lower() in expected_class.lower())
logger.info(
f"Object class '{expected_class}' successfully selected " assert is_match, (
f"(actual: '{actual_class}')" f"Selected class does not match expected. "
) f"Expected: '{expected_class}', Got: '{actual_class}'"
else: )
error_msg = (
f"Selected class does not match expected. " logger.info(
f"Expected: '{expected_class}', Got: '{actual_class}'" f"Object class '{expected_class}' successfully selected "
) f"(actual: '{actual_class}')"
raise AssertionError(error_msg) )
def check_toolbar_title(self, expected_title: str) -> None: def check_toolbar_title(self, expected_title: str) -> None:
""" """
@ -213,16 +202,15 @@ class CreateChildElementFrame(BaseComponent):
Args: Args:
expected_title: Ожидаемый заголовок тулбара expected_title: Ожидаемый заголовок тулбара
Raises:
AssertionError: Если заголовок не соответствует ожидаемому
""" """
logger.info(f"Checking toolbar title: '{expected_title}'...") logger.info(f"Checking toolbar title: '{expected_title}'...")
# Используем метод тулбара с фильтрацией по тексту # Используем метод тулбара с фильтрацией по тексту
actual_text = self.toolbar.get_toolbar_title_text( actual_text = self.toolbar.get_toolbar_title_text(
filter_text="Создать дочерний элемент в" filter_text="Создать дочерний элемент в"
) )
assert expected_title in actual_text, ( assert expected_title in actual_text, (
f"Title does not match. Expected: '{expected_title}', " f"Title does not match. Expected: '{expected_title}', "
f"Got: '{actual_text}'" f"Got: '{actual_text}'"
@ -233,35 +221,43 @@ class CreateChildElementFrame(BaseComponent):
def should_be_toolbar_buttons(self) -> None: def should_be_toolbar_buttons(self) -> None:
""" """
Проверяет наличие и функциональность кнопок тулбара. Проверяет наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если кнопки недоступны или подсказки неверны.
""" """
self.wait_for_timeout(2000) 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_visibility("cancel")
self.toolbar.check_button_tooltip("cancel", "Отменить") self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.get_button_by_name("cancel").click() self.toolbar.click_button("cancel")
self.wait_for_timeout(2000) self.wait_for_timeout(2000)
# Методы проверки ошибок полей (используют SelectionBarComponent) def _get_field_locator(self, field_name: str) -> str:
def check_field_highlighted_error(self, field_name: str) -> None:
""" """
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). Возвращает локатор поля по его названию.
Args: Args:
field_name: Название поля для проверки 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: Returns:
str: Локатор поля
""" """
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args: field_map = {
field_name: Название поля для проверки "Имя": RackLocators.RACK_NAME_FIELD,
""" "Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
field_locator = self._get_field_locator(field_name) "Глубина (мм)": RackLocators.RACK_DEPTH_FIELD,
self.selection_bar.check_field_not_highlighted_error(field_name, field_locator) "Серийный номер": 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]

View File

@ -27,6 +27,7 @@ class SelectionBarComponent(BaseComponent):
locator_or_text: Локатор панели выбора значения (строка или объект Locator) locator_or_text: Локатор панели выбора значения (строка или объект Locator)
или текст для поиска или текст для поиска
""" """
super().__init__(page) super().__init__(page)
# Определяем локатор в зависимости от типа параметра # Определяем локатор в зависимости от типа параметра
@ -47,6 +48,7 @@ class SelectionBarComponent(BaseComponent):
# Действия: # Действия:
def clear_selections(self) -> None: def clear_selections(self) -> None:
"""Удаление ранее выбранных значений""" """Удаление ранее выбранных значений"""
selected_values = self.get_selected_values() selected_values = self.get_selected_values()
if len(selected_values) > 0: if len(selected_values) > 0:
clear_button_locator = self.selection_bar_locator.locator( clear_button_locator = self.selection_bar_locator.locator(
@ -60,6 +62,7 @@ class SelectionBarComponent(BaseComponent):
Returns: Returns:
list[str]: Список доступных опций list[str]: Список доступных опций
""" """
logger.info("Getting available options from dropdown list...") logger.info("Getting available options from dropdown list...")
# Открываем выпадающий список # Открываем выпадающий список
@ -82,11 +85,13 @@ class SelectionBarComponent(BaseComponent):
def get_selection_bar_title(self) -> str: def get_selection_bar_title(self) -> str:
"""Возвращает название панели выбора значения""" """Возвращает название панели выбора значения"""
title_locator = self.selection_bar_locator.locator(SelectionBarLocators.TITLE_LOCATOR) title_locator = self.selection_bar_locator.locator(SelectionBarLocators.TITLE_LOCATOR)
return title_locator.text_content() return title_locator.text_content()
def get_selected_values(self) -> list[str]: def get_selected_values(self) -> list[str]:
"""Возвращает список выбранных значений""" """Возвращает список выбранных значений"""
selected_values_locator = self.selection_bar_locator.locator( selected_values_locator = self.selection_bar_locator.locator(
SelectionBarLocators.PARAMETERS_SELECTED SelectionBarLocators.PARAMETERS_SELECTED
) )
@ -100,6 +105,7 @@ class SelectionBarComponent(BaseComponent):
field_name: Название поля для очистки field_name: Название поля для очистки
field_locator: Локатор поля combobox field_locator: Локатор поля combobox
""" """
logger.info(f"Clearing combobox field '{field_name}' using close button...") logger.info(f"Clearing combobox field '{field_name}' using close button...")
# Находим поле по локатору # Находим поле по локатору
@ -132,6 +138,7 @@ class SelectionBarComponent(BaseComponent):
def open_values_list(self) -> None: def open_values_list(self) -> None:
"""Открытие выпадающего списка путем нажатия на панель выбора значения""" """Открытие выпадающего списка путем нажатия на панель выбора значения"""
expect(self.selection_bar_locator).to_be_visible() expect(self.selection_bar_locator).to_be_visible()
# Проверяем, не открыт ли уже список # Проверяем, не открыт ли уже список
@ -149,26 +156,20 @@ class SelectionBarComponent(BaseComponent):
def select_value(self, name: str) -> None: def select_value(self, name: str) -> None:
"""Выбор значения из списка""" """Выбор значения из списка"""
self.selected_values_list.check_item_with_text(name) self.selected_values_list.check_item_with_text(name)
self.selected_values_list.click_item_with_text(name) self.selected_values_list.click_item_with_text(name)
def wait_for_timeout(self, timeout: int) -> None:
"""Ожидает указанное количество миллисекунд.
Args:
timeout: Время ожидания в миллисекундах
"""
self.page.wait_for_timeout(timeout)
# Проверки: # Проверки:
def check_field_highlighted_error(self, field_name: str, field_locator: str) -> None: def check_field_error_highlighted(self, field_name: str, field_locator: str) -> None:
"""Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). """Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
Args: Args:
field_name: Название поля для проверки field_name: Название поля для проверки
field_locator: Локатор поля для проверки field_locator: Локатор поля для проверки
""" """
logger.info(f"Checking field '{field_name}' for error highlighting...") logger.info(f"Checking field '{field_name}' for error highlighting...")
field_element = self.page.locator(field_locator).first field_element = self.page.locator(field_locator).first
@ -183,18 +184,18 @@ class SelectionBarComponent(BaseComponent):
if parent_container.count() > 0: if parent_container.count() > 0:
has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0 has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0
if not has_error: assert has_error, f"Field '{field_name}' is not highlighted with error color"
raise AssertionError(f"Field '{field_name}' is not highlighted with error color")
logger.info(f"Field '{field_name}' is correctly highlighted with error color") logger.info(f"Field '{field_name}' is correctly highlighted with error color")
def check_field_not_highlighted_error(self, field_name: str, field_locator: str) -> None: def check_field_error_not_highlighted(self, field_name: str, field_locator: str) -> None:
"""Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна). """Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args: Args:
field_name: Название поля для проверки field_name: Название поля для проверки
field_locator: Локатор поля для проверки field_locator: Локатор поля для проверки
""" """
logger.info(f"Checking field '{field_name}' for absence of error highlighting...") logger.info(f"Checking field '{field_name}' for absence of error highlighting...")
field_element = self.page.locator(field_locator).first field_element = self.page.locator(field_locator).first
@ -205,11 +206,10 @@ class SelectionBarComponent(BaseComponent):
# Ищем родительский контейнер # Ищем родительский контейнер
parent_container = field_element.locator(SelectionBarLocators.INPUT_PARENT_CONTAINER).first parent_container = field_element.locator(SelectionBarLocators.INPUT_PARENT_CONTAINER).first
# Проверяем отсутствие классов ошибки с использованием локатора из SelectionBarLocators # Проверяем отсутствие классов ошибки
if parent_container.count() > 0: if parent_container.count() > 0:
has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0 has_error = parent_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS).count() > 0
if has_error: assert not has_error, f"Field '{field_name}' is highlighted with error"
raise AssertionError(f"Field '{field_name}' is highlighted with error")
logger.info(f"Field '{field_name}' correctly has no error highlighting") logger.info(f"Field '{field_name}' correctly has no error highlighting")

View File

@ -24,6 +24,10 @@ class TestCreateRackElement:
4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки 4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки
""" """
# Инициализируем атрибуты
main_page: MainPage = None
location_page: LocationPage = None
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None: def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения. """Фикстура для подготовки тестового окружения.
@ -31,6 +35,7 @@ class TestCreateRackElement:
Args: Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
""" """
# Авторизация в системе # Авторизация в системе
login_page = LoginPage(browser) login_page = LoginPage(browser)
login_page.do_login() login_page.do_login()
@ -99,10 +104,6 @@ class TestCreateRackElement:
# Открывается набор плашек для задания параметров стойки # Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser) rack_maker = RackObjectMaker(browser)
# Проверяем что после выбора 'Стойка' появляются специфичные поля
rack_maker.check_rack_fields_presence()
logger.info("Rack-specific fields are displayed correctly")
# Создаем объект данных стойки # Создаем объект данных стойки
rack_data = RackData( rack_data = RackData(
name="Test-Rack-01", name="Test-Rack-01",
@ -131,12 +132,13 @@ class TestCreateRackElement:
Проверяет, что система корректно обрабатывает попытку создания Проверяет, что система корректно обрабатывает попытку создания
стойки с именем, которое уже используется. стойки с именем, которое уже используется.
""" """
logger.info("Starting test for creating rack with duplicate name") logger.info("Starting test for creating rack with duplicate name")
rack_name = "Test-Rack-01" rack_name = "Test-Rack-01"
# Проверяем, существует ли уже стойка с таким именем # Проверяем, существует ли уже стойка с таким именем
if not self._check_rack_exists(browser, rack_name): if not self._check_rack_existance(browser, rack_name):
logger.info(f"Rack with name '{rack_name}' not found. Creating first rack.") logger.info(f"Rack with name '{rack_name}' not found. Creating first rack.")
self._create_rack(browser, rack_name) self._create_rack(browser, rack_name)
logger.info(f"First rack with name '{rack_name}' created successfully") logger.info(f"First rack with name '{rack_name}' created successfully")
@ -200,6 +202,7 @@ class TestCreateRackElement:
rack_maker: Объект для работы со стойкой rack_maker: Объект для работы со стойкой
test_data: Словарь с данными теста test_data: Словарь с данными теста
""" """
# Распаковываем данные теста # Распаковываем данные теста
name_value = test_data["name"] name_value = test_data["name"]
height_value = test_data["height"] height_value = test_data["height"]
@ -207,9 +210,53 @@ class TestCreateRackElement:
expected_alert_height = test_data["expected_alert_height"] expected_alert_height = test_data["expected_alert_height"]
expected_alert_depth = test_data["expected_alert_depth"] expected_alert_depth = test_data["expected_alert_depth"]
# Очистить поля # Функция для проверки заполненности combobox поля
create_child_frame.clear_combobox_field("Глубина (мм)") def is_field_filled(field_name: str) -> bool:
create_child_frame.clear_combobox_field("Высота в юнитах") """Проверяет, заполнено ли combobox поле."""
# Получаем локатор поля
field_locator = create_child_frame._get_field_locator(field_name)
# Находим элемент поля
field_element = create_child_frame.page.locator(field_locator).first
if not field_element.is_visible():
logger.info(f"Field '{field_name}' not visible")
return False
# Проверяем наличие кнопки закрытия (крестика) - признак заполненного поля
close_button = field_element.locator(
".v-select__selections" # Или другой локатор для кнопки закрытия
)
# Если есть кнопка закрытия, поле заполнено
has_close_button = close_button.count() > 0 and close_button.is_visible()
# Также можно проверить по тексту в поле
field_text = field_element.text_content() or ""
has_text = bool(field_text.strip())
logger.info(f"Field '{field_name}' - has close button: {has_close_button}, has text: {has_text}")
return has_close_button or has_text
# Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено
logger.info("Checking field: Depth (mm)")
if is_field_filled("Глубина (мм)"):
logger.info("Field 'Depth (mm)' is filled, performing clearing")
create_child_frame.clear_combobox_field("Глубина (мм)")
logger.info("Clearing completed for 'Depth (mm)'")
else:
logger.info("Field 'Depth (mm)' is already empty, skipping clearing")
# Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено
logger.info("Checking field: Height in units")
if is_field_filled("Высота в юнитах"):
logger.info("Field 'Height in units' is filled, performing clearing")
create_child_frame.clear_combobox_field("Высота в юнитах")
logger.info("Clearing completed for 'Height in units'")
else:
logger.info("Field 'Height in units' is already empty, skipping clearing")
# Создаем объект данных стойки # Создаем объект данных стойки
rack_data = RackData( rack_data = RackData(
@ -218,35 +265,48 @@ class TestCreateRackElement:
depth=depth_value depth=depth_value
) )
# Заполняем данные # Заполняем данные стойки
logger.info(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'")
rack_maker.fill_rack_data(rack_data) rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания # Нажимаем кнопку создания
logger.info("Submitting form for validation")
create_child_frame.click_add_button() create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(3000) create_child_frame.wait_for_timeout(3000)
# Проверяем подсветку полей # Проверяем валидацию полей
logger.info("Checking validation results")
if height_value: if height_value:
create_child_frame.check_field_not_highlighted_error("Высота в юнитах") create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
logger.info("Height field validation passed")
else: else:
create_child_frame.check_field_highlighted_error("Высота в юнитах") create_child_frame.check_field_error_highlighted("Высота в юнитах")
logger.info("Height field validation failed as expected")
if depth_value: if depth_value:
create_child_frame.check_field_not_highlighted_error("Глубина (мм)") create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.info("Depth field validation passed")
else: else:
create_child_frame.check_field_highlighted_error("Глубина (мм)") create_child_frame.check_field_error_highlighted("Глубина (мм)")
logger.info("Depth field validation failed as expected")
# Обрабатываем alert-окна # Обрабатываем alert-окна
if not height_value: if not height_value:
logger.info("Expecting height validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_height) create_child_frame.alert.check_alert_presence(expected_alert_height)
create_child_frame.alert.close_alert_by_text(expected_alert_height) create_child_frame.alert.close_alert_by_text(expected_alert_height)
logger.info("Height alert handled")
if not depth_value: if not depth_value:
logger.info("Expecting depth validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_depth) create_child_frame.alert.check_alert_presence(expected_alert_depth)
create_child_frame.alert.close_alert_by_text(expected_alert_depth) create_child_frame.alert.close_alert_by_text(expected_alert_depth)
logger.info("Depth alert handled")
# Проверяем, что остались на той же странице # Проверяем, что остались на странице создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в') create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.info("Test completed successfully")
def test_required_fields_validation(self, browser: Page) -> None: def test_required_fields_validation(self, browser: Page) -> None:
""" """
@ -256,6 +316,7 @@ class TestCreateRackElement:
- Поле 'Высота в юнитах' должно быть заполнено - Поле 'Высота в юнитах' должно быть заполнено
- Поле 'Глубина (мм)' должно быть заполнено - Поле 'Глубина (мм)' должно быть заполнено
""" """
# Текст сообщения alert-окна # Текст сообщения alert-окна
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено" expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено" expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
@ -275,9 +336,6 @@ class TestCreateRackElement:
# Открывается набор плашек для задания параметров стойки # Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser) rack_maker = RackObjectMaker(browser)
# Проверяем наличие полей стойки
rack_maker.check_rack_fields_presence()
# Тестовые данные # Тестовые данные
test_cases = [ test_cases = [
{ {
@ -347,9 +405,9 @@ class TestCreateRackElement:
rack_maker.fill_rack_data(rack_data) rack_maker.fill_rack_data(rack_data)
# Проверяем, что ни одно поле не подсвечено цветом ошибки # Проверяем, что ни одно поле не подсвечено цветом ошибки
create_child_frame.check_field_not_highlighted_error("Имя") create_child_frame.check_field_error_not_highlighted("Имя")
create_child_frame.check_field_not_highlighted_error("Высота в юнитах") create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
create_child_frame.check_field_not_highlighted_error("Глубина (мм)") create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.info("No required fields are highlighted with error color - all fields filled correctly") logger.info("No required fields are highlighted with error color - all fields filled correctly")
# Нажимаем кнопку создания # Нажимаем кнопку создания
@ -370,8 +428,9 @@ class TestCreateRackElement:
logger.info("Required fields validation test completed successfully") logger.info("Required fields validation test completed successfully")
def _check_rack_exists(self, browser: Page, rack_name: str) -> bool: def _check_rack_existance(self, browser: Page, rack_name: str) -> bool:
"""Проверяет существование стойки.""" """Проверяет существование стойки."""
logger.info(f"Checking existence of rack with name '{rack_name}'") logger.info(f"Checking existence of rack with name '{rack_name}'")
# Обновляем навигационную панель # Обновляем навигационную панель
@ -395,6 +454,7 @@ class TestCreateRackElement:
def _create_rack(self, browser: Page, rack_name: str) -> None: def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку.""" """Создает стойку."""
logger.info(f"Creating rack with name '{rack_name}'") logger.info(f"Creating rack with name '{rack_name}'")
# Переходим обратно к созданию новой стойки # Переходим обратно к созданию новой стойки