Compare commits

..

4 Commits

109 changed files with 4397 additions and 8606 deletions

View File

@ -1,3 +0,0 @@
ENV=test
AUTH_LOGIN = admin
AUTH_PASSWORD = enodemon-admin

View File

@ -123,13 +123,12 @@ class AlertComponent(BaseComponent):
).filter(has_text=text)).to_be_hidden(timeout=timeout), msg ).filter(has_text=text)).to_be_hidden(timeout=timeout), msg
logger.info(f"Alert window with text '{text}' successfully disappeared") logger.info(f"Alert window with text '{text}' successfully disappeared")
def check_alert_presence(self, text: str, timeout: int = 30000) -> None: def check_alert_presence(self, text: str) -> None:
"""Проверяет наличие alert-окна с заданным текстом. """Проверяет наличие alert-окна с заданным текстом.
Args: Args:
text: Текст для проверки. Если пустая строка - проверяет только text: Текст для проверки. Если пустая строка - проверяет только
наличие окна. наличие окна.
timeout: Время ожидания появления alert в миллисекундах
Raises: Raises:
AssertionError: Если alert-окно не найдено. AssertionError: Если alert-окно не найдено.
@ -137,12 +136,12 @@ class AlertComponent(BaseComponent):
msg = "Alert window is missing" msg = "Alert window is missing"
if text == "": if text == "":
expect(self.page.get_by_role(AlertLocators.ALERT_ROLE)).to_be_visible(timeout=timeout), msg expect(self.page.get_by_role(AlertLocators.ALERT_ROLE)).to_be_visible(), msg
logger.info(f"Alert window successfully displayed") logger.info(f"Alert window successfully displayed")
else: else:
expect(self.page.get_by_role( expect(self.page.get_by_role(
AlertLocators.ALERT_ROLE AlertLocators.ALERT_ROLE
).filter(has_text=text)).to_be_visible(timeout=timeout), msg ).filter(has_text=text)).to_be_visible(), msg
logger.info(f"Alert window with text '{text}' successfully displayed") logger.info(f"Alert window with text '{text}' successfully displayed")
def check_text(self, alert_text: str) -> None: def check_text(self, alert_text: str) -> None:

View File

@ -1,186 +0,0 @@
"""Модуль компонента группы чек-боксов.
Содержит класс CheckboxGroupComponent для работы с группами чек-боксов,
в том числе в выпадающих списках с множественным выбором.
"""
import re
from playwright.sync_api import Page, Locator, expect
from tools.logger import get_logger
from components.base_component import BaseComponent
logger = get_logger("CHECKBOX_GROUP_COMPONENT")
class CheckboxGroupComponent(BaseComponent):
"""Компонент для работы с группами чек-боксов.
Позволяет выбирать/снимать выбор с чек-боксов в группе,
получать список выбранных элементов и проверять их состояние.
Может использоваться как для выпадающих списков с множественным выбором,
так и для любых других групп чек-боксов на странице.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компонент группы чек-боксов.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
def get_checkbox_locator(self, text: str, container_locator: Locator | None = None) -> Locator:
"""Возвращает локатор чек-бокса с указанным текстом.
Args:
text (str): Текст элемента для выбора.
container_locator (Locator | None): Локатор контейнера с чек-боксами.
Если не указан, поиск по всей странице.
Returns:
Locator: Локатор чек-бокса.
"""
if container_locator:
listitem_locator = container_locator.get_by_role("listitem")
else:
listitem_locator = self.page.locator("//div[contains(@class, 'menuable__content__active')]"). \
get_by_role("listitem")
listitem_locator.last.scroll_into_view_if_needed()
listitem_locator.last.wait_for(state="visible")
all_items = listitem_locator.all()
for i, item in enumerate(all_items):
if item.inner_text() == text:
checkbox_locator = item.get_by_role("checkbox")
expect(checkbox_locator).to_be_visible(), \
f"Checkbox with text '{text}' is missing or not visible"
return checkbox_locator
assert False, f"Checkbox locator for {text} has not been found"
def get_checkbox_locator_or(self, text: str, container_locator: Locator | None = None) -> Locator:
"""Возвращает локатор чек-бокса с указанным текстом.
Args:
text (str): Текст элемента для выбора.
container_locator (Locator | None): Локатор контейнера с чек-боксами.
Если не указан, поиск по всей странице.
Returns:
Locator: Локатор чек-бокса.
"""
if container_locator:
checkbox_locator = container_locator.get_by_role("listitem").filter(has_text=text).get_by_role("checkbox")
else:
checkbox_locator = self.page.get_by_role("listitem").filter(has_text=text).get_by_role("checkbox")
if checkbox_locator.count() > 1:
rtext = f"^{text}$"
if container_locator:
checkbox_locator = container_locator.get_by_role("listitem").filter(
has_text=re.compile(rtext)
).get_by_role("checkbox")
else:
checkbox_locator = self.page.get_by_role("listitem").filter(
has_text=re.compile(rtext)
).get_by_role("checkbox")
expect(checkbox_locator).to_be_visible(), \
f"Checkbox with text '{text}' is missing or not visible"
return checkbox_locator
def uncheck_by_text(self, text: str, container_locator: Locator | None = None) -> None:
"""Снимает выбор с чек-бокса по указанному тексту.
Args:
text (str): Текст чек-бокса для снятия выбора.
container_locator (Locator | None): Локатор контейнера с чек-боксами.
"""
logger.info(f"Unchecking checkbox with text: {text}")
self.get_checkbox_locator(text, container_locator).uncheck(force=True)
def check_by_text(self, text: str, container_locator: Locator | None = None) -> None:
"""Выбирает чек-бокс по указанному тексту.
Args:
text (str): Текст чек-бокса для выбора.
container_locator (Locator | None): Локатор контейнера с чек-боксами.
"""
logger.info(f"Checking checkbox with text: {text}")
self.get_checkbox_locator(text, container_locator).check(force=True)
def get_checked_items(self, container_locator: str | Locator) -> list[str]:
"""Возвращает список текстов отмеченных чек-боксов.
Args:
container_locator (str | Locator): Локатор контейнера с группой чек-боксов.
Returns:
list[str]: Список текстов выбранных чек-боксов.
"""
checked_items = []
list_container = self.get_locator(container_locator)
items = list_container.get_by_role("listitem").all()
for item in items:
if item.get_by_role("checkbox").is_checked():
item_text = item.text_content().strip()
if item_text:
checked_items.append(item_text)
logger.info(f"Checked items: {checked_items}")
return checked_items
def are_items_checked(self, container_locator: str | Locator, expected_items: list[str]) -> bool:
"""Проверяет, что указанные чек-боксы выбраны.
Args:
container_locator (str | Locator): Локатор контейнера с группой чек-боксов.
expected_items (list[str]): Список ожидаемых выбранных элементов.
Returns:
bool: True если все указанные чек-боксы выбраны.
"""
checked_items = self.get_checked_items(container_locator)
return all(item in checked_items for item in expected_items)
def check_all(self, container_locator: str | Locator) -> None:
"""Выбирает все чек-боксы в группе.
Args:
container_locator (str | Locator): Локатор контейнера с группой чек-боксов.
"""
logger.info("Checking all checkboxes in group")
list_container = self.get_locator(container_locator)
checkboxes = list_container.get_by_role("checkbox").all()
for checkbox in checkboxes:
if not checkbox.is_checked():
checkbox.check(force=True)
def uncheck_all(self, container_locator: str | Locator) -> None:
"""Снимает выбор со всех чек-боксов в группе.
Args:
container_locator (str | Locator): Локатор контейнера с группой чек-боксов.
"""
logger.info("Unchecking all checkboxes in group")
list_container = self.get_locator(container_locator)
checkboxes = list_container.get_by_role("checkbox").all()
for checkbox in checkboxes:
if checkbox.is_checked():
checkbox.uncheck(force=True)
def get_items_count(self, container_locator: str | Locator) -> int:
"""Возвращает количество чек-боксов в группе.
Args:
container_locator (str | Locator): Локатор контейнера с группой чек-боксов.
Returns:
int: Количество чек-боксов.
"""
list_container = self.get_locator(container_locator)
return list_container.get_by_role("checkbox").count()

View File

@ -86,7 +86,7 @@ class DatePickerComponent(BaseComponent):
days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS) days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS)
days_table_locator.wait_for(timeout=300) days_table_locator.wait_for(timeout=300)
day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day, exact=True) day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day)
visible = day_button_locator.is_visible() visible = day_button_locator.is_visible()
if visible: if visible:
day_button_locator.click() day_button_locator.click()
@ -189,7 +189,7 @@ class DatePickerComponent(BaseComponent):
assert actual_month_year == expected_month_year, \ assert actual_month_year == expected_month_year, \
f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body" f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body"
expected_day = str(expected_date.strftime("%d")).lstrip('0') expected_day = str(expected_date.strftime("%d"))
actual_day = self.get_day() actual_day = self.get_day()
assert actual_day == expected_day, \ assert actual_day == expected_day, \
f"Expected day {expected_day} is not equal actual day {actual_day} on date picker body" f"Expected day {expected_day} is not equal actual day {actual_day} on date picker body"

View File

@ -82,12 +82,7 @@ class DropdownList(BaseComponent):
loc = self.get_locator(locator) loc = self.get_locator(locator)
texts = loc.all_inner_texts() texts = loc.all_inner_texts()
if len(texts) == 1 and texts[0].find("\n") != -1: return texts[0].splitlines()
names = list(texts[0].splitlines())
else:
names = list(texts)
return names
def get_selected_combobox_value(self, combobox_locator: str | Locator, def get_selected_combobox_value(self, combobox_locator: str | Locator,
value_locator: str | Locator = None) -> str: value_locator: str | Locator = None) -> str:
@ -219,15 +214,30 @@ class DropdownList(BaseComponent):
Raises: Raises:
AssertionError: Если элемент отсутствует или недоступен. AssertionError: Если элемент отсутствует или недоступен.
""" """
# Получаем текущий открытый dropdown menu
dropdown_menu = self.page.locator(".v-menu__content--active, .menuable__content__active").first
if dropdown_menu.count() > 0 and dropdown_menu.is_visible():
# Ищем span с точным текстом
element = dropdown_menu.locator(f"span:has-text('{text}')").first
if element.count() > 0:
logger.debug(f"Found user '{text}' directly with span selector")
if element.is_enabled():
return
else:
# Проверяем родительский a
parent_a = element.locator("xpath=ancestor::a").first
if parent_a.count() > 0 and parent_a.is_enabled():
return
# Fallback на старый метод
element = self.page.get_by_role("listitem").filter(has_text=text) element = self.page.get_by_role("listitem").filter(has_text=text)
if element.count() > 1: if element.count() > 1:
rtext = f"^{text}$" rtext = f"^{text}$"
element = self.page.get_by_role("listitem").filter( element = self.page.get_by_role("listitem").filter(
has_text=re.compile(rtext) has_text=re.compile(rtext)
) )
enabled = element.is_enabled() if not element.first.is_enabled():
if not enabled:
assert False, f"Dropdown list item '{text}' is missing or disabled" assert False, f"Dropdown list item '{text}' is missing or disabled"
def check_vertical_scrolling(self, locator: str | Locator) -> bool: def check_vertical_scrolling(self, locator: str | Locator) -> bool:

View File

@ -0,0 +1,151 @@
# components/dynamic_form_component.py
"""Универсальный компонент для работы с динамическими формами."""
from typing import Optional, Dict, List, Any
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
logger = get_logger("DYNAMIC_FORM_COMPONENT")
class DynamicFormComponent:
"""Компонент для работы с формами, находит поля по меткам."""
def __init__(self, page: Page, form_selector: str = "form, .v-form"):
self.page = page
self.form_selector = form_selector
self._form_container = None
self._field_labels_cache = {}
def _get_form_container(self) -> Locator:
"""Получает контейнер формы."""
if self._form_container is None:
container = self.page.locator(self.form_selector)
try:
container.wait_for(state="visible", timeout=5000)
self._form_container = container
except:
raise ValueError(f"Form container not found: {self.form_selector}")
return self._form_container
def get_all_field_labels(self) -> List[str]:
"""Получает все метки полей в форме."""
self._load_field_labels()
return list(self._field_labels_cache.keys())
def _load_field_labels(self) -> None:
"""Загружает метки полей формы."""
if self._field_labels_cache:
return
form = self._get_form_container()
labels = {}
# Ищем все элементы с текстом, которые могут быть метками
# Адаптируйте селекторы под вашу структуру DOM
label_elements = form.locator(
".v-label, label, .field-label, [class*='label']"
)
for i in range(label_elements.count()):
elem = label_elements.nth(i)
label_text = elem.text_content().strip()
if label_text and len(label_text) < 100: # Исключаем большие тексты
labels[label_text] = elem
self._field_labels_cache = labels
def get_field_by_label(self, label_text: str) -> Optional[Locator]:
"""Находит поле по метке."""
self._load_field_labels()
# Прямое совпадение
if label_text in self._field_labels_cache:
return self._get_field_input(self._field_labels_cache[label_text])
# Частичное совпадение
for label, element in self._field_labels_cache.items():
if label_text in label or label in label_text:
return self._get_field_input(element)
logger.warning(f"Поле с меткой '{label_text}' не найдено")
return None
def _get_field_input(self, label_element: Locator) -> Optional[Locator]:
"""Получает элемент ввода рядом с меткой."""
# Разные стратегии поиска input элемента
strategies = [
lambda: label_element.locator("+ input, + textarea").first,
lambda: label_element.locator("../..").locator("input, textarea").first,
lambda: self.page.locator(f"input[aria-label*='{label_element.text_content()}']"),
lambda: self.page.locator(f"input[placeholder*='{label_element.text_content()}']"),
]
for strategy in strategies:
try:
input_elem = strategy()
if input_elem.count() > 0:
return input_elem
except:
continue
return None
def get_field_type_by_label(self, label_text: str) -> str:
"""Определяет тип поля по метке."""
field_element = self.get_field_by_label(label_text)
if not field_element:
return "unknown"
# Определяем тип по атрибутам
input_type = field_element.get_attribute("type")
role = field_element.get_attribute("role")
if input_type == "checkbox" or role == "checkbox":
return "checkbox"
elif role == "combobox" or field_element.get_attribute("aria-haspopup") == "listbox":
return "combobox"
else:
return "text"
def fill_field_by_label(self, label_text: str, value: Any) -> bool:
"""Заполняет поле по метке."""
field_type = self.get_field_type_by_label(label_text)
if field_type == "text":
return self._fill_text_field_by_label(label_text, str(value))
elif field_type == "combobox":
return self._fill_combobox_by_label(label_text, str(value))
elif field_type == "checkbox":
return self._set_checkbox_by_label(label_text, bool(value))
else:
logger.warning(f"Неизвестный тип поля для '{label_text}'")
return False
def _fill_text_field_by_label(self, label_text: str, value: str) -> bool:
"""Заполняет текстовое поле по метке."""
field = self.get_field_by_label(label_text)
if not field:
return False
try:
field.click()
field.fill("")
field.fill(value)
# Проверяем результат
actual_value = field.input_value()
if actual_value == value:
logger.debug(f"✓ Заполнено поле '{label_text}': '{value}'")
return True
else:
logger.warning(f"Несоответствие значения для '{label_text}'")
return False
except Exception as e:
logger.error(f"Ошибка при заполнении поля '{label_text}': {e}")
return False
def _fill_combobox_by_label(self, label_text: str, value: str) -> bool:
"""Заполняет combobox по метке."""
# Реализация аналогичная modal_rack_edit.py
# ...

View File

@ -158,12 +158,12 @@ class EventPanelComponent(BaseComponent):
"""Возвращает текущее положение панели событий относительно страницы: "top", "center","bottom".""" """Возвращает текущее положение панели событий относительно страницы: "top", "center","bottom"."""
style_attr = self.page.locator(EventPanelLocators.AREA_EVENTS).get_attribute("style") style_attr = self.page.locator(EventPanelLocators.AREA_EVENTS).get_attribute("style")
position = "top" position = "bottom"
if style_attr.find("display: none;") == -1: if style_attr.find("display: none;") == -1:
height = style_attr.replace("position: relative;","").replace("height: ","").replace(";", "").lstrip() height = style_attr.replace("height: ","").replace(";", "")
if height == "100%": if height == "100%":
position = "bottom" position = "top"
else: else:
position = "center" position = "center"

View File

@ -94,7 +94,7 @@ class EventsContainerComponent(BaseComponent):
self.last_page.click() self.last_page.click()
def click_filter_button(self) -> EventsFilterPanel: def click_filter_button(self) -> EventsFilterPanel:
"""Нажатие кнопки фильтр""" """Нажатие кнопки перехода на первую страницу"""
self.toolbar.click_button("filter_button") self.toolbar.click_button("filter_button")
expect(self.page.locator("div.menuable__content__active")).to_be_visible(), "Events filter is missing" expect(self.page.locator("div.menuable__content__active")).to_be_visible(), "Events filter is missing"
@ -207,16 +207,6 @@ class EventsContainerComponent(BaseComponent):
self.events_table.check_table_headers(actual_headers, expected_headers) self.events_table.check_table_headers(actual_headers, expected_headers)
def check_events_table_status_button(self, row_index: int, tooltip_text: str) -> None:
""" Проверка наличия в строке кнопки статуса (состояния) и ее тултипа"""
loc = self.container_locator.locator(self.table_locator)
row_locator = self.events_table.get_row_locator(loc, row_index-1)
button = TooltipButton(self.page, row_locator.get_by_role("button"), "status_button")
button.check_visibility(f"Tooltip button is missing in {row_index} table row")
button.check_tooltip_with_text(tooltip_text)
def check_events_table_column_descending_order(self, def check_events_table_column_descending_order(self,
index: int, index: int,
convert2timestamp=False) -> bool: convert2timestamp=False) -> bool:

View File

@ -0,0 +1,72 @@
"""Модуль expand_button_component содержит класс для работы с кнопкой расширения/уменьшения рабочей области вкладки
на странице."""
from playwright.sync_api import Page
from tools.logger import get_logger
from elements.button_element import Button
from components.base_component import BaseComponent
logger = get_logger("EXPAND_BUTTON")
class ExpandButton(BaseComponent):
"""Класс для работы с кнопкой расширения/сжатия рабочей области вкладки на странице.
"""
def __init__(self, page: Page):
"""Инициализирует компонент.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
self.expand_work_area_button_locator = page.get_by_role("button").filter(has_text="navigate_")
self.expand_work_area_button = Button(page,
self.expand_work_area_button_locator,
"expand_work_area_button")
# Действия:
def expand(self) -> None:
"""Нажатие кнопки для расширения рабочей области вкладки"""
can_do_expand = self.is_in_expand_state()
if can_do_expand:
self.expand_work_area_button.click()
else:
assert False, "Work area already expanded"
def reduce(self) -> None:
"""Нажатие кнопки для сжатия рабочей области вкладки"""
can_do_expand = self.is_in_expand_state()
if can_do_expand:
assert False, "Work area already reduced"
else:
self.expand_work_area_button.click()
# Проверки:
def is_in_expand_state(self) -> bool:
"""Проверяет состояние кнопки, способность ее увеличить размер рабочей области вкладки"""
button_state = self.expand_work_area_button_locator.text_content()
if button_state.find("before") != -1:
return True
elif button_state.find("next") != -1:
return False
else:
assert False, f"Got unexpected button state {button_state}"
def should_be_button(self) -> None:
"""Проверяет наличие кнопки расширения/сжатия рабочей области вкладки.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.expand_work_area_button.check_visibility(
"Expand work area button is missing on page"
)

View File

@ -0,0 +1,644 @@
"""Модуль для работы с полями формы."""
import re
from typing import Dict, Optional, List, Callable
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
logger = get_logger("FORM_FIELD_COMPONENT")
class FormFieldComponent:
"""Компонент для работы с полями формы."""
def __init__(self, page: Page):
"""
Инициализирует компонент для работы с полями формы.
Args:
page (Page): Экземпляр страницы Playwright
"""
self.page = page
self._form_fields = None
# Колбэк для загрузки полей
self._load_form_fields_callback = None
def set_form_fields(self, form_fields: Dict[str, Locator]) -> None:
"""
Устанавливает поля формы для работы.
Args:
form_fields: Словарь с полями формы {название: локатор}
"""
self._form_fields = form_fields
logger.debug(f"Set {len(form_fields)} form fields")
def set_load_form_fields_callback(self, callback: Callable[[], Dict[str, Locator]]) -> None:
"""
Устанавливает колбэк для ленивой загрузки полей формы.
Args:
callback: Функция, которая возвращает словарь полей формы
"""
self._load_form_fields_callback = callback
def get_available_fields(self) -> list:
"""
Получает список доступных полей.
Если поля не загружены и есть колбэк для загрузки - загружает их.
Returns:
list: Список названий полей
"""
if not self._form_fields:
if self._load_form_fields_callback:
logger.debug("Lazy loading form fields via callback...")
form_fields = self._load_form_fields_callback()
self.set_form_fields(form_fields)
else:
logger.warning("No form fields set and no load callback available")
return []
return list(self._form_fields.keys())
def get_combobox_options(self, field_name: str) -> List[str]:
"""
Получает список доступных опций из combobox поля.
Args:
field_name: Название combobox поля
Returns:
list[str]: Список доступных опций
"""
if not self._form_fields:
logger.warning("No form fields set")
return []
if field_name not in self._form_fields:
logger.debug(f"Combobox field '{field_name}' not found")
return []
field_container = self._form_fields[field_name]
try:
# Открываем combobox
if not self._open_combobox(field_container):
return []
# Получаем опции из открытого меню
options = self._get_dropdown_options()
# Закрываем combobox
self.page.keyboard.press("Escape")
self.page.wait_for_timeout(500)
logger.debug(f"Found {len(options)} options for '{field_name}': {options}")
return options
except Exception as e:
logger.error(f"Error getting combobox options for '{field_name}': {e}")
self.page.keyboard.press("Escape")
return []
def get_selected_combobox_value(self, field_name: str) -> str:
"""
Получает выбранное значение из combobox.
Args:
field_name: Название combobox поля
Returns:
str: Выбранное значение или пустая строка если ничего не выбрано
"""
if not self._form_fields:
logger.warning("No form fields set")
return ""
if field_name not in self._form_fields:
logger.debug(f"Combobox field '{field_name}' not found")
return ""
field_container = self._form_fields[field_name]
try:
# Ищем span элементы с текстом
span_locator = field_container.locator("span")
if span_locator.count() == 0:
# Пробуем найти в input
input_locator = field_container.locator("input")
if input_locator.count() > 0:
return input_locator.first.input_value().strip()
return ""
# Ищем непустой текст в span
for i in range(span_locator.count()):
span_text = span_locator.nth(i).text_content().strip()
if span_text:
# Пропускаем заголовочные или системные тексты
if any(skip_text in span_text.lower() for skip_text in ['выберите', 'select', 'не выбрано']):
continue
logger.debug(f"Selected value for '{field_name}': '{span_text}'")
return span_text
return ""
except Exception as e:
logger.error(f"Error getting selected value for '{field_name}': {e}")
return ""
def get_field_value(self, field_name: str) -> str:
"""
Получает значение поля (универсальный метод для разных типов полей).
Args:
field_name: Название поля
Returns:
str: Значение поля или пустая строка если поле не найдено
"""
if not self._form_fields or field_name not in self._form_fields:
return ""
field_container = self._form_fields[field_name]
try:
# Пробуем получить значение input
input_field = field_container.locator("input, textarea").first
if input_field.count() > 0:
return input_field.input_value().strip()
# Для combobox получаем выбранное значение
combobox_value = self.get_selected_combobox_value(field_name)
if combobox_value:
return combobox_value
# Для чекбокса получаем состояние
checkbox_state = self.is_checkbox_checked(field_name)
if checkbox_state is not None:
return "checked" if checkbox_state else "unchecked"
return ""
except Exception as e:
logger.error(f"Error getting value for field '{field_name}': {e}")
return ""
def set_checkbox_field(self, field_name: str, checked: bool, checkbox_container_locator: str = None) -> bool:
"""
Устанавливает состояние чекбокса по полному совпадению названия или локатору контейнера.
Args:
field_name: Название поля (для поиска по метке)
checked: True - включить, False - выключить
checkbox_container_locator: Опциональный локатор контейнера чекбокса для прямого поиска
Returns:
bool: True если успешно, False если нет
"""
logger.debug(f"Setting checkbox: field_name='{field_name}', checked={checked}, container_locator={checkbox_container_locator}")
try:
# Находим чекбокс
checkbox = self._find_checkbox(field_name, checkbox_container_locator)
if checkbox is None:
logger.warning(f"Checkbox '{field_name}' not found")
return False
# Получаем текущее состояние
current_state = self._get_checkbox_state(checkbox)
# Если уже в нужном состоянии
if current_state is not None and current_state == checked:
logger.debug(f"Checkbox '{field_name}' already in desired state ({checked})")
return True
# Устанавливаем нужное состояние
if checked:
checkbox.check(force=True)
else:
checkbox.uncheck(force=True)
self.page.wait_for_timeout(500)
# Проверяем результат
new_state = self._get_checkbox_state(checkbox)
if new_state is not None and new_state == checked:
logger.info(f"✓ Checkbox '{field_name}' set to: {checked}")
return True
else:
logger.warning(f"Checkbox '{field_name}' setting failed. Expected: {checked}, got: {new_state}")
return False
except Exception as e:
logger.error(f"Error setting checkbox '{field_name}': {e}")
return False
def is_checkbox_checked(self, field_name: str, checkbox_container_locator: str = None) -> Optional[bool]:
"""
Проверяет состояние чекбокса.
Args:
field_name: Название чекбокс поля
checkbox_container_locator: Опциональный локатор контейнера чекбокса для прямого поиска
Returns:
bool: True если включен, False если выключен, None если поле не найдено или произошла ошибка
"""
logger.debug(f"Checking checkbox state: field_name='{field_name}', container_locator={checkbox_container_locator}")
try:
checkbox = self._find_checkbox(field_name, checkbox_container_locator)
if checkbox is None:
return None
return self._get_checkbox_state(checkbox)
except Exception as e:
logger.error(f"Error checking checkbox state for '{field_name}': {e}")
return None
def _find_checkbox(self, field_name: str, checkbox_container_locator: str = None) -> Optional[Locator]:
"""
Находит чекбокс по названию поля или локатору контейнера.
Returns:
Locator: Локатор чекбокса или None если не найден
"""
# 1. Поиск по локатору контейнера (если указан)
if checkbox_container_locator:
try:
# Ищем контейнер чекбокса
container = self.page.locator(checkbox_container_locator).first
if container.count() > 0:
# Ищем input чекбокса внутри контейнера
checkbox = container.locator("input[type='checkbox']").first
if checkbox.count() > 0:
logger.debug(f"Found checkbox in container: {checkbox_container_locator}")
return checkbox
# Ищем элемент с role='checkbox' внутри контейнера
checkbox = container.locator("[role='checkbox']").first
if checkbox.count() > 0:
logger.debug(f"Found checkbox by role in container: {checkbox_container_locator}")
return checkbox
logger.debug(f"Checkbox container not found: {checkbox_container_locator}")
except Exception as e:
logger.error(f"Error finding checkbox by container locator '{checkbox_container_locator}': {e}")
# 2. Поиск по названию поля (если указаны form_fields)
if field_name and self._form_fields and field_name in self._form_fields:
try:
field_container = self._form_fields[field_name]
field_container.scroll_into_view_if_needed()
self.page.wait_for_timeout(300)
checkbox = field_container.locator("input[type='checkbox'], [role='checkbox']").first
if checkbox.count() > 0:
logger.debug(f"Found checkbox by field name: {field_name}")
return checkbox
except Exception as e:
logger.error(f"Error finding checkbox by field name '{field_name}': {e}")
# 3. Поиск по тексту метки (fallback)
if field_name:
try:
# Ищем label с текстом, затем связанный чекбокс
label = self.page.locator(f"label:has-text('{field_name}')").first
if label.count() > 0:
# Ищем по атрибуту for
label_for = label.get_attribute("for")
if label_for:
checkbox = self.page.locator(f"#{label_for}").first
if checkbox.count() > 0:
return checkbox
# Ищем чекбокс рядом с label
checkbox = label.locator("..").locator("input[type='checkbox'], [role='checkbox']").first
if checkbox.count() > 0:
return checkbox
except Exception as e:
logger.error(f"Error finding checkbox by label text '{field_name}': {e}")
logger.warning(f"Checkbox '{field_name}' not found")
return None
def _get_checkbox_state(self, checkbox: Locator) -> Optional[bool]:
"""
Получает текущее состояние чекбокса.
Используется внутри is_checkbox_checked() и set_checkbox_field().
"""
try:
# 1. aria-checked атрибут
aria_checked = checkbox.get_attribute("aria-checked")
if aria_checked == "true":
return True
elif aria_checked == "false":
return False
# 2. checked атрибут
checked_attr = checkbox.get_attribute("checked")
if checked_attr is not None:
return True
# 3. метод is_checked()
try:
return checkbox.is_checked()
except:
pass
# 4. По классу иконки (для Vuetify)
icon = checkbox.locator(".v-icon, i").first
if icon.count() > 0:
icon_class = icon.get_attribute("class") or ""
if any(marked in icon_class for marked in ["mdi-checkbox-marked", "mdi-check", "check_box"]):
return True
elif any(unmarked in icon_class for unmarked in ["mdi-checkbox-blank-outline", "mdi-checkbox-blank", "check_box_outline_blank"]):
return False
logger.debug("Could not determine checkbox state")
return None
except Exception as e:
logger.debug(f"Error getting checkbox state: {e}")
return None
def _open_combobox(self, field_container: Locator) -> bool:
"""
Открывает выпадающий список combobox.
Args:
field_container: Локатор контейнера поля
Returns:
bool: True если успешно открыт, False если нет
"""
try:
field_container.scroll_into_view_if_needed()
self.page.wait_for_timeout(300)
# Ищем кнопку открытия dropdown
dropdown_button = field_container.locator(".v-input__append-inner, [role='button']").first
if dropdown_button.count() == 0:
# Может быть поле уже открыто или нужно кликнуть на input
input_field = field_container.locator("input").first
input_field.click()
self.page.wait_for_timeout(1000)
else:
dropdown_button.click()
self.page.wait_for_timeout(1000)
# Проверяем что меню открылось
return self._is_dropdown_opened()
except Exception as e:
logger.error(f"Error opening combobox: {e}")
return False
def _is_dropdown_opened(self) -> bool:
"""
Проверяет, открыт ли выпадающий список.
Returns:
bool: True если открыт, False если нет
"""
menu_selectors = [
".v-menu__content.menuable__content__active",
".v-select__menu",
".v-autocomplete__content",
".v-menu__content"
]
for selector in menu_selectors:
menu = self.page.locator(selector).first
if menu.count() > 0 and menu.is_visible():
return True
return False
def _get_dropdown_options(self) -> List[str]:
"""
Получает опции из открытого выпадающего списка.
Returns:
list[str]: Список опций
"""
menu_selectors = [
".v-menu__content.menuable__content__active",
".v-select__menu",
".v-autocomplete__content",
".v-menu__content"
]
for selector in menu_selectors:
menu = self.page.locator(selector).first
if menu.count() > 0 and menu.is_visible():
# Получаем все элементы списка
items = menu.locator("div[role='listitem'], .v-list-item")
if items.count() == 0:
return []
options = []
for i in range(items.count()):
text = items.nth(i).text_content().strip()
if text:
options.append(text)
return options
return []
def check_combobox_has_option(self, field_name: str, option_text: str) -> bool:
"""
Проверяет наличие опции в combobox.
Args:
field_name: Название combobox поля
option_text: Текст опции для проверки
Returns:
bool: True если опция существует, False если нет
"""
options = self.get_combobox_options(field_name)
return option_text in options
def clear_field(self, field_name: str) -> bool:
"""
Очищает значение поля.
Args:
field_name: Название поля
Returns:
bool: True если успешно, False если нет
"""
if not self._form_fields or field_name not in self._form_fields:
return False
field_container = self._form_fields[field_name]
try:
field_container.scroll_into_view_if_needed()
self.page.wait_for_timeout(300)
# Для текстовых полей
input_field = field_container.locator("input, textarea").first
if input_field.count() > 0:
input_field.click()
self.page.wait_for_timeout(200)
input_field.fill("")
self.page.wait_for_timeout(500)
logger.debug(f"✓ Field '{field_name}' cleared")
return True
# Для combobox полей (если есть кнопка очистки)
clear_button = field_container.locator(".v-input__icon--clear, [aria-label='Clear']").first
if clear_button.count() > 0:
clear_button.click()
self.page.wait_for_timeout(500)
logger.debug(f"✓ Combobox '{field_name}' cleared")
return True
logger.debug(f"No clear method found for field '{field_name}'")
return False
except Exception as e:
logger.error(f"Error clearing field '{field_name}': {e}")
return False
def fill_text_field(self, field_name: str, value: str) -> bool:
"""
Заполняет текстовое поле по полному совпадению названия.
Args:
field_name: Название поля
value: Значение для заполнения
Returns:
bool: True если успешно, False если нет
"""
if not self._form_fields:
logger.warning("No form fields set")
return False
# Ищем точное совпадение
if field_name not in self._form_fields:
logger.debug(f"Text field '{field_name}' not found. Available fields: {list(self._form_fields.keys())}")
return False
field_container = self._form_fields[field_name]
try:
field_container.scroll_into_view_if_needed()
# Используем wait_for_timeout из BaseComponent или добавляем небольшую задержку
self.page.wait_for_timeout(300)
# Ищем input поле
input_field = field_container.locator("input, textarea").first
if input_field.count() == 0:
logger.debug(f"Field '{field_name}' doesn't have input element")
return False
# Очищаем и заполняем
input_field.click()
self.page.wait_for_timeout(200)
input_field.fill("")
self.page.wait_for_timeout(200)
input_field.fill(value)
self.page.wait_for_timeout(500)
# Проверяем что значение установлено
actual_value = input_field.input_value()
if actual_value == value:
logger.debug(f"✓ Text field '{field_name}' filled with: '{value}'")
return True
else:
logger.warning(f"Field '{field_name}' value mismatch: expected '{value}', got '{actual_value}'")
return False
except Exception as e:
logger.error(f"Error filling text field '{field_name}': {e}")
return False
def fill_combobox_field(self, field_name: str, value: str) -> bool:
"""
Заполняет combobox поле по полному совпадению названия.
Args:
field_name: Название поля
value: Значение для выбора
Returns:
bool: True если успешно, False если нет
"""
if not self._form_fields:
logger.warning("No form fields set")
return False
# Ищем точное совпадение
if field_name not in self._form_fields:
logger.debug(f"Combobox field '{field_name}' not found. Available fields: {list(self._form_fields.keys())}")
return False
field_container = self._form_fields[field_name]
try:
field_container.scroll_into_view_if_needed()
self.page.wait_for_timeout(300)
# Ищем кнопку открытия dropdown
dropdown_button = field_container.locator(".v-input__append-inner, [role='button']").first
if dropdown_button.count() == 0:
# Может быть поле уже открыто
input_field = field_container.locator("input").first
input_field.click()
self.page.wait_for_timeout(1000)
else:
dropdown_button.click()
self.page.wait_for_timeout(1000)
# Ищем выпадающий список
active_menu = None
menu_selectors = [
".v-menu__content.menuable__content__active",
".v-select__menu",
".v-autocomplete__content",
".v-menu__content"
]
for selector in menu_selectors:
menu = self.page.locator(selector).first
if menu.count() > 0 and menu.is_visible():
active_menu = menu
break
if not active_menu:
logger.debug(f"No dropdown menu found for '{field_name}'")
return False
# Ищем нужный элемент
dropdown_item = active_menu.locator(f"div[role='listitem'], .v-list-item").filter(
has_text=value
).first
if dropdown_item.count() == 0:
logger.debug(f"Value '{value}' not found in dropdown for '{field_name}'")
self.page.keyboard.press("Escape")
return False
# Выбираем значение
dropdown_item.click()
logger.debug(f"✓ Combobox '{field_name}' set to: '{value}'")
self.page.wait_for_timeout(1000)
return True
except Exception as e:
logger.error(f"Error filling combobox '{field_name}': {e}")
self.page.keyboard.press("Escape")
return False

View File

@ -68,6 +68,11 @@ class ModalWindowComponent(BaseComponent):
self.toolbar.click_button("close") self.toolbar.click_button("close")
def clear_content_items(self) -> None:
"""Очищает все элементы содержимого окна."""
self.content_items = {}
def scroll_window_down(self) -> None: def scroll_window_down(self) -> None:
"""Прокручивает содержимое окна вниз.""" """Прокручивает содержимое окна вниз."""

View File

@ -3,7 +3,6 @@
from playwright.sync_api import Page, Locator from playwright.sync_api import Page, Locator
from tools.logger import get_logger from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators from locators.navigation_panel_locators import NavigationPanelLocators
from elements.button_element import Button
from components.base_component import BaseComponent from components.base_component import BaseComponent
logger = get_logger("NAVIGATION_PANEL") logger = get_logger("NAVIGATION_PANEL")
@ -21,14 +20,6 @@ class NavigationPanelComponent(BaseComponent):
super().__init__(page) super().__init__(page)
# кнопки расширения/сжатия рабочей области вкладки на странице
self.expand_workarea_button = Button(page,
page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA),
"expand_workarea_button")
self.reduce_workarea_button = Button(page,
page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA),
"reduce_workarea_button")
# Действия: # Действия:
def click_item(self, locator: str | Locator, item_name: str) -> None: def click_item(self, locator: str | Locator, item_name: str) -> None:
"""Кликает по элементу с указанным текстом. """Кликает по элементу с указанным текстом.
@ -49,20 +40,7 @@ class NavigationPanelComponent(BaseComponent):
item_name: Текст элемента для клика. item_name: Текст элемента для клика.
""" """
root_locator = self.get_locator(node_root_locator) def find_and_click_item(page, root_locator, item_name: str, parent: None|str) -> Locator|None:
if parent:
parent_loc = self._find_and_click_item(self.page, root_locator, parent, parent=None)
found = self._find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found = self._find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found, f"Navigation panel item {item_name} is missing"
def _find_and_click_item(self, page, root_locator, item_name: str, parent: None|str) -> Locator|None:
"""Поиск вложенного элемента с указанным текстом и локатором корневого элемента"""
# Находим все локаторы корневых узлов на текущем уровне # Находим все локаторы корневых узлов на текущем уровне
nodes_count = root_locator.locator('>div.v-treeview-node').count() nodes_count = root_locator.locator('>div.v-treeview-node').count()
@ -131,7 +109,7 @@ class NavigationPanelComponent(BaseComponent):
child_nodes_locator = root_locator.locator( child_nodes_locator = root_locator.locator(
f">div:nth-child({index + 1})" f">div:nth-child({index + 1})"
).locator('>div.v-treeview-node__children') ).locator('>div.v-treeview-node__children')
found_loc = self._find_and_click_item( found_loc = find_and_click_item(
page, child_nodes_locator, item_name, parent=None page, child_nodes_locator, item_name, parent=None
) )
if found_loc: if found_loc:
@ -155,6 +133,17 @@ class NavigationPanelComponent(BaseComponent):
# элемент с заданным именем не найден # элемент с заданным именем не найден
return None return None
root_locator = self.get_locator(node_root_locator)
if parent:
parent_loc = find_and_click_item(self.page, root_locator, parent, parent=None)
found = find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found = find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found, f"Navigation panel item {item_name} is missing"
def get_item_names(self, locator: str | Locator) -> list[str]: def get_item_names(self, locator: str | Locator) -> list[str]:
"""Возвращает тексты всех элементов по указанному локатору. """Возвращает тексты всех элементов по указанному локатору.
@ -235,30 +224,13 @@ class NavigationPanelComponent(BaseComponent):
root_locator = self.get_locator(node_root_locator) root_locator = self.get_locator(node_root_locator)
traverse_tree(self.page, root_locator, level=level, debug=debug) traverse_tree(self.page, root_locator, level=level, debug=debug)
def expand_workarea(self) -> None:
"""Нажатие кнопки для расширения рабочей области страницы"""
if self.page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA).count() > 0:
self.expand_workarea_button.click()
else:
assert False, "Workarea already expanded"
def reduce_workarea(self) -> None:
"""Нажатие кнопки для сжатия рабочей области страницы"""
if self.page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA).count() > 0:
self.reduce_workarea_button.click()
else:
assert False, "Workarea already reduced"
# Проверки: # Проверки:
def check_item_visibility(self, locator: str | Locator, item_name: str, parent = None) -> None: def check_item_visibility(self, locator: str | Locator, item_name: str) -> None:
"""Проверяет видимость элемента с указанным текстом. """Проверяет видимость элемента с указанным текстом.
Args: Args:
locator: Локатор элемента или строка с CSS/XPath. locator: Локатор элемента или строка с CSS/XPath.
item_name: Текст элемента для проверки. item_name: Текст элемента для проверки.
parent: Текст родительского элемента (необязательный параметр)
Note: Note:
Временная обработка для элементов с текстом 'Шаблоны'. Временная обработка для элементов с текстом 'Шаблоны'.
@ -266,13 +238,17 @@ class NavigationPanelComponent(BaseComponent):
msg = f"Navigation panel item '{item_name}' is not visible" msg = f"Navigation panel item '{item_name}' is not visible"
## временно: в навигационной панели есть две панели с именем Шаблоны
## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны
## Шаблоны_2 для Настройки/ZTP/Шаблоны
loc = self.get_locator(locator) loc = self.get_locator(locator)
if parent: if item_name == "Шаблоны_1":
parent_loc = f"//div[contains(@class, 'v-treeview-node') and contains(.,'{parent}')]" loc = loc.get_by_text("Шаблоны").first
loc = loc.locator(parent_loc) elif item_name == "Шаблоны_2":
item_loc = loc.get_by_text(item_name).first loc = loc.get_by_text("Шаблоны").nth(1)
else:
self.check_visibility(item_loc, msg) loc = loc.get_by_text(item_name)
self.check_visibility(loc, msg)
def is_item_visible(self, locator: str | Locator, item_name: str) -> bool: def is_item_visible(self, locator: str | Locator, item_name: str) -> bool:
""" """
@ -292,57 +268,3 @@ class NavigationPanelComponent(BaseComponent):
return False return False
return element_locator.is_visible() return element_locator.is_visible()
def check_sub_item_state(self, node_root_locator: str | Locator, item_name: str, parent: None|str) -> str|None:
"""Выполняет рекурсивный поиск по панели навигации
заданного элемента, делает клик по нему, проверяет наличие индикатора состояния.
Если индикатор состояния присутствует, возвращается его цвет. Иначе None"""
root_locator = self.get_locator(node_root_locator)
if parent:
parent_loc = self._find_and_click_item(self.page, root_locator, parent, parent=None)
found_node_loc = self._find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found_node_loc = self._find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found_node_loc, f"Navigation panel item {item_name} is missing"
color = None
sub_item_state_loc_str = f"//span[text()='{item_name}']/preceding-sibling::*[name()='svg'][2]"
sub_item_state_locator = found_node_loc.locator("div.v-treeview-node__label").locator(sub_item_state_loc_str)
if sub_item_state_locator.count() > 0:
color = sub_item_state_locator.get_attribute("fill")
if color: color = color.lstrip('#')
return color
def should_be_expand_workarea_button(self) -> None:
"""Проверяет наличие кнопки расширения рабочей области страницы.
Raises:
AssertionError: Если кнопка отсутствует.
"""
if self.page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA).count() > 0:
self.expand_workarea_button.check_visibility(
"Expand workarea button is missing on page"
)
else:
assert False, "Expand workarea button is missing on page"
def should_be_reduce_workarea_button(self) -> None:
"""Проверяет наличие кнопки сжатия рабочей области страницы.
Raises:
AssertionError: Если кнопка отсутствует.
"""
if self.page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA).count() > 0:
self.reduce_workarea_button.check_visibility(
"Rduce workarea button is missing on page"
)
else:
assert False, "Reduce workarea button is missing on page"

View File

@ -0,0 +1,351 @@
"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации."""
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from elements.button_element import Button
from components.base_component import BaseComponent
logger = get_logger("NAVIGATION_PANEL")
class NavigationPanelComponent(BaseComponent):
"""Компонент панели навигации. Предоставляет методы для взаимодействия с ней."""
def __init__(self, page: Page):
"""Инициализирует компонент панели навигации.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
# кнопки расширения/сжатия рабочей области вкладки на странице
self.expand_workarea_button = Button(page,
page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA),
"expand_workarea_button")
self.reduce_workarea_button = Button(page,
page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA),
"reduce_workarea_button")
# Действия:
def click_item(self, locator: str | Locator, item_name: str) -> None:
"""Кликает по элементу с указанным текстом.
Args:
locator: Локатор элемента или строка с CSS/XPath.
item_name: Текст элемента для клика.
"""
loc = self.get_locator(locator)
loc.get_by_text(item_name).click()
def click_sub_item(self, node_root_locator: str | Locator, item_name: str, parent: None|str) -> None:
"""Кликает по вложенному элементу с указанным текстом.
Args:
node_root_locator: Локатор для поиска корневых элементов дерева.
item_name: Текст элемента для клика.
"""
root_locator = self.get_locator(node_root_locator)
if parent:
parent_loc = self._find_and_click_item(self.page, root_locator, parent, parent=None)
found = self._find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found = self._find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found, f"Navigation panel item {item_name} is missing"
def _find_and_click_item(self, page, root_locator, item_name: str, parent: None|str) -> Locator|None:
"""Поиск вложенного элемента с указанным текстом и локатором корневого элемента"""
# Находим все локаторы корневых узлов на текущем уровне
nodes_count = root_locator.locator('>div.v-treeview-node').count()
# Если искомый элемент находится на данном уровне, вычисляем локатор и делаем клик
if parent is None:
for index in range(nodes_count):
node = root_locator.locator(f">div:nth-child({index + 1})").first
node_content = node.locator('div.v-treeview-node__content')
if node_content.count() > 0:
node_text = node_content.first.inner_text().strip()
node_texts = node_text.splitlines()
if len(node_texts) > 1:
node_text = node_texts[1]
if item_name == node_text:
node_attr = node.get_attribute('class')
if "v-treeview-node--leaf" not in node_attr:
toggle_button = node.locator(
NavigationPanelLocators.NODE_ROOT
).locator(NavigationPanelLocators.TOGGLE_BUTTON).first
toogle_class_attr = toggle_button.get_attribute('class')
if "v-treeview-node__toggle--open" not in toogle_class_attr:
toggle_button.click()
else:
node.locator(NavigationPanelLocators.NODE_ROOT).click()
page.wait_for_timeout(1000)
return node
# Если элемента нет, рекурсивно ищем дальше
for index in range(nodes_count):
node = root_locator.locator(f">div:nth-child({index + 1})").first
# Извлекаем аттрибуты из корневого узла
node_class_attr = node.get_attribute('class')
is_expanded = False
has_children = False
# Проверяем лист это или начало поддерева
if "v-treeview-node--leaf" not in node_class_attr:
# Проверяем, является ли узел раскрытым
class_attr = node.locator(
NavigationPanelLocators.NODE_ROOT
).locator(NavigationPanelLocators.TOGGLE_BUTTON).first.get_attribute('class')
if "v-treeview-node__toggle--open" in class_attr:
is_expanded = True
# Если узел закрыт можем его раскрыть
if is_expanded is False:
toggle_button = node.locator(
NavigationPanelLocators.NODE_ROOT
).locator(NavigationPanelLocators.TOGGLE_BUTTON).first
toggle_button.click()
# Ждем, пока дочерние элементы прогрузятся/появятся
page.wait_for_timeout(1000)
is_expanded = True
# Проверяем, имеет ли узел дочерние элементы
children_count = node.locator('>div.v-treeview-node__children').count()
content = node.locator('>div.v-treeview-node__children').inner_html()
if children_count > 0 and len(content) != 0:
has_children = True
# Рекурсивный вызов для дочерних элементов
# Ищем дочерние элементы *внутри* текущего узла
if has_children and is_expanded:
child_nodes_locator = root_locator.locator(
f">div:nth-child({index + 1})"
).locator('>div.v-treeview-node__children')
found_loc = self._find_and_click_item(
page, child_nodes_locator, item_name, parent=None
)
if found_loc:
if parent is None:
return found_loc
root_texts = root_locator.locator(
f">div:nth-child({index + 1})"
).inner_text().splitlines()
if parent in root_texts:
return found_loc
# закрываем узел, если в нем ничего не нашли
if is_expanded:
toggle_button = node.locator(
NavigationPanelLocators.NODE_ROOT
).locator(NavigationPanelLocators.TOGGLE_BUTTON).first
toggle_button.click()
page.wait_for_timeout(1000)
# элемент с заданным именем не найден
return None
def get_item_names(self, locator: str | Locator) -> list[str]:
"""Возвращает тексты всех элементов по указанному локатору.
Args:
locator: Локатор элементов или строка с CSS/XPath.
Returns:
Список текстов элементов.
"""
loc = self.get_locator(locator)
return loc.all_inner_texts()
def traverse_panel_tree(self, node_root_locator: str | Locator, level=0, debug=False):
"""
Рекурсивно обходит дерево v-treeview и выводит информацию об элементах.
Args:
node_root_locator: Локатор для поиска корневых элементов дерева.
"""
def traverse_tree(page, root_locator, level=0, debug=False):
# Находим все локаторы корневых узлов на текущем уровне
nodes_count = root_locator.locator('>div.v-treeview-node').count()
for index in range(nodes_count):
node = root_locator.locator(f">div:nth-child({index + 1})").first
# Извлекаем текст и аттрибуты из корневого узла
node_text = node.inner_text()
node_class_attr = node.get_attribute('class')
is_expanded = False
has_children = False
# Проверяем лист это или начало поддерева
if "v-treeview-node--leaf" in node_class_attr:
if debug:
leaf_msg = f'[{level}][{index}] {node_text} (LEAF, Expanded: {is_expanded}'
print(f"{leaf_msg}, Has Children: {has_children})")
print("-----------------------------------------")
else:
# Проверяем, является ли узел раскрытым
class_attr = node.locator(NavigationPanelLocators.TOGGLE_BUTTON).get_attribute('class')
if "v-treeview-node__toggle--open" in class_attr:
is_expanded = True
# Если узел закрыт можем его раскрыть
if is_expanded is False:
toggle_button = node.locator(NavigationPanelLocators.TOGGLE_BUTTON)
toggle_button.click()
# Ждем, пока дочерние элементы прогрузятся/появятся
page.wait_for_timeout(300)
is_expanded = True
# Проверяем, имеет ли узел дочерние элементы
children_count = node.locator('>div.v-treeview-node__children').count()
content = node.locator('>div.v-treeview-node__children').inner_html()
if children_count > 0 and len(content) != 0:
has_children = True
edited_node_text = node_text.replace("expand_more\n", "")
if debug:
# Выводим информацию об узле
node_msg = f'[{level}][{index}] {edited_node_text} (NODE, Expanded: {is_expanded}'
print(f"{node_msg}, Has Children: {has_children})")
print("-----------------------------------------")
# Рекурсивный вызов для дочерних элементов
# Ищем дочерние элементы *внутри* текущего узла
if has_children and is_expanded:
child_nodes_locator = root_locator.locator(
f">div:nth-child({index + 1})"
).locator('>div.v-treeview-node__children')
traverse_tree(page, child_nodes_locator, level+1, debug)
root_locator = self.get_locator(node_root_locator)
traverse_tree(self.page, root_locator, level=level, debug=debug)
def expand_workarea(self) -> None:
"""Нажатие кнопки для расширения рабочей области страницы"""
if self.page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA).count() > 0:
self.expand_workarea_button.click()
else:
assert False, "Workarea already expanded"
def reduce_workarea(self) -> None:
"""Нажатие кнопки для сжатия рабочей области страницы"""
if self.page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA).count() > 0:
self.reduce_workarea_button.click()
else:
assert False, "Workarea already reduced"
# Проверки:
def check_item_visibility(self, locator: str | Locator, item_name: str) -> None:
"""Проверяет видимость элемента с указанным текстом.
Args:
locator: Локатор элемента или строка с CSS/XPath.
item_name: Текст элемента для проверки.
Note:
Временная обработка для элементов с текстом 'Шаблоны'.
"""
msg = f"Navigation panel item '{item_name}' is not visible"
## временно: в навигационной панели есть две панели с именем Шаблоны
## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны
## Шаблоны_2 для Настройки/ZTP/Шаблоны
loc = self.get_locator(locator)
if item_name == "Шаблоны_1":
loc = loc.get_by_text("Шаблоны").first
elif item_name == "Шаблоны_2":
loc = loc.get_by_text("Шаблоны").nth(1)
else:
loc = loc.get_by_text(item_name)
self.check_visibility(loc, msg)
def is_item_visible(self, locator: str | Locator, item_name: str) -> bool:
"""
Проверяет видимость элемента с указанным текстом без выбрасывания исключения.
Args:
locator: Локатор элемента или строка с CSS/XPath.
item_name: Текст элемента для проверки.
Returns:
bool: True если элемент видим, False если нет.
"""
element_locator = self.page.locator(locator).filter(has_text=item_name)
# Сначала проверяем что элемент вообще существует
if element_locator.count() == 0:
return False
return element_locator.is_visible()
def check_sub_item_state(self, node_root_locator: str | Locator, item_name: str, parent: None|str) -> str|None:
"""Выполняет рекурсивный поиск по панели навигации
заданного элемента, делает клик по нему, проверяет наличие индикатора состояния.
Если индикатор состояния присутствует, возвращается его цвет. Иначе None"""
root_locator = self.get_locator(node_root_locator)
if parent:
parent_loc = self._find_and_click_item(self.page, root_locator, parent, parent=None)
found_node_loc = self._find_and_click_item(
self.page, parent_loc.locator('>div.v-treeview-node__children'),
item_name, parent=None
)
else:
found_node_loc = self._find_and_click_item(self.page, root_locator, item_name, parent=None)
assert found_node_loc, f"Navigation panel item {item_name} is missing"
color = None
sub_item_state_loc_str = f"//span[text()='{item_name}']/preceding-sibling::*[name()='svg'][2]"
sub_item_state_locator = found_node_loc.locator("div.v-treeview-node__label").locator(sub_item_state_loc_str)
if sub_item_state_locator.count() > 0:
color = sub_item_state_locator.get_attribute("fill")
if color: color = color.lstrip('#')
return color
def should_be_expand_workarea_button(self) -> None:
"""Проверяет наличие кнопки расширения рабочей области страницы.
Raises:
AssertionError: Если кнопка отсутствует.
"""
if self.page.locator(NavigationPanelLocators.BUTTON_EXPAND_WORKAREA).count() > 0:
self.expand_workarea_button.check_visibility(
"Expand workarea button is missing on page"
)
else:
assert False, "Expand workarea button is missing on page"
def should_be_reduce_workarea_button(self) -> None:
"""Проверяет наличие кнопки сжатия рабочей области страницы.
Raises:
AssertionError: Если кнопка отсутствует.
"""
if self.page.locator(NavigationPanelLocators.BUTTON_REDUCE_WORKAREA).count() > 0:
self.reduce_workarea_button.check_visibility(
"Rduce workarea button is missing on page"
)
else:
assert False, "Reduce workarea button is missing on page"

View File

@ -327,32 +327,56 @@ class TableComponent(BaseComponent):
new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor") new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
assert initial_color != new_color, "Color of row did not change when hovering the cursor" assert initial_color != new_color, "Color of row did not change when hovering the cursor"
def check_mui_table_row_highlighting(self, locator: str | Locator, row_index: int) -> None: def check_mui_table_row_highlighting(self, locator: str | Locator,
row_index: int,
offset_x: float,
offset_y: float,
scale_x: float,
scale_y: float) -> None:
"""Проверяет изменение цвета строки при наведении. """Проверяет изменение цвета строки при наведении.
Args: Args:
locator: Локатор таблицы. locator: Локатор таблицы.
row_index: Индекс проверяемой строки. row_index: Индекс проверяемой строки.
offset_x, offset_y: смещение координат таблицы относительно начала координат
scale_x, scale_y: коээфициенты масштабирования (причина: несовпадение масштабов контента страницы и фрейма)
""" """
print("row_index: "+str(row_index))
table = self.get_locator(locator) table = self.get_locator(locator)
row = table.locator("tbody").locator(".MuiTableRow-root").nth(row_index) row = table.locator("tbody").locator(".MuiTableRow-root").nth(row_index)
# print(row)
# Прокручиваем и ждем
# row.scroll_into_view_if_needed()
# self.page.wait_for_timeout(5000)
# Получение "ограничительной рамки" строки
bounding_box = row.evaluate("el => el.getBoundingClientRect()")
print(bounding_box)
assert bounding_box, "Requested row is not visible"
# Получение текущего цвета фона # Получение текущего цвета фона
initial_color = row.evaluate("el => window.getComputedStyle(el).backgroundColor") initial_color = row.evaluate("el => window.getComputedStyle(el).backgroundColor")
# Вычисление координат целевой строки таблицы и перевод на нее курсора мыши
bounding_box = row.evaluate("el => el.getBoundingClientRect()")
# center_x = (bounding_box["x"] + bounding_box["width"] / 2 + offset_x) * scale_x
# center_y = (bounding_box["y"] + bounding_box["height"] / 2 + offset_y) * scale_y
center_x = (bounding_box["x"] + bounding_box["width"] / 2) * scale_x + offset_x
center_y = (bounding_box["y"] + bounding_box["height"] / 2) * scale_y + offset_y
# Прокручиваем и ждем
row.scroll_into_view_if_needed() row.scroll_into_view_if_needed()
self.page.wait_for_timeout(1000) self.page.wait_for_timeout(3000)
bounding_box = row.bounding_box()
assert bounding_box, "Requested row is not visible"
center_x = bounding_box["x"] + bounding_box["width"] / 4
center_y = bounding_box["y"] + bounding_box["height"] / 2
self.page.mouse.move(math.ceil(center_x), math.ceil(center_y), steps=5) self.page.mouse.move(math.ceil(center_x), math.ceil(center_y), steps=5)
self.page.wait_for_timeout(1000) self.page.wait_for_timeout(3000)
# print(math.ceil(center_y))
# print(offset_y)
# print(scale_y)
# Получение текущего цвета фона # Получение текущего цвета фона
new_color = row.evaluate("el => window.getComputedStyle(el).backgroundColor") new_color = row.evaluate("el => window.getComputedStyle(el).backgroundColor")

View File

@ -240,7 +240,7 @@ class ToolbarComponent(BaseComponent):
message (str): Сообщение об ошибке если тулбар не виден message (str): Сообщение об ошибке если тулбар не виден
""" """
locator = self.get_locator(locator).filter(has_text=self.title).first locator = self.get_locator(locator).filter(has_text=self.title)
expect(locator).to_be_visible(), message expect(locator).to_be_visible(), message
def check_button_visibility(self, name: str) -> None: def check_button_visibility(self, name: str) -> None:

View File

@ -1,39 +0,0 @@
"""Модуль компонента тулбара (class=toolbar_castom).
Содержит класс ToolbarComponent для работы с элементами тулбара
- Проверка видимости элементов
"""
from playwright.sync_api import Page, expect
from tools.logger import get_logger
from locators.certificate_locators import CertificateLocators
from components.base_component import BaseComponent
logger = get_logger("TOOLBAR_CUSTOM")
class CustomToolbar(BaseComponent):
"""Класс для работы с информационным тулбаром на странице.
Наследует функциональность BaseComponent и добавляет специфичные
методы и проверки.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компонент тулбара."""
super().__init__(page)
# Действия:
# (Методы действий будут добавлены по мере необходимости)
# Проверки:
def check_toolbar_presence(self, titles: list[str]) -> None:
"""Проверяет видимость тулбара.
Args:
titles: Набор заголовков тулбара
"""
for title in titles:
locator = self.page.locator(f"{CertificateLocators.TOOLBAR_CASTOM}//span[contains(text(),'{title}')]")
expect(locator).to_be_visible(), f"Toolbar with title {title} is not visible"

View File

@ -0,0 +1,331 @@
"""Модуль создания объекта 'Стойка'."""
from dataclasses import dataclass
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from components.base_component import BaseComponent
logger = get_logger("RACK_MAKER")
logger.setLevel("INFO")
@dataclass
class RackData:
"""Класс для хранения данных стойки."""
name: str
height: str = "42"
depth: str = "1000"
serial: str = ""
inventory: str = ""
comment: str = ""
cable_entry: str = ""
state: str = ""
owner: str = ""
service_org: str = ""
project: str = ""
class RackObjectMaker(BaseComponent):
"""Компонент для создания и настройки стойки."""
def __init__(self, page: Page) -> None:
"""
Инициализирует компонент создания стойки.
Args:
page (Page): Экземпляр страницы Playwright
"""
super().__init__(page)
# Действия:
def _fill_combobox_field(self, field_name: str, value: str, fields_locators: dict) -> None:
"""
Заполняет combobox поле.
Args:
field_name (str): Название поля
value (str): Значение для установки
fields_locators (dict): Словарь с найденными полями формы
Raises:
ValueError: Если поле не найдено в форме
"""
# Получаем контейнер поля по его названию
field_container = fields_locators.get(field_name)
if not field_container:
logger.error(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}")
raise ValueError(f"Field '{field_name}' not found in form")
logger.debug(f"Filling field '{field_name}' with value '{value}'...")
# Прокручиваем до поля
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(300)
# Проверяем видимость поля
self.check_visibility(field_container, f"Field '{field_name}' not found")
# Находим кнопку открытия выпадающего списка внутри контейнера поля
open_button = field_container.locator(".v-input__append-inner").first
# Кликаем для открытия выпадающего списка
open_button.click(force=True)
self.wait_for_timeout(300)
# Вводим значение из выпадающего списка
dropdown_item_locator = RackLocators.DROPDOWN_ITEM_BY_TEXT.format(value)
element = self.page.locator(dropdown_item_locator).first
# Скроллим к элементу если нужно
self._scroll_until_element(
self.page.locator(RackLocators.DROPDOWN_LIST).first,
value
)
self.wait_for_timeout(300)
element.click()
logger.debug(f"Field '{field_name}' filled successfully")
def _fill_combobox_fields(self, rack_data: RackData) -> None:
"""Заполняет combobox поля."""
# Получаем все поля формы
fields_locators = self._get_form_fields()
# Обязательные поля.
if rack_data.height:
self._fill_combobox_field("Высота в юнитах", rack_data.height, fields_locators)
logger.debug(f"Selected height: {rack_data.height} units")
if rack_data.depth:
self._fill_combobox_field("Глубина (мм)", rack_data.depth, fields_locators)
logger.debug(f"Selected depth: {rack_data.depth} mm")
# Опциональные поля.
if rack_data.cable_entry:
self._fill_combobox_field("Ввод кабеля", rack_data.cable_entry, fields_locators)
logger.debug(f"Selected cable entry: {rack_data.cable_entry}")
if rack_data.state:
self._fill_combobox_field("Состояние", rack_data.state, fields_locators)
logger.debug(f"Selected state: {rack_data.state}")
if rack_data.owner:
self._fill_combobox_field("Владелец", rack_data.owner, fields_locators)
logger.debug(f"Selected owner: {rack_data.owner}")
if rack_data.service_org:
self._fill_combobox_field("Обслуживающая организация", rack_data.service_org, fields_locators)
logger.debug(f"Selected service organization: {rack_data.service_org}")
if rack_data.project:
self._fill_combobox_field("Проект/Титул", rack_data.project, fields_locators)
logger.debug(f"Selected project/title: {rack_data.project}")
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)
if not field_container:
logger.warning(f"Field '{field_name}' not found in form. Available fields: {list(fields_locators.keys())}")
return
# Находим input внутри контейнера
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}'")
return
# Проверяем видимость
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 ли поле
is_disabled = input_field.get_attribute("disabled")
is_readonly = input_field.get_attribute("readonly")
if is_disabled or is_readonly:
logger.warning(f"Field '{field_name}' is disabled or readonly")
return
# Очищаем поле
input_field.click()
input_field.press("Control+A")
input_field.press("Backspace")
# Заполняем значение
input_field.fill(value)
logger.debug(f"Filled '{field_name}': {value}")
# Обязательные поля
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:
"""
Скроллит список до тех пор, пока не перестанут подгружаться новые элементы.
Args:
locator (Locator): Локатор элементов или строка с CSS/XPath
name (str): Имя элемента для поиска
"""
loc = self.get_locator(locator)
items_count = 0
attempts = 0
max_attempts = 3
last_item_name = ""
while attempts < max_attempts:
self.page.wait_for_timeout(300)
item_texts = loc.all_inner_texts()
item_names = item_texts[0].splitlines()
current_count = len(item_names)
if current_count == items_count:
attempts += 1
else:
items_count = current_count
attempts = 0
if name in item_names:
last_item_name = name
else:
last_item_name = item_names[current_count-1]
element = loc.get_by_role("listitem").filter(
has_text=last_item_name
)
element.scroll_into_view_if_needed()
self.wait_for_timeout(300)
def 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:
"""
Проверяет наличие полей специфичных для стойки.
Raises:
AssertionError: Если какое-либо поле не найдено
"""
logger.debug("Checking rack fields presence...")
# Получаем все поля формы
fields_locators = self._get_form_fields()
logger.debug(f"Found fields in form: {list(fields_locators.keys())}")
# Список ожидаемых полей для стойки
expected_fields = [
"Имя",
"Высота в юнитах",
"Глубина (мм)",
"Серийный номер",
"Инвентарный номер",
"Комментарий",
"Ввод кабеля",
"Состояние",
"Владелец",
"Обслуживающая организация",
"Проект/Титул"
]
# Проверяем наличие обязательных полей с помощью assert
required_fields = ["Имя", "Высота в юнитах", "Глубина (мм)"]
for field_name in required_fields:
# Проверяем наличие поля в словаре
assert field_name in fields_locators, f"Required field '{field_name}' not found"
field_container = fields_locators[field_name]
# check_visibility внутри использует expect, который тоже вызывает AssertionError
self.check_visibility(field_container, f"Required field '{field_name}' not visible")
logger.debug(f"Required field '{field_name}' found and visible")
# Проверяем наличие дополнительных полей (только логгирование)
for field_name in expected_fields:
if field_name in fields_locators:
field_container = fields_locators[field_name]
if field_container.is_visible():
logger.debug(f"Optional field '{field_name}' found and visible")
else:
logger.debug(f"Optional field '{field_name}' found but not visible")
else:
logger.debug(f"Optional field '{field_name}' not found in form")
logger.debug("All main rack fields are present")

View File

@ -96,12 +96,22 @@ class ActionsEventsContainer(EventsContainerComponent):
events_filter = self.click_filter_button() events_filter = self.click_filter_button()
events_filter.check_content() events_filter.check_content()
events_filter.should_be_filtering_parameter("Статус") filter_status_bar = events_filter.get_filtering_parameter("filter_status")
events_filter.should_be_filtering_parameter("НАИМЕНОВАНИЕ ЗАДАЧИ") filter_status_title = filter_status_bar.get_selection_bar_title()
events_filter.should_be_filtering_parameter("Объект") assert filter_status_title == "Статус", "Filtering parameter bar 'Статус' is missing"
events_filter.should_be_filtering_parameter("Пользователь")
events_filter.click_close_button() filter_task_name_bar = events_filter.get_filtering_parameter("filter_task_name")
filter_task_name_title = filter_task_name_bar.get_selection_bar_title()
assert filter_task_name_title == "НАИМЕНОВАНИЕ ЗАДАЧИ", \
"Filtering parameter bar 'НАИМЕНОВАНИЕ ЗАДАЧИ' is missing"
filter_object_bar = events_filter.get_filtering_parameter("filter_object")
filter_object_title = filter_object_bar.get_selection_bar_title()
assert filter_object_title == "Объект", "Filtering parameter bar 'Объект' is missing"
filter_user_bar = events_filter.get_filtering_parameter("filter_user")
filter_user_title = filter_user_bar.get_selection_bar_title()
assert filter_user_title == "Пользователь", "Filtering parameter bar 'Пользователь' is missing"
def check_events_table_content(self, expected_headers: list[str]) -> None: def check_events_table_content(self, expected_headers: list[str]) -> None:
"""Проверка содержимого таблицы""" """Проверка содержимого таблицы"""
@ -114,16 +124,8 @@ class ActionsEventsContainer(EventsContainerComponent):
self.check_events_table_headers(events_table[0], expected_headers) self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1: if len(events_table) == 1:
logger.info("Table body is missing") logger.info("Table body is missing")
else:
rows_count = len(events_table)
for j in range(1, rows_count-1):
self.check_events_table_status_button(j, "Статус")
self.should_be_pagination_buttons() self.should_be_pagination_buttons()

View File

@ -44,7 +44,7 @@ class AuditEventsContainer(EventsContainerComponent):
# Действия: # Действия:
# Проверки: # Проверки:
def check_content(self) -> None: def check_content(self) -> None:
"""Проверяет содержимое контейнера для отображения событий аудита.""" """Проверяет содержимое контейнера для отображения событий системного журнала."""
expected_headers = [ 'ВРЕМЯ', 'ОПИСАНИЕ', 'ИДЕНТИФИКАТОР'] expected_headers = [ 'ВРЕМЯ', 'ОПИСАНИЕ', 'ИДЕНТИФИКАТОР']
@ -62,9 +62,6 @@ class AuditEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing" assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers) self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1: if len(events_table) == 1:
logger.info("Table body is missing") logger.info("Table body is missing")
@ -74,50 +71,21 @@ class AuditEventsContainer(EventsContainerComponent):
events_filter = self.click_filter_button() events_filter = self.click_filter_button()
events_filter.check_content() events_filter.check_content()
events_filter.should_be_filtering_parameter("Тип") filter_type_bar = events_filter.get_filtering_parameter("filter_type")
events_filter.should_be_filtering_parameter("Роль") filter_type_title = filter_type_bar.get_selection_bar_title()
events_filter.should_be_filtering_parameter("Имя") assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing"
events_filter.should_be_filtering_parameter("ip")
events_filter.click_close_button() filter_role_bar = events_filter.get_filtering_parameter("filter_role")
filter_role_title = filter_role_bar.get_selection_bar_title()
assert filter_role_title == "Роль", "Filtering parameter bar 'Роль' is missing"
def check_content_security(self) -> None: filter_name_bar = events_filter.get_filtering_parameter("filter_name")
"""Проверяет содержимое контейнера для отображения событий безопасности.""" filter_name_title = filter_name_bar.get_selection_bar_title()
assert filter_name_title == "Имя", "Filtering parameter bar 'Имя' is missing"
expected_headers = [ 'ВРЕМЯ', 'ОПИСАНИЕ', 'ИДЕНТИФИКАТОР', 'ТИП'] filter_ip_bar = events_filter.get_filtering_parameter("filter_ip")
filter_ip_title = filter_ip_bar.get_selection_bar_title()
self.should_be_toolbar() assert filter_ip_title == "ip", "Filtering parameter bar 'ip' is missing"
self.should_be_toolbar_buttons()
if not self.is_tab_active("view_events_button"):
self.click_tab_button("view_events_button")
self.wait_for_timeout(1000)
self.should_be_events_table()
events_table = self.get_events_table_content()
if len(events_table) == 0:
assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
logger.info("Table body is missing")
self.should_be_pagination_buttons()
events_filter = self.click_filter_button()
events_filter.check_content()
events_filter.should_be_filtering_parameter("Тип")
events_filter.should_be_filtering_parameter("Роль")
events_filter.should_be_filtering_parameter("Имя")
events_filter.should_be_filtering_parameter("ip")
events_filter.click_close_button()
def should_be_toolbar_buttons(self) -> None: def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара.""" """Проверяет наличие и видимость кнопок тулбара."""

View File

@ -61,9 +61,6 @@ class EventsTabContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing" assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers) self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1: if len(events_table) == 1:
logger.info("Table body is missing") logger.info("Table body is missing")
@ -73,11 +70,17 @@ class EventsTabContainer(EventsContainerComponent):
events_filter = self.click_filter_button() events_filter = self.click_filter_button()
events_filter.check_content() events_filter.check_content()
events_filter.should_be_filtering_parameter("Тип") filter_type_bar = events_filter.get_filtering_parameter("filter_type")
events_filter.should_be_filtering_parameter("Критичность") filter_type_title = filter_type_bar.get_selection_bar_title()
events_filter.should_be_filtering_parameter("Объект") assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing"
events_filter.click_close_button() filter_strictness_bar = events_filter.get_filtering_parameter("filter_strictness")
filter_strictness_title = filter_strictness_bar.get_selection_bar_title()
assert filter_strictness_title == "Критичность", "Filtering parameter bar 'Критичность' is missing"
filter_object_bar = events_filter.get_filtering_parameter("filter_object")
filter_object_title = filter_object_bar.get_selection_bar_title()
assert filter_object_title == "Объект", "Filtering parameter bar 'Объект' is missing"
def should_be_toolbar_buttons(self) -> None: def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара.""" """Проверяет наличие и видимость кнопок тулбара."""

View File

@ -37,6 +37,7 @@ class MaintenanceEventsContainer(EventsContainerComponent):
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.CSV_TOOLBAR_BUTTON), "export_to_csv_button") self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.CSV_TOOLBAR_BUTTON), "export_to_csv_button")
events_filter = self.get_events_filter() events_filter = self.get_events_filter()
events_filter.add_filtering_parameter("filter_date", "Дата")
events_filter.add_filtering_parameter("filter_event_name", "Наименование события") events_filter.add_filtering_parameter("filter_event_name", "Наименование события")
events_filter.add_filtering_parameter("filter_type", "Тип") events_filter.add_filtering_parameter("filter_type", "Тип")
events_filter.add_filtering_parameter("filter_status", "Состояние") events_filter.add_filtering_parameter("filter_status", "Состояние")
@ -66,33 +67,43 @@ class MaintenanceEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing" assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers) self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
rows_count = len(events_table) if len(events_table) == 1:
if rows_count == 1:
logger.info("Table body is missing") logger.info("Table body is missing")
else:
j = 1
while j < rows_count:
self.check_events_table_status_button(j, "Состояние")
j += 1
self.should_be_pagination_buttons() self.should_be_pagination_buttons()
events_filter = self.click_filter_button() events_filter = self.click_filter_button()
events_filter.check_content() events_filter.check_content()
events_filter.should_be_filtering_parameter("Наименование события") filter_date_bar = events_filter.get_filtering_parameter("filter_date")
events_filter.should_be_filtering_parameter("Тип") filter_date_title = filter_date_bar.get_selection_bar_title()
events_filter.should_be_filtering_parameter("Состояние") assert filter_date_title == "Дата", "Filtering parameter bar 'Дата' is missing"
events_filter.should_be_filtering_parameter("Объект")
events_filter.should_be_filtering_parameter("Автор")
events_filter.should_be_filtering_parameter("Расположение")
events_filter.click_close_button() filter_event_name_bar = events_filter.get_filtering_parameter("filter_event_name")
filter_event_name_title = filter_event_name_bar.get_selection_bar_title()
assert filter_event_name_title == "Наименование события", \
"Filtering parameter bar 'Наименование события' is missing"
filter_type_bar = events_filter.get_filtering_parameter("filter_type")
filter_type_title = filter_type_bar.get_selection_bar_title()
assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing"
filter_status_bar = events_filter.get_filtering_parameter("filter_status")
filter_status_title = filter_status_bar.get_selection_bar_title()
assert filter_status_title == "Состояние", "Filtering parameter bar 'Состояние' is missing"
filter_object_bar = events_filter.get_filtering_parameter("filter_object")
filter_object_title = filter_object_bar.get_selection_bar_title()
assert filter_object_title == "Объект", "Filtering parameter bar 'Объект' is missing"
filter_author_bar = events_filter.get_filtering_parameter("filter_author")
filter_author_title = filter_author_bar.get_selection_bar_title()
assert filter_author_title == "Автор", "Filtering parameter bar 'Автор' is missing"
filter_location_bar = events_filter.get_filtering_parameter("filter_location")
filter_location_title = filter_location_bar.get_selection_bar_title()
assert filter_location_title == "Расположение", "Filtering parameter bar 'Расположение' is missing"
def should_be_toolbar_buttons(self) -> None: def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара.""" """Проверяет наличие и видимость кнопок тулбара."""

View File

@ -61,32 +61,26 @@ class SystemLogEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing" assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers) self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1: if len(events_table) == 1:
logger.info("Table body is missing") logger.info("Table body is missing")
else:
j = 1
# так как записей много, проверяем первые 40
rows_count = 40
if len(events_table) < 40:
rows_count = len(events_table)
while j < rows_count:
self.check_events_table_status_button(j, "Критичность")
j += 1
self.should_be_pagination_buttons() self.should_be_pagination_buttons()
events_filter = self.click_filter_button() events_filter = self.click_filter_button()
events_filter.check_content() events_filter.check_content()
events_filter.should_be_filtering_parameter("Тип") filter_type_bar = events_filter.get_filtering_parameter("filter_type")
events_filter.should_be_filtering_parameter("Критичность") filter_type_title = filter_type_bar.get_selection_bar_title()
events_filter.should_be_filtering_parameter("Объект") assert filter_type_title == "Тип", "Filtering parameter bar 'Тип' is missing"
events_filter.click_close_button() filter_strictness_bar = events_filter.get_filtering_parameter("filter_strictness")
filter_strictness_title = filter_strictness_bar.get_selection_bar_title()
assert filter_strictness_title == "Критичность", "Filtering parameter bar 'Критичность' is missing"
filter_object_bar = events_filter.get_filtering_parameter("filter_object")
filter_object_title = filter_object_bar.get_selection_bar_title()
assert filter_object_title == "Объект", "Filtering parameter bar 'Объект' is missing"
def should_be_toolbar_buttons(self) -> None: def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара.""" """Проверяет наличие и видимость кнопок тулбара."""

View File

@ -181,7 +181,6 @@ class DateInput(BaseComponent):
result = False result = False
inner_text = self.switch_mode_button.get_text(0).strip() inner_text = self.switch_mode_button.get_text(0).strip()
print(inner_text)
if inner_text == "keyboard": if inner_text == "keyboard":
result = True result = True
return result return result

View File

@ -1,7 +1,7 @@
"""Модуль панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс """Модуль панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс
для работы с формами ввода, их элементами и проверками.""" для работы с формами ввода, их элементами и проверками."""
from playwright.sync_api import Page, Locator, expect from playwright.sync_api import Page, Locator
from tools.logger import get_logger from tools.logger import get_logger
from elements.button_element import Button from elements.button_element import Button
from components.base_component import BaseComponent from components.base_component import BaseComponent
@ -43,16 +43,12 @@ class EventsFilterPanel(BaseComponent):
self.reset_button = Button(page, self.reset_button = Button(page,
self.page.get_by_role("button").filter(has_text='Сбросить Фильтры'), self.page.get_by_role("button").filter(has_text='Сбросить Фильтры'),
"reset_button") "reset_button")
self.close_button = Button(page,
self.page.get_by_role("button").filter(has_text='Закрыть'),
"close_button")
# Действия: # Действия:
def add_filtering_parameter(self, name: str, title: str) -> None: def add_filtering_parameter(self, name: str, title: str) -> None:
"""Добавляет поле задания параметров фильтрации по заданному имени.""" """Добавляет поле задания параметров фильтрации по заданному имени."""
# loc = self.events_filter_locator.get_by_role("combobox").filter(has_text=title) loc = self.events_filter_locator.get_by_role("combobox").filter(has_text=title)
loc = self.events_filter_locator.get_by_role("combobox").get_by_placeholder(title)
self.filtering_parameters[name] = SelectionBarComponent(self.page, loc) self.filtering_parameters[name] = SelectionBarComponent(self.page, loc)
def get_filtering_parameter(self, name: str) -> SelectionBarComponent | None: def get_filtering_parameter(self, name: str) -> SelectionBarComponent | None:
@ -80,11 +76,6 @@ class EventsFilterPanel(BaseComponent):
self.reset_button.click() self.reset_button.click()
def click_close_button(self) -> None:
"""Клик по кнопке закрытия окна фильтрации."""
self.close_button.click()
# Проверки: # Проверки:
def check_content(self) -> None: def check_content(self) -> None:
"""Проверяет наличие постоянных полей панели параметров фильтрации.""" """Проверяет наличие постоянных полей панели параметров фильтрации."""
@ -94,7 +85,7 @@ class EventsFilterPanel(BaseComponent):
self.check_apply_button_visibility() self.check_apply_button_visibility()
self.check_reset_button_visibility() self.check_reset_button_visibility()
self.check_close_button_visibility()
def check_vertical_scrolling(self, locator: str| Locator) -> bool: def check_vertical_scrolling(self, locator: str| Locator) -> bool:
"""Проверяет возможность вертикальной прокрутки формы.""" """Проверяет возможность вертикальной прокрутки формы."""
@ -106,18 +97,7 @@ class EventsFilterPanel(BaseComponent):
self.apply_button.check_visibility("Apply Filter Button is missing") self.apply_button.check_visibility("Apply Filter Button is missing")
def check_close_button_visibility(self) -> None:
"""Проверяет наличие кнопки закрытия окна фильтрации."""
self.close_button.check_visibility("Close Filter window Button is missing")
def check_reset_button_visibility(self) -> None: def check_reset_button_visibility(self) -> None:
"""Проверяет наличие кнопки сброса фильтра.""" """Проверяет наличие кнопки сброса фильтра."""
self.reset_button.check_visibility("Reset Filter Button is missing") self.reset_button.check_visibility("Reset Filter Button is missing")
def should_be_filtering_parameter(self, title: str) -> None:
"""Проверяет наличие поля панели параметров фильтрации по его заголовку."""
loc = self.events_filter_locator.get_by_role("combobox").get_by_placeholder(title)
expect(loc).to_be_visible(), f"Filtering parameter bar '{title}' is missing"

View File

@ -1,7 +1,6 @@
"""Модуль фрейма создания дочернего элемента.""" """Модуль фрейма создания дочернего элемента."""
import re import re
from typing import Dict, Any, Optional
from playwright.sync_api import 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
@ -10,14 +9,13 @@ from components.alert_component import AlertComponent
from components.base_component import BaseComponent from components.base_component import BaseComponent
from components.toolbar_component import ToolbarComponent from components.toolbar_component import ToolbarComponent
from components_derived.selection_bar_component import SelectionBarComponent from components_derived.selection_bar_component import SelectionBarComponent
from forms.create_rack_form import CreateRackForm, CreateRackData
logger = get_logger("CREATE_ELEMENT_FRAME") logger = get_logger("CREATE_CHILD_ELEMENT_FRAME")
logger.setLevel("INFO") logger.setLevel("INFO")
class CreateChildElementFrame(BaseComponent):
class CreateElementFrame(BaseComponent):
"""Фрейм создания дочернего элемента.""" """Фрейм создания дочернего элемента."""
def __init__(self, page: Page) -> None: def __init__(self, page: Page) -> None:
@ -27,10 +25,8 @@ class CreateElementFrame(BaseComponent):
Args: Args:
page (Page): Экземпляр страницы Playwright page (Page): Экземпляр страницы Playwright
""" """
super().__init__(page)
# Инициализация формы создания стойки super().__init__(page)
self.rack_form = CreateRackForm(page)
# Инициализация компонентов # Инициализация компонентов
self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в") self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в")
@ -42,7 +38,7 @@ class CreateElementFrame(BaseComponent):
has_text="Создать дочерний элемент в" has_text="Создать дочерний элемент в"
).get_by_role("button").nth(0) ).get_by_role("button").nth(0)
# Кнопка "Отменить" - используем рабочий локатор # Кнопка "Отменить" -
cancel_button_locator = self.page.get_by_role("navigation").filter( cancel_button_locator = self.page.get_by_role("navigation").filter(
has_text=re.compile('Создать дочерний элемент в') has_text=re.compile('Создать дочерний элемент в')
).get_by_role("button").nth(1) ).get_by_role("button").nth(1)
@ -51,67 +47,7 @@ class CreateElementFrame(BaseComponent):
self.toolbar.add_tooltip_button(add_button_locator, "add") self.toolbar.add_tooltip_button(add_button_locator, "add")
self.toolbar.add_tooltip_button(cancel_button_locator, "cancel") self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")
# Делегирование методов форме создания стойки # Действия:
def fill_rack_data(self, rack_data: CreateRackData) -> Dict[str, int]:
"""
Заполняет поля формы создания стойки.
Args:
rack_data: Данные для заполнения
Returns:
Словарь с результатами заполнения
"""
return self.rack_form.fill_rack_data(rack_data)
def clear_field(self, field_name: str) -> None:
"""
Очищает указанное поле формы.
Args:
field_name: Название поля для очистки
"""
self.rack_form.clear_field(field_name)
def get_field_value(self, field_name: str) -> Optional[str]:
"""
Получает значение поля формы.
Args:
field_name: Название поля
Returns:
Значение поля или None если поле не найдено
"""
return self.rack_form.get_field_value(field_name)
def is_field_highlighted_as_error(self, field_name: str) -> bool:
"""
Проверяет, подсвечено ли поле как ошибочное.
Args:
field_name: Название поля для проверки
Returns:
bool: True если поле подсвечено ошибкой
"""
return self.rack_form.is_field_highlighted_as_error(field_name)
def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool:
"""
Ожидает появления подсветки ошибки на поле.
Args:
field_name: Название поля
timeout: Таймаут в миллисекундах
Returns:
bool: True если ошибка появилась
"""
return self.rack_form.wait_for_field_error(field_name, timeout)
# Оригинальные методы фрейма
def clear_combobox_field(self, field_name: str) -> None: def clear_combobox_field(self, field_name: str) -> None:
""" """
@ -120,20 +56,25 @@ class CreateElementFrame(BaseComponent):
Args: Args:
field_name (str): Название поля для очистки field_name (str): Название поля для очистки
""" """
logger.debug(f"Clearing combobox field '{field_name}'...") logger.debug(f"Clearing combobox field '{field_name}'...")
# Получаем контейнер формы # Получаем контейнер формы
container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER).nth(1) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = self.get_input_fields_locators(container_locator) fields_locators = self.get_input_fields_locators(container_locator)
if field_name not in fields_locators: if field_name not in fields_locators:
logger.warning(f"Field '{field_name}' not found in form") logger.warning(f"Field '{field_name}' not found in form")
return return
# Получаем контейнер поля
field_container = fields_locators[field_name] field_container = fields_locators[field_name]
# Прокручиваем до поля
field_container.scroll_into_view_if_needed() field_container.scroll_into_view_if_needed()
self.wait_for_timeout(300) self.wait_for_timeout(300)
# Проверяем видимость
if not field_container.is_visible(): if not field_container.is_visible():
logger.debug(f"Field '{field_name}' is not visible after scrolling") logger.debug(f"Field '{field_name}' is not visible after scrolling")
return return
@ -141,7 +82,11 @@ class CreateElementFrame(BaseComponent):
# Ищем кнопку закрытия (крестик) внутри контейнера поля # Ищем кнопку закрытия (крестик) внутри контейнера поля
close_button = field_container.locator("i.mdi-close").first close_button = field_container.locator("i.mdi-close").first
# Проверяем наличие и видимость кнопки закрытия
if close_button.count() > 0: if close_button.count() > 0:
logger.debug(f"Found close button for field '{field_name}'")
# Если кнопка закрытия видима - кликаем на нее
close_button.click(force=True) close_button.click(force=True)
self.wait_for_timeout(300) 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")
@ -150,11 +95,13 @@ class CreateElementFrame(BaseComponent):
def click_add_button(self) -> None: def click_add_button(self) -> None:
"""Кликает на кнопку 'Добавить'.""" """Кликает на кнопку 'Добавить'."""
logger.debug("Clicking on 'Add' button...") logger.debug("Clicking on 'Add' button...")
self.toolbar.click_button("add") self.toolbar.click_button("add")
def click_cancel_button(self) -> None: def click_cancel_button(self) -> None:
"""Кликает на кнопку 'Отменить'.""" """Кликает на кнопку 'Отменить'."""
logger.debug("Clicking on 'Cancel' button...") logger.debug("Clicking on 'Cancel' button...")
self.toolbar.click_button("cancel") self.toolbar.click_button("cancel")
@ -165,6 +112,7 @@ class CreateElementFrame(BaseComponent):
Returns: Returns:
str: Выбранный класс объекта или пустая строка если ничего не выбрано str: Выбранный класс объекта или пустая строка если ничего не выбрано
""" """
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: def is_field_filled(self, field_name: str, container_locator: Locator = None) -> bool:
@ -178,28 +126,38 @@ class CreateElementFrame(BaseComponent):
Returns: Returns:
bool: True если поле заполнено, False в противном случае bool: True если поле заполнено, False в противном случае
""" """
logger.debug(f"Checking if field '{field_name}' is filled...") logger.debug(f"Checking if field '{field_name}' is filled...")
# Если контейнер не передан, используем контейнер по умолчанию
if container_locator is None: if container_locator is None:
container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER).nth(1) container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
# Получаем словарь всех полей формы
fields_locators = self.get_input_fields_locators(container_locator) fields_locators = self.get_input_fields_locators(container_locator)
if field_name not in fields_locators: if field_name not in fields_locators:
logger.debug(f"Field '{field_name}' not found in fields_locators") logger.debug(f"Field '{field_name}' not found in fields_locators")
return False return False
# Получаем контейнер поля
field_container = fields_locators[field_name] field_container = fields_locators[field_name]
if not field_container.is_visible(): if not field_container.is_visible():
logger.debug(f"Field '{field_name}' not visible") logger.debug(f"Field '{field_name}' not visible")
return False return False
# Проверяем наличие выбранного значения через v-chip (чип выбранного значения в combobox)
selected_chip = field_container.locator(".v-chip").first selected_chip = field_container.locator(".v-chip").first
# Проверяем наличие текста в поле
field_text = field_container.text_content() or "" field_text = field_container.text_content() or ""
has_text = bool(field_text.strip()) has_text = bool(field_text.strip())
# Проверяем наличие чипа
has_chip = selected_chip.count() > 0 and selected_chip.is_visible() has_chip = selected_chip.count() > 0 and selected_chip.is_visible()
# Для текстовых полей проверяем значение input
if not has_chip: if not has_chip:
input_field = field_container.locator("input").first input_field = field_container.locator("input").first
if input_field.count() > 0: if input_field.count() > 0:
@ -209,11 +167,13 @@ class CreateElementFrame(BaseComponent):
has_text = has_text or 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}") logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}")
return has_chip or has_text return has_chip or has_text
def open_object_class_combobox(self) -> None: def open_object_class_combobox(self) -> None:
"""Открывает выпадающий список combobox.""" """Открывает выпадающий список combobox."""
container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER)
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER)
fields_locators = self.get_input_fields_locators(container_locator) fields_locators = self.get_input_fields_locators(container_locator)
combobox_container = fields_locators.get("Класс объекта учета") combobox_container = fields_locators.get("Класс объекта учета")
@ -221,10 +181,12 @@ class CreateElementFrame(BaseComponent):
logger.error("Combobox 'Класс объекта учета' not found") logger.error("Combobox 'Класс объекта учета' not found")
return return
# Проверяем, не открыт ли уже выпадающий список
menu_selector = "div.v-menu__content.menuable__content__active" menu_selector = "div.v-menu__content.menuable__content__active"
is_menu_open = self.page.locator(menu_selector).count() > 0 is_menu_open = self.page.locator(menu_selector).count() > 0
if not is_menu_open: if not is_menu_open:
# Используем OPEN_PARAMETERS_LIST_BUTTON из SelectionBarLocators
open_button = combobox_container.locator(SelectionBarLocators.OPEN_PARAMETERS_LIST_BUTTON) open_button = combobox_container.locator(SelectionBarLocators.OPEN_PARAMETERS_LIST_BUTTON)
open_button.click(force=True, timeout=5000) open_button.click(force=True, timeout=5000)
else: else:
@ -237,10 +199,18 @@ class CreateElementFrame(BaseComponent):
Args: Args:
class_name (str): Название класса объекта для выбора class_name (str): Название класса объекта для выбора
""" """
logger.debug(f"Selecting object class: '{class_name}'...") logger.debug(f"Selecting object class: '{class_name}'...")
# Открываем combobox
self.open_object_class_combobox() self.open_object_class_combobox()
# Выбирает значение из списка
self.selection_bar.select_value(class_name) self.selection_bar.select_value(class_name)
# Даем время на применение выбора
self.wait_for_timeout(300) self.wait_for_timeout(300)
logger.debug(f"Object class '{class_name}' successfully selected") logger.debug(f"Object class '{class_name}' successfully selected")
# Проверки: # Проверки:
@ -256,10 +226,30 @@ class CreateElementFrame(BaseComponent):
ValueError: Если поле не найдено в форме ValueError: Если поле не найдено в форме
AssertionError: Если поле не подсвечено ошибкой AssertionError: Если поле не подсвечено ошибкой
""" """
logger.debug(f"Checking field '{field_name}' for error highlighting...") logger.debug(f"Checking field '{field_name}' for error highlighting...")
assert self.is_field_highlighted_as_error(field_name), (
f"Field '{field_name}' is not highlighted as error" # Получаем контейнеры всех полей
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER)
fields_locators = self.get_input_fields_locators(container_locator)
# Получаем контейнер конкретного поля
field_container = fields_locators.get(field_name)
if not field_container:
raise ValueError(f"Field '{field_name}' not found in form")
# Ищем элементы с классами ошибки внутри контейнера поля
error_elements = field_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS)
# Проверяем, что есть хотя бы один элемент с классом ошибки
has_error = error_elements.count() > 0
assert has_error, (
f"Field '{field_name}' has no elements with error classes. "
f"Expected to find elements matching: {SelectionBarLocators.ERROR_CSS_SELECTORS}"
) )
logger.debug(f"Field '{field_name}' is correctly highlighted with error color") logger.debug(f"Field '{field_name}' is correctly highlighted with error color")
def check_field_error_not_highlighted(self, field_name: str) -> None: def check_field_error_not_highlighted(self, field_name: str) -> None:
@ -273,10 +263,30 @@ class CreateElementFrame(BaseComponent):
ValueError: Если поле не найдено в форме ValueError: Если поле не найдено в форме
AssertionError: Если поле подсвечено ошибкой 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...")
assert not self.is_field_highlighted_as_error(field_name), (
f"Field '{field_name}' is incorrectly highlighted as error" # Получаем контейнеры всех полей
container_locator = self.page.locator(RackLocators.FORM_INPUT_CONTAINER)
fields_locators = self.get_input_fields_locators(container_locator)
# Получаем контейнер конкретного поля
field_container = fields_locators.get(field_name)
if not field_container:
raise ValueError(f"Field '{field_name}' not found in form")
# Ищем элементы с классами ошибки внутри контейнера поля
error_elements = field_container.locator(SelectionBarLocators.ERROR_CSS_SELECTORS)
# Проверяем, что нет элементов с классами ошибки
has_error = error_elements.count() > 0
assert not has_error, (
f"Field '{field_name}' has {error_elements.count()} elements with error classes. "
f"Expected no elements matching: {SelectionBarLocators.ERROR_CSS_SELECTORS}"
) )
logger.debug(f"Field '{field_name}' correctly has no error highlighting") logger.debug(f"Field '{field_name}' correctly has no error highlighting")
def check_object_class_selected(self, expected_class: str) -> None: def check_object_class_selected(self, expected_class: str) -> None:
@ -289,6 +299,7 @@ class CreateElementFrame(BaseComponent):
Raises: Raises:
AssertionError: Если выбранный класс не соответствует ожидаемому AssertionError: Если выбранный класс не соответствует ожидаемому
""" """
logger.debug(f"Checking selected object class: '{expected_class}'...") logger.debug(f"Checking selected object class: '{expected_class}'...")
self.wait_for_timeout(500) self.wait_for_timeout(500)
@ -302,7 +313,10 @@ class CreateElementFrame(BaseComponent):
f"Expected: '{expected_class}', Got: '{actual_class}'" f"Expected: '{expected_class}', Got: '{actual_class}'"
) )
logger.debug(f"Object class '{expected_class}' successfully selected (actual: '{actual_class}')") logger.debug(
f"Object class '{expected_class}' successfully selected "
f"(actual: '{actual_class}')"
)
def check_toolbar_title(self, expected_title: str) -> None: def check_toolbar_title(self, expected_title: str) -> None:
""" """
@ -314,14 +328,17 @@ class CreateElementFrame(BaseComponent):
Raises: Raises:
AssertionError: Если заголовок не соответствует ожидаемому AssertionError: Если заголовок не соответствует ожидаемому
""" """
logger.debug(f"Checking toolbar title: '{expected_title}'...") logger.debug(f"Checking toolbar title: '{expected_title}'...")
# Используем метод тулбара с фильтрацией по тексту
actual_text = self.toolbar.get_toolbar_title_text( actual_text = self.toolbar.get_toolbar_title_text(
filter_text="Создать дочерний элемент в" filter_text="Создать дочерний элемент в"
) )
assert expected_title in actual_text, ( assert expected_title in actual_text, (
f"Title does not match. Expected: '{expected_title}', Got: '{actual_text}'" f"Title does not match. Expected: '{expected_title}', "
f"Got: '{actual_text}'"
) )
logger.debug(f"Toolbar title is correct: '{actual_text}'") logger.debug(f"Toolbar title is correct: '{actual_text}'")
@ -330,6 +347,7 @@ class CreateElementFrame(BaseComponent):
""" """
Проверяет наличие и функциональность кнопок тулбара. Проверяет наличие и функциональность кнопок тулбара.
""" """
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")

View File

@ -1,93 +0,0 @@
"""Модуль контейнера для импорта сертификата во вкладке 'Сертификаты'.
Содержит класс для работы с формой для импорта
сертификата во вкладке 'Сертификаты' через Playwright.
"""
from playwright.sync_api import Page, expect
from tools.logger import get_logger
from locators.certificate_locators import CertificateLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.tooltip_button_element import TooltipButton
from components.toolbar_custom_component import CustomToolbar
from components.base_component import BaseComponent
logger = get_logger("IMPORT_CRTIFICATE_FORM")
class ImportCertificateForm(BaseComponent):
"""Компонент формы для импорта сертификата во вкладке 'Сертификаты'.
Предоставляет методы для взаимодействия с элементами
формы для импорта сертификата во вкладке 'Сертификаты'.
"""
def __init__(self, page: Page):
"""Инициализирует компонент формы для импорта сертификата во вкладке 'Сертификаты'.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
import_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Импорт CA (P12)')
self.import_title = Text(page, import_title_locator, "import_title")
button_locator = page.locator(CertificateLocators.BUTTON_IMPORT)
self.button_import = TooltipButton(page, button_locator, "button_import")
self.toolbar_info = CustomToolbar(page)
self.password_input = TextInput(page, CertificateLocators.FIELD_INPUT_PASSWORD,
"password_input_field")
# Действия:
def get_password_field_value(self) -> str:
"""Возвращает текущее значение поля 'Пароль'.
Returns:
str : Текущее значение поля 'Пароль.
"""
return self.password_input.get_input_value().strip()
def input_password_field(self, value: str) -> None:
"""Заполнение поля 'Пароль'"""
self.password_input.clear()
self.password_input.input_value(value)
def _get_label_for_input_field(self, field_locator: str) -> str:
div_loc = f"//div[contains(@class, 'flex')][.{field_locator}]"
label = self.page.locator(div_loc).locator("//preceding-sibling::div[1]").locator("//input")
return label.input_value()
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов формы."""
self.import_title.check_visibility("Title 'Импорт CA (P12)' is missing")
self.button_import.check_visibility("Import certificate button is missing")
assert self.button_import.is_disabled(), "Import certificate button should be disabled"
self.button_import.check_tooltip_with_text("Импорт сертификата (CA)")
# Проверка информационного тулбара
self.toolbar_info.check_toolbar_presence(['Создание нового сертификата',
'Приведет к замене корневого сертификата системы'])
# проверка наличия всех полей формы
password_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_PASSWORD).strip()
assert password_label == 'Пароль', f"Unexpected field name {password_label} has got"
self.password_input.check_visibility("Field password input is missing")
info_loc = self.page.get_by_text("Пароль используется для расшифровки закрытого ключа в файле P12")
expect(info_loc).to_be_visible()
def is_import_button_disabled(self) -> bool:
"""Проверяет наличие и доступность кнопки перевыпуска сертификата."""
self.button_import.check_visibility("Import certificate button is missing")
return self.button_import.is_disabled()

View File

@ -0,0 +1,78 @@
"""Модуль interactive_dropdown_list_component содержит класс для работы с интерактивными выпадающими списками,
позволяющими сделать выбор нескольких элементов.
Класс InteractiveDropdownList наследует базовый функционал BaseComponent и добавляет
методы для взаимодействия с интерактивными выпадающими списками на странице.
"""
from playwright.sync_api import Page, Locator, expect
from tools.logger import get_logger
from components.base_component import BaseComponent
logger = get_logger("INTERACTIVE_DROPDOWN_LIST")
class InteractiveDropdownList(BaseComponent):
"""Класс для работы с выпадающими списками.
Наследует функциональность BaseElement и добавляет специфичные
методы для выбора и проверки элементов списка.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компонент интерактивного выпадающего списка.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
# Действия:
def get_checkbox_locator(self, text: str) -> Locator:
"""Возвращает локатор чек-бокса для элемента списка с указанным текстом.
Args:
text (str): Текст элемента для выбора.
"""
checkbox_locator = self.get_locator('div.v-list__tile__title').get_by_text(text). \
locator("../..").locator("//input[@role='checkbox']")
expect(checkbox_locator).to_be_visible(), \
f"Checkbox for dropdown list item with text {text} is missing"
return checkbox_locator
def deselect_item_with_text(self, text: str) -> None:
"""Выбирает элемент списка по указанному тексту.
Args:
text (str): Текст элемента для выбора.
"""
self.get_checkbox_locator(text).uncheck(force=True)
def select_item_with_text(self, text: str) -> None:
"""Выбирает элемент списка по указанному тексту.
Args:
text (str): Текст элемента для выбора.
"""
self.get_checkbox_locator(text).check(force=True)
def get_selected_items(self, locator: str|Locator) -> list[str]:
"""Возвращает список отмеченных элементов."""
selected_items = []
list_locator = self.get_locator(locator)
items = list_locator.get_by_role("listitem").all()
for item in items:
if item.get_by_role("checkbox").is_checked():
item_text = item.text_content().strip()
if item_text:
selected_items.append(item_text)
return selected_items
# Проверки:

View File

@ -49,12 +49,11 @@ class AddUserModalWindow(ModalWindowComponent):
self.add_toolbar_button(locator_button_toolbar_close, "close") self.add_toolbar_button(locator_button_toolbar_close, "close")
elements_locators = self.get_input_fields_locators(page.locator(input_form_locator)) elements_locators = self.get_input_fields_locators(page.locator(input_form_locator))
# print(elements_locators)
# Поле Тип авторизации # Поле Тип авторизации
auth_type_loc = elements_locators.get("Тип авторизации") loc = elements_locators.get("Тип авторизации")
if auth_type_loc: if loc:
auth_type_selector = SelectionBarComponent(page, auth_type_loc.get_by_role("combobox").first) auth_type_selector = SelectionBarComponent(page, loc.get_by_role("combobox"))
self.add_content_item("auth_type_selector", auth_type_selector) self.add_content_item("auth_type_selector", auth_type_selector)
# Поле Имя # Поле Имя
@ -353,12 +352,13 @@ class AddUserModalWindow(ModalWindowComponent):
Форма для создания keycloack пользователя имеет тот же набор полей. Форма для создания keycloack пользователя имеет тот же набор полей.
""" """
expected_auth_types = ['local', 'LDAP', 'keycloak'] expected_auth_types = ['local', 'LDAP', 'keycloack']
expected_roles = ['$collector', 'Администратор', expected_roles = ['$collector', 'Администратор',
'Специалист информационной безопасности', 'Специалист информационной безопасности',
'Контактное лицо', 'Оператор'] 'Контактное лицо', 'Оператор']
menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM) menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM)
items_locator = menu_locator.locator(ModalWindowLocators.MENU_ACTIVE_ITEMS)
self.check_by_window_title() self.check_by_window_title()
@ -387,7 +387,7 @@ class AddUserModalWindow(ModalWindowComponent):
continue continue
assert current_auth_type == 'local', "Default Auth Type value should be 'local'" assert current_auth_type == 'local', "Default Auth Type value should be 'local'"
actual_auth_types = item.get_available_options() actual_auth_types = item.get_available_options(items_locator)
assert actual_auth_types == expected_auth_types, \ assert actual_auth_types == expected_auth_types, \
f"Actual auth types {actual_auth_types} are not equal expected values {expected_auth_types}." f"Actual auth types {actual_auth_types} are not equal expected values {expected_auth_types}."
elif name == "role_input": elif name == "role_input":

View File

@ -47,7 +47,7 @@ class ChangePasswordModalWindow(ModalWindowComponent):
"old password hidden icon") "old password hidden icon")
self.add_content_item("old_password_hidden_icon", old_password_hidden_icon) self.add_content_item("old_password_hidden_icon", old_password_hidden_icon)
loc = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_NEW_PASSWORD) loc = loc = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_NEW_PASSWORD)
new_password_input = TextInput(page, loc, "new_password_input") new_password_input = TextInput(page, loc, "new_password_input")
self.add_content_item("new_password_input", new_password_input) self.add_content_item("new_password_input", new_password_input)
@ -56,7 +56,7 @@ class ChangePasswordModalWindow(ModalWindowComponent):
"new password hidden icon") "new password hidden icon")
self.add_content_item("new_password_hidden_icon", new_password_hidden_icon) self.add_content_item("new_password_hidden_icon", new_password_hidden_icon)
loc = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_CHECK_PASSWORD) loc = loc = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_CHECK_PASSWORD)
confirm_password_input = TextInput(page, loc, "confirm_password_input") confirm_password_input = TextInput(page, loc, "confirm_password_input")
self.add_content_item("confirm_password_input", confirm_password_input) self.add_content_item("confirm_password_input", confirm_password_input)
@ -71,10 +71,10 @@ class ChangePasswordModalWindow(ModalWindowComponent):
self.add_content_item("input_form_error_message", input_form_error_message) self.add_content_item("input_form_error_message", input_form_error_message)
# Добавление кнопок действий # Добавление кнопок действий
locator_button_save = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_BUTTON_SAVE) locator_button_save = self.page.get_by_role("button", name="Сохранить")
self.add_button(locator_button_save, "save") self.add_button(locator_button_save, "save")
locator_button_cancel = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_BUTTON_CANCEL) locator_button_cancel = self.page.get_by_role("button", name="Отменить")
self.add_button(locator_button_cancel, "cancel") self.add_button(locator_button_cancel, "cancel")
# Alert при успешном добавлении пользователя # Alert при успешном добавлении пользователя

View File

@ -1,32 +1,63 @@
# makers/edit_rack_maker.py
"""Модуль для работы с модальным окном редактирования стойки.""" """Модуль для работы с модальным окном редактирования стойки."""
import re import re
from dataclasses import dataclass
from typing import Optional, List, Tuple, Any from typing import Optional, List, Tuple, Any
from playwright.sync_api import Page from playwright.sync_api import Page
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 elements.text_input_element import TextInput
from elements.text_element import Text
from elements.checkbox_element import Checkbox
from components.modal_window_component import ModalWindowComponent from components.modal_window_component import ModalWindowComponent
from components.dropdown_list_component import DropdownList from components.dropdown_list_component import DropdownList
from components.confirm_component import ConfirmComponent from components.confirm_component import ConfirmComponent
from elements.text_input_element import TextInput
from forms.edit_rack_form import EditRackForm, EditRackData
logger = get_logger("EDIT_RACK_MAKER") logger = get_logger("MODAL_EDIT_RACK")
logger.setLevel("INFO") logger.setLevel("INFO")
@dataclass
class RackEditData:
"""Класс для хранения данных редактирования стойки.
# Re-export EditRackData for backward compatibility Содержит все возможные поля, которые могут быть изменены
EditRackData = EditRackData в модальном окне редактирования стойки.
__all__ = ['EditRackMaker', 'EditRackData'] """
class EditRackMaker(ModalWindowComponent): # Основные поля (редактируемые)
name: str = ""
serial: str = ""
inventory: str = ""
comment: str = ""
allocated_power: str = ""
# Combobox поля (редактируемые)
cable_entry: str = ""
state: str = ""
depth: str = ""
usize: str = ""
owner: str = ""
service_org: str = ""
project: str = ""
# Checkbox поля (редактируемые)
ventilation_panel: Optional[bool] = None
# Правила доступа
read_access_rules: str = ""
write_access_rules: str = ""
sms_access_rules: str = ""
email_access_rules: str = ""
push_access_rules: str = ""
class ModalEditRack(ModalWindowComponent):
"""Компонент для работы с модальным окном редактирования стойки. """Компонент для работы с модальным окном редактирования стойки.
Предоставляет методы для взаимодействия с элементами окна: Предоставляет методы для взаимодействия с элементами окна:
- переключение между вкладками - переключение между вкладками
- заполнение полей общей информации (через EditRackForm) - заполнение полей общей информации
- работа с изображениями - работа с изображениями
- настройка правил доступа - настройка правил доступа
- сохранение/отмена изменений - сохранение/отмена изменений
@ -38,6 +69,46 @@ class EditRackMaker(ModalWindowComponent):
TAB_IMAGE = "Изображение" TAB_IMAGE = "Изображение"
TAB_SETTINGS = "Настройки" TAB_SETTINGS = "Настройки"
# Маппинг полей для заполнения текстовых полей
TEXT_FIELDS_MAPPING = {
"Имя": ("name", "name_input"),
"Комментарий": ("comment", "comment_input"),
"Серийный номер": ("serial", "serial_input"),
"Инвентарный номер": ("inventory", "inventory_input"),
"Выделенная мощность (Вт/ВА)": ("allocated_power", "power_input"),
}
# Маппинг полей для заполнения combobox полей
COMBOBOX_FIELDS_MAPPING = {
"Ввод кабеля": ("cable_entry", "cable_entry_input", "cable_entry_list"),
"Состояние": ("state", "state_input", "state_list"),
"Глубина (мм)": ("depth", "depth_input", "depth_list"),
"Высота в юнитах": ("usize", "usize_input", "usize_list"),
"Владелец": ("owner", "owner_input", "owner_list"),
"Обслуживающая организация": ("service_org", "service_input", "service_list"),
"Проект/Титул": ("project", "project_input", "project_list")
}
# Локаторы для текстовых полей (из RackLocators)
TEXT_FIELDS_LOCATORS = {
"Имя": RackLocators.INPUT_FORM_RACK_DATA_FIELD_NAME,
"Комментарий": RackLocators.INPUT_FORM_RACK_DATA_FIELD_COMMENT,
"Серийный номер": RackLocators.INPUT_FORM_RACK_DATA_FIELD_SERIAL,
"Инвентарный номер": RackLocators.INPUT_FORM_RACK_DATA_FIELD_INVENTORY,
"Выделенная мощность (Вт/ВА)": RackLocators.INPUT_FORM_RACK_DATA_FIELD_POWER,
}
# Локаторы для combobox полей (из RackLocators)
COMBOBOX_FIELDS_LOCATORS = {
"Ввод кабеля": RackLocators.INPUT_FORM_RACK_DATA_FIELD_CABLE_ENTRY,
"Состояние": RackLocators.INPUT_FORM_RACK_DATA_FIELD_CONDITION_TYPE,
"Глубина (мм)": RackLocators.INPUT_FORM_RACK_DATA_FIELD_DEPTH,
"Высота в юнитах": RackLocators.INPUT_FORM_RACK_DATA_FIELD_USIZE,
"Владелец": RackLocators.INPUT_FORM_RACK_DATA_FIELD_OWNER,
"Обслуживающая организация": RackLocators.INPUT_FORM_RACK_DATA_FIELD_SERVICE_PROVIDER,
"Проект/Титул": RackLocators.INPUT_FORM_RACK_DATA_FIELD_PROJECT,
}
# Маппинг полей для вкладки "Настройки" # Маппинг полей для вкладки "Настройки"
ACCESS_RULES_MAPPING = { ACCESS_RULES_MAPPING = {
"Правила доступа для чтения": ( "Правила доступа для чтения": (
@ -57,7 +128,7 @@ class EditRackMaker(ModalWindowComponent):
), ),
} }
# Локаторы для полей правил доступа # Локаторы для полей правил доступа (из RackLocators)
ACCESS_RULES_LOCATORS = { ACCESS_RULES_LOCATORS = {
"Правила доступа для чтения": RackLocators.SETTINGS_READ_RULES, "Правила доступа для чтения": RackLocators.SETTINGS_READ_RULES,
"Правила доступа для записи": RackLocators.SETTINGS_WRITE_RULES, "Правила доступа для записи": RackLocators.SETTINGS_WRITE_RULES,
@ -77,11 +148,11 @@ class EditRackMaker(ModalWindowComponent):
super().__init__(page) super().__init__(page)
self.rack_name = rack_name self.rack_name = rack_name
self.page = page self.page = page
self.available_fields = None
self.active_tab = self.TAB_GENERAL self.active_tab = self.TAB_GENERAL
self.tabs = {} self.tabs = {}
self.content_items = {} self.content_items = {}
self.delete_confirm = None self.delete_confirm = None
self.edit_form = None
# Настройка заголовка и кнопки закрытия # Настройка заголовка и кнопки закрытия
self.window_title = rack_name self.window_title = rack_name
@ -127,11 +198,101 @@ class EditRackMaker(ModalWindowComponent):
def _init_general_tab_content(self) -> None: def _init_general_tab_content(self) -> None:
"""Инициализирует содержимое вкладки 'Общая информация'.""" """Инициализирует содержимое вкладки 'Общая информация'."""
# Инициализируем форму редактирования # Получаем доступные поля формы с помощью базового метода
self.edit_form = EditRackForm(self.page) self.available_fields = self.get_input_fields_locators(
# Копируем content_items из формы self.page.locator(RackLocators.INPUT_FORM_RACK_DATA))
self.content_items.update(self.edit_form.content_items)
logger.debug("General tab content initialized via EditRackForm") self._init_text_fields()
self._init_combobox_fields()
self._init_checkbox_fields()
def _init_text_fields(self) -> None:
"""Инициализирует текстовые поля формы."""
for field_label, (_, widget_name) in self.TEXT_FIELDS_MAPPING.items():
locator = self.TEXT_FIELDS_LOCATORS.get(field_label)
if not locator:
continue
self._init_single_text_field(field_label, locator, widget_name)
def _init_single_text_field(self, field_label: str, locator: str, widget_name: str) -> None:
"""Инициализирует одно текстовое поле.
Args:
field_label: Метка поля.
locator: Локатор поля.
widget_name: Имя виджета.
"""
try:
element = self.page.locator(locator).first
if element.count() > 0 and element.is_visible():
field_input = TextInput(self.page, element, widget_name)
self.add_content_item(widget_name, field_input)
logger.debug(f"Initialized text field: '{field_label}'")
except Exception as e:
logger.error(f"Error initializing text field '{field_label}': {e}")
def _init_combobox_fields(self) -> None:
"""Инициализирует combobox поля формы."""
for field_label, (_, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items():
locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_label)
if not locator:
continue
self._init_single_combobox_field(field_label, locator, input_name, list_name)
def _init_single_combobox_field(
self, field_label: str, locator: str, input_name: str, list_name: str
) -> None:
"""Инициализирует одно combobox поле.
Args:
field_label: Метка поля.
locator: Локатор поля.
input_name: Имя поля ввода.
list_name: Имя списка.
"""
try:
element = self.page.locator(locator).first
if element.count() > 0 and element.is_visible():
field_input = TextInput(self.page, element, input_name)
self.add_content_item(input_name, field_input)
self.add_content_item(list_name, DropdownList(self.page))
logger.debug(f"Initialized combobox field: '{field_label}'")
except Exception as e:
logger.error(f"Error initializing combobox field '{field_label}': {e}")
def _init_checkbox_fields(self) -> None:
"""Инициализирует checkbox поля формы."""
try:
self._init_ventilation_checkbox()
except Exception as e:
logger.error(f"Error initializing checkbox: {e}")
def _init_ventilation_checkbox(self) -> None:
"""Инициализирует чекбокс вентиляционной панели."""
checkbox_input = self.page.locator(
RackLocators.INPUT_FORM_RACK_DATA_CHECKBOX_VENTILATION
).first
if checkbox_input.count() == 0:
return
checkbox = Checkbox(self.page, checkbox_input, "ventilation_panel")
self.add_content_item("ventilation_checkbox", checkbox)
label_locator = self.page.locator("label:has-text('Вентиляционная панель')").first
if label_locator.count() > 0:
label_text = Text(self.page, label_locator, "ventilation_checkbox_label")
self.add_content_item("ventilation_checkbox_label", label_text)
logger.debug("Initialized ventilation panel checkbox")
def _init_image_tab_content(self) -> None: def _init_image_tab_content(self) -> None:
"""Инициализирует содержимое вкладки 'Изображение'.""" """Инициализирует содержимое вкладки 'Изображение'."""
@ -210,82 +371,6 @@ class EditRackMaker(ModalWindowComponent):
self.add_button(self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON), "cancel") self.add_button(self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON), "cancel")
self.add_button(self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON), "delete") self.add_button(self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON), "delete")
# Делегирование методов форме редактирования
def fill_rack_data(self, rack_data: EditRackData) -> dict:
"""Заполняет поля формы редактирования стойки.
Args:
rack_data: Данные для заполнения.
Returns:
Словарь с результатами заполнения.
"""
if self.active_tab != self.TAB_GENERAL:
self.switch_to_tab(self.TAB_GENERAL)
if not self.edit_form:
logger.error("Edit form not initialized")
return {
"text_fields_filled": 0,
"combobox_fields_filled": 0,
"checkboxes_set": 0
}
results = self.edit_form.fill_rack_data(rack_data)
logger.info(f"Filled rack data via EditRackForm: {results}")
return results
def clear_field(self, field_name: str) -> None:
"""Очищает указанное поле формы.
Args:
field_name: Название поля для очистки.
"""
if self.edit_form:
self.edit_form.clear_field(field_name)
def get_field_value(self, field_name: str) -> Optional[str]:
"""Получает значение поля формы.
Args:
field_name: Название поля.
Returns:
Значение поля или None если поле не найдено.
"""
if self.edit_form:
return self.edit_form.get_field_value(field_name)
return None
def is_field_highlighted_as_error(self, field_name: str) -> bool:
"""Проверяет, подсвечено ли поле как ошибочное.
Args:
field_name: Название поля для проверки.
Returns:
bool: True если поле подсвечено ошибкой.
"""
if self.edit_form:
return self.edit_form.is_field_highlighted_as_error(field_name)
return False
def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool:
"""Ожидает появления подсветки ошибки на поле.
Args:
field_name: Название поля.
timeout: Таймаут в миллисекундах.
Returns:
bool: True если ошибка появилась.
"""
if self.edit_form:
return self.edit_form.wait_for_field_error(field_name, timeout)
return False
# Действия с вкладками # Действия с вкладками
def switch_to_tab(self, tab_name: str) -> None: def switch_to_tab(self, tab_name: str) -> None:
"""Переключается на указанную вкладку. """Переключается на указанную вкладку.
@ -424,7 +509,11 @@ class EditRackMaker(ModalWindowComponent):
target_fields: Список целевых полей для заполнения. target_fields: Список целевых полей для заполнения.
Returns: Returns:
Словарь с результатами заполнения. Словарь с результатами заполнения:
- access_rules_filled: количество добавленных пользователей
- errors: список ошибок
- fields_processed: обработанные поля
- field_stats: статистика по каждому полю
""" """
if self.active_tab != self.TAB_SETTINGS: if self.active_tab != self.TAB_SETTINGS:
@ -471,11 +560,11 @@ class EditRackMaker(ModalWindowComponent):
""" """
return [ return [
"admin", "TestUserRulesAdmin",
"manager", "TestUserRulesOper",
"operator", "TestUserRulesManager",
"sec", "TestUserRulesSec",
"collector" "TestUserRulesCollector"
] ]
def _process_single_field( def _process_single_field(
@ -646,52 +735,28 @@ class EditRackMaker(ModalWindowComponent):
username: str, username: str,
field_label: str field_label: str
) -> Tuple[bool, Optional[str]]: ) -> Tuple[bool, Optional[str]]:
"""Добавляет пользователя из выпадающего списка.""" """Добавляет пользователя из выпадающего списка.
Args:
dropdown_menu: Выпадающее меню.
username: Имя пользователя.
field_label: Название поля.
Returns:
Кортеж (добавлен ли пользователь, сообщение об ошибке или None).
"""
try: try:
# Получаем все элементы списка user_item = dropdown_menu.locator(f"[role='listitem']:has-text('{username}')").first
listitem_locator = dropdown_menu.get_by_role("listitem") if user_item.count() == 0:
user_item = dropdown_menu.locator(f"div:has-text('{username}')").first
# Проверяем, что элементы есть if user_item.count() > 0:
if listitem_locator.count() == 0: user_item.click()
return False, f"No list items found in dropdown for {field_label}"
# Прокручиваем к последнему элементу
listitem_locator.last.scroll_into_view_if_needed()
# Ждем, пока последний элемент станет видимым
listitem_locator.last.wait_for(state="visible")
# Ищем элемент с точным совпадением текста
all_items = listitem_locator.all()
target_item = None
for item in all_items:
# Ищем span с текстом внутри элемента
span = item.locator("span").first
if span.inner_text().strip() == username:
target_item = item
break
if target_item is None:
return False, f"User '{username}' not found in dropdown for {field_label}"
# Проверяем, не выбран ли уже этот пользователь
class_attribute = target_item.get_attribute("class") or ""
# Если элемент уже выбран (есть класс v-list__tile--active)
if "v-list__tile--active" in class_attribute:
logger.debug(f"User '{username}' is already selected in {field_label}, skipping")
return True, None # Считаем как успех, т.к. пользователь уже есть
# Прокручиваем к найденному элементу
target_item.scroll_into_view_if_needed()
target_item.wait_for(state="visible")
# Кликаем
target_item.click()
self.wait_for_timeout(500) self.wait_for_timeout(500)
return True, None return True, None
return False, f"User '{username}' not found in dropdown for {field_label}"
except Exception as e: except Exception as e:
return False, f"Failed to add user '{username}' to {field_label}: {str(e)}" return False, f"Failed to add user '{username}' to {field_label}: {str(e)}"
@ -728,7 +793,13 @@ class EditRackMaker(ModalWindowComponent):
target_fields: Список целевых полей для проверки. target_fields: Список целевых полей для проверки.
Returns: Returns:
Словарь с результатами проверки. Словарь с результатами проверки:
- total_expected_fields: общее количество ожидаемых значений
- correctly_filled: количество корректно заполненных
- incorrectly_filled: количество некорректно заполненных
- field_errors: список ошибок по полям
- fields_verified: список проверенных полей
- expected_users: список ожидаемых пользователей
""" """
if self.active_tab != self.TAB_SETTINGS: if self.active_tab != self.TAB_SETTINGS:
@ -949,10 +1020,152 @@ class EditRackMaker(ModalWindowComponent):
save_button.click() save_button.click()
logger.debug("Clicked done button") logger.debug("Clicked done button")
def fill_rack_data(self, rack_data: RackEditData) -> dict:
"""Заполняет поля формы редактирования стойки.
Args:
rack_data: Данные для заполнения.
Returns:
Словарь с результатами заполнения.
"""
if self.active_tab != self.TAB_GENERAL:
self.switch_to_tab(self.TAB_GENERAL)
results = {
"text_fields_filled": 0,
"combobox_fields_filled": 0,
"checkboxes_set": 0
}
self._fill_text_fields(rack_data, results)
self._fill_combobox_fields(rack_data, results)
self._set_checkbox(rack_data, results)
return results
def _fill_text_fields(self, rack_data: RackEditData, results: dict) -> None:
"""Заполняет текстовые поля.
Args:
rack_data: Данные для заполнения.
results: Словарь с результатами для обновления.
"""
for field_label, (attr_name, field_name) in self.TEXT_FIELDS_MAPPING.items():
value = getattr(rack_data, attr_name, "")
if not value or not str(value).strip():
continue
self._fill_single_text_field(field_label, field_name, value, results)
def _fill_single_text_field(
self,
field_label: str,
field_name: str,
value: str,
results: dict
) -> None:
"""Заполняет одно текстовое поле.
Args:
field_label: Метка поля.
field_name: Имя поля.
value: Значение для заполнения.
results: Словарь с результатами.
"""
try:
input_field = self.get_content_item(field_name)
if input_field:
input_field.input_value(value)
results["text_fields_filled"] += 1
logger.info(f"Field '{field_label}' filled: '{value}'")
except Exception as e:
logger.error(f"Error filling field '{field_label}': {e}")
def _fill_combobox_fields(self, rack_data: RackEditData, results: dict) -> None:
"""Заполняет combobox поля.
Args:
rack_data: Данные для заполнения.
results: Словарь с результатами для обновления.
"""
for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items():
value = getattr(rack_data, attr_name, "")
if not value or not str(value).strip():
continue
self._fill_single_combobox_field(
field_label, input_name, list_name, value, results
)
def _fill_single_combobox_field(
self,
field_label: str,
input_name: str,
list_name: str,
value: str,
results: dict
) -> None:
"""Заполняет одно combobox поле.
Args:
field_label: Метка поля.
input_name: Имя поля ввода.
list_name: Имя списка.
value: Значение для выбора.
results: Словарь с результатами.
"""
try:
combobox_field = self.get_content_item(input_name)
if not combobox_field:
return
combobox_field.click(force=True)
self.wait_for_timeout(500)
dropdown_list = self.get_content_item(list_name)
if dropdown_list:
dropdown_list.click_item_with_text(value)
results["combobox_fields_filled"] += 1
logger.info(f"Field '{field_label}' set: '{value}'")
except Exception as e:
logger.error(f"Error filling combobox '{field_label}': {e}")
def _set_checkbox(self, rack_data: RackEditData, results: dict) -> None:
"""Устанавливает чекбокс.
Args:
rack_data: Данные для заполнения.
results: Словарь с результатами для обновления.
"""
if rack_data.ventilation_panel is None:
return
try:
checkbox = self.get_content_item("ventilation_checkbox")
if not checkbox:
return
if rack_data.ventilation_panel:
checkbox.check(force=True)
else:
checkbox.uncheck(force=True)
results["checkboxes_set"] += 1
logger.info(f"Checkbox 'Ventilation panel' set to: {rack_data.ventilation_panel}")
except Exception as e:
logger.error(f"Error setting checkbox: {e}")
# Проверки # Проверки
def verify_all_filled_fields( def verify_all_filled_fields(
self, self,
rack_data: EditRackData, rack_data: RackEditData,
skip_fields: Optional[List[str]] = None skip_fields: Optional[List[str]] = None
) -> dict: ) -> dict:
"""Проверяет, что все поля заполнены корректно. """Проверяет, что все поля заполнены корректно.
@ -980,18 +1193,8 @@ class EditRackMaker(ModalWindowComponent):
if skip_fields is None: if skip_fields is None:
skip_fields = [] skip_fields = []
if not self.edit_form:
logger.error("Edit form not initialized")
results["field_errors"].append("Edit form not initialized")
return results
# Проверяем текстовые поля
self._verify_text_fields(rack_data, skip_fields, results) self._verify_text_fields(rack_data, skip_fields, results)
# Проверяем combobox поля
self._verify_combobox_fields(rack_data, skip_fields, results) self._verify_combobox_fields(rack_data, skip_fields, results)
# Проверяем чекбокс
self._verify_checkbox(rack_data, skip_fields, results) self._verify_checkbox(rack_data, skip_fields, results)
if results["total_expected_fields"] > 0: if results["total_expected_fields"] > 0:
@ -1005,7 +1208,7 @@ class EditRackMaker(ModalWindowComponent):
def _verify_text_fields( def _verify_text_fields(
self, self,
rack_data: EditRackData, rack_data: RackEditData,
skip_fields: List[str], skip_fields: List[str],
results: dict results: dict
) -> None: ) -> None:
@ -1017,7 +1220,7 @@ class EditRackMaker(ModalWindowComponent):
results: Словарь с результатами для обновления. results: Словарь с результатами для обновления.
""" """
for field_label, (attr_name, field_name) in self.edit_form.TEXT_FIELDS_MAPPING.items(): for field_label, (attr_name, field_name) in self.TEXT_FIELDS_MAPPING.items():
expected_value = getattr(rack_data, attr_name, "") expected_value = getattr(rack_data, attr_name, "")
if not expected_value or not str(expected_value).strip(): if not expected_value or not str(expected_value).strip():
continue continue
@ -1047,7 +1250,7 @@ class EditRackMaker(ModalWindowComponent):
""" """
try: try:
input_field = self.edit_form.get_content_item(field_name) input_field = self.get_content_item(field_name)
if not input_field: if not input_field:
results["not_filled"] += 1 results["not_filled"] += 1
results["field_errors"].append(f"Field '{field_label}' input not found") results["field_errors"].append(f"Field '{field_label}' input not found")
@ -1067,7 +1270,7 @@ class EditRackMaker(ModalWindowComponent):
def _verify_combobox_fields( def _verify_combobox_fields(
self, self,
rack_data: EditRackData, rack_data: RackEditData,
skip_fields: List[str], skip_fields: List[str],
results: dict results: dict
) -> None: ) -> None:
@ -1079,7 +1282,7 @@ class EditRackMaker(ModalWindowComponent):
results: Словарь с результатами для обновления. results: Словарь с результатами для обновления.
""" """
for field_label, (attr_name, _, _) in self.edit_form.COMBOBOX_FIELDS_MAPPING.items(): for field_label, (attr_name, _, _) in self.COMBOBOX_FIELDS_MAPPING.items():
expected_value = getattr(rack_data, attr_name, "") expected_value = getattr(rack_data, attr_name, "")
if not expected_value or not str(expected_value).strip(): if not expected_value or not str(expected_value).strip():
continue continue
@ -1107,9 +1310,9 @@ class EditRackMaker(ModalWindowComponent):
""" """
try: try:
actual_value = self.edit_form.get_field_value(field_label) or "" actual_value = self._get_combobox_value(field_label)
actual_clean = actual_value.strip() actual_clean = actual_value.strip() if actual_value else ""
expected_clean = expected_value.strip() expected_clean = expected_value.strip() if expected_value else ""
if actual_clean == expected_clean: if actual_clean == expected_clean:
results["correctly_filled"] += 1 results["correctly_filled"] += 1
@ -1122,9 +1325,39 @@ class EditRackMaker(ModalWindowComponent):
results["not_filled"] += 1 results["not_filled"] += 1
results["field_errors"].append(f"Error checking combobox '{field_label}': {e}") results["field_errors"].append(f"Error checking combobox '{field_label}': {e}")
def _get_combobox_value(self, field_label: str) -> str:
"""Получает значение из combobox поля.
Args:
field_label: Название поля.
Returns:
Значение поля или пустая строка.
"""
actual_value = ""
locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_label)
if not locator:
return actual_value
element = self.page.locator(locator).first
if element.count() == 0:
return actual_value
selections_container = element.locator(
"xpath=ancestor::div[contains(@class, 'v-select__selections')]"
).first
if selections_container.count() > 0:
value_span = selections_container.locator("span").first
actual_value = value_span.text_content() or ""
return actual_value
def _verify_checkbox( def _verify_checkbox(
self, self,
rack_data: EditRackData, rack_data: RackEditData,
skip_fields: List[str], skip_fields: List[str],
results: dict results: dict
) -> None: ) -> None:
@ -1146,7 +1379,7 @@ class EditRackMaker(ModalWindowComponent):
return return
try: try:
checkbox = self.edit_form.get_content_item("ventilation_checkbox") checkbox = self.get_content_item("ventilation_checkbox")
if not checkbox: if not checkbox:
results["not_filled"] += 1 results["not_filled"] += 1
results["field_errors"].append("Checkbox 'Ventilation panel' not found") results["field_errors"].append("Checkbox 'Ventilation panel' not found")

View File

@ -50,13 +50,14 @@ class EditUserModalWindow(ModalWindowComponent):
# Добавление полей формы # Добавление полей формы
elements_locators = self.get_input_fields_locators( elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Поле Имя # Поле Имя
loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
name_input = TextInput(page, loc, "name_input") name_input = TextInput(page, loc, "name_input")
self.add_content_item("name_input", name_input) self.add_content_item("name_input", name_input)
# Поле Роль # Поле Роль
role_loc = elements_locators.get("Роль").get_by_role("combobox").first role_loc = self.page.locator(input_form_locator).get_by_role("combobox").first
role_input = TextInput(page, role_loc, "role_input") role_input = TextInput(page, role_loc, "role_input")
self.add_content_item("role_input", role_input) self.add_content_item("role_input", role_input)
self.add_content_item("roles_list", DropdownList(page)) self.add_content_item("roles_list", DropdownList(page))
@ -212,16 +213,16 @@ class EditUserModalWindow(ModalWindowComponent):
if "blocking_checked" in fields: if "blocking_checked" in fields:
checkbox = self.get_content_item("blocking_checkbox") checkbox = self.get_content_item("blocking_checkbox")
if user_data["blocking_checked"]: if user_data["blocking_checked"]:
checkbox.check(force=True) checkbox.check()
else: else:
checkbox.uncheck(force=True) checkbox.uncheck()
if "push_notification_checked" in fields: if "push_notification_checked" in fields:
checkbox = self.get_content_item("push_notification_checkbox") checkbox = self.get_content_item("push_notification_checkbox")
if user_data["push_notification_checked"]: if user_data["push_notification_checked"]:
checkbox.check(force=True) checkbox.check()
else: else:
checkbox.uncheck(force=True) checkbox.uncheck()
save_button = self.get_button_by_name("save") save_button = self.get_button_by_name("save")
save_button.click() save_button.click()
@ -239,6 +240,16 @@ class EditUserModalWindow(ModalWindowComponent):
reset_password_button = self.get_button_by_name("reset_password") reset_password_button = self.get_button_by_name("reset_password")
reset_password_button.click() reset_password_button.click()
# def _get_fields_locators(self, page) -> dict:
# fields_locators = {}
# elements = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA). \
# locator("div.v-text-field__slot > input").all()
# for el in elements:
# val = el.input_value().strip()
# if val:
# fields_locators[val] = el.locator("../ancestor::div[5]")
# return fields_locators
# Проверки: # Проверки:
def check_content(self, user_name, role): def check_content(self, user_name, role):
"""Проверяет наличие и корректность элементов окна. """Проверяет наличие и корректность элементов окна.

View File

@ -1,121 +0,0 @@
"""Модуль modal_send_test_email содержит класс для работы с модальным окном для посылки тестового E-mail на
базе настроек вкладки 'Уведомления/E-mail'.
Класс SendTestEmailModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы просмотра модального окна отображения задачи.
"""
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.modal_window_locators import ModalWindowLocators
from elements.text_input_element import TextInput
from components.modal_window_component import ModalWindowComponent
from components.alert_component import AlertComponent
logger = get_logger("SEND_TEST_EMAIL_MODAL_WINDOW")
class SendTestEmailModalWindow(ModalWindowComponent):
"""Модальное окно для посылки тестового E-mail.
Наследует ModalWindowComponent и добавляет функционал для:
1. Инициализации модального окна
2. Закрытия модального окна через тулбар
3. Проверки содержимого модального окна
"""
def __init__(self, page: Page):
"""Инициализирует элементы формы модального окна отображения задачи."""
super().__init__(page)
window_locator = page.locator(ModalWindowLocators.MODAL_WINDOW)
self.window_title_locator = window_locator.locator("//div[@class='v-toolbar__title']")
self.add_toolbar_title("Тест")
# Настройка кнопки закрытия
toolbar_button_close_locator = window_locator.locator("//button[@data-testid='E_MAIL_CARD__btn__close']")
self.add_toolbar_button(toolbar_button_close_locator, "close")
# Поле ввода адреса
loc = window_locator.locator("//input[@data-testid='E_MAIL_CARD__text-field_text__email']")
email_input = TextInput(page, loc, "email_input")
self.add_content_item("email_input", email_input)
# Добавление кнопок действий
locator_button_test = window_locator.locator("//button[@data-testid='E_MAIL_CARD__footer_btn__test']")
self.add_button(locator_button_test, "test_button")
locator_button_close = window_locator.locator("//button[@data-testid='E_MAIL_CARD__footer_btn__close']")
self.add_button(locator_button_close, "close_button")
self.alert = AlertComponent(page)
# Действия:
def close_by_toolbar_button(self):
"""Закрывает окно кнопкой на тулбаре."""
self.click_toolbar_close_button()
def close(self):
"""Закрывает окно кнопкой на 'Закрыть'."""
close_button = self.get_button_by_name("close_button")
close_button.click()
def click_test_button(self):
"""Отсылка письма по указанному адресу нажатием кнопки 'Тест'."""
close_button = self.get_button_by_name("test_button")
close_button.click()
def input_email(self, address: str) -> None:
"""Заполнение поля 'E-MAIL'."""
email_input_field = self.get_content_item("email_input")
email_input_field.clear()
email_input_field.input_value(address)
# Проверки:
def check_content(self) -> None:
"""Проверяет наличие элементов окна.
"""
self.check_by_window_title()
self.check_toolbar_button_visibility("close")
self.check_toolbar_button_tooltip("close", "Закрыть")
email_input_field = self.get_content_item("email_input")
email_input_field.check_visibility("E-mail input field is missing")
email_input_field.check_editable_input("E-mail input field should be editable")
self.check_button_visibility("test_button")
self.check_button_visibility("close_button")
def should_be_success_alert(self) -> None:
"""Проверяет наличие сообщения об успешной отправке тестового сообщения.
Raises:
AssertionError: Если тулбар отсутствует.
"""
alert_type = self.alert.get_alert_type()
assert alert_type == "success", f"Expected success alert, but got {alert_type} alert"
self.alert.check_alert_presence('\nТестовое сообщение\nотправлено\n')
self.alert.check_alert_absence('\nТестовое сообщение\nотправлено\n')
def should_be_error_alert(self, alert_text: str) -> None:
"""Проверяет наличие сообщения об неуспешной отправке тестового сообщения.
Raises:
AssertionError: Если тулбар отсутствует.
"""
alert_type = self.alert.get_alert_type()
assert alert_type == "error", f"Expected error alert, but got {alert_type} alert"
self.alert.check_alert_presence(alert_text)
self.alert.check_alert_absence(alert_text)

View File

@ -1,109 +0,0 @@
"""Модуль modal_send_test_sms содержит класс для работы с модальным окном для посылки тестового СМС на
базе настроек вкладки 'Уведомления/СМС'.
Класс SendTestSMSModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы просмотра модального окна отображения задачи.
"""
from playwright.sync_api import Page, expect
from tools.logger import get_logger
from locators.modal_window_locators import ModalWindowLocators
from elements.text_input_element import TextInput
from components.modal_window_component import ModalWindowComponent
# from components.alert_component import AlertComponent
logger = get_logger("SEND_TEST_SMS_MODAL_WINDOW")
class SendTestSMSModalWindow(ModalWindowComponent):
"""Модальное окно для посылки тестового СМС.
Наследует ModalWindowComponent и добавляет функционал для:
1. Инициализации модального окна
2. Закрытия модального окна через тулбар
3. Проверки содержимого модального окна
"""
def __init__(self, page: Page):
"""Инициализирует элементы формы модального окна отображения задачи."""
super().__init__(page)
window_locator = page.locator(ModalWindowLocators.MODAL_WINDOW)
self.window_title_locator = window_locator.locator("//div[@class='v-toolbar__title']")
self.add_toolbar_title("Проверка sms уведомления")
# Настройка кнопки закрытия
toolbar_button_close_locator = window_locator.locator("//button[@data-testid='SMS_TEST_CARD__btn__close']")
self.add_toolbar_button(toolbar_button_close_locator, "close")
# Поле ввода номера телефона
loc = window_locator.locator("//input[@data-testid='SMS_TEST_CARD__text-field_integer__sms_phone']")
sms_phone_input = TextInput(page, loc, "sms_phone_input")
self.add_content_item("sms_phone_input", sms_phone_input)
# Добавление кнопок действий
locator_button_test = window_locator.locator("//button[@data-testid='SMS_TEST_CARD__btn__testSmsSend']")
self.add_button(locator_button_test, "test_button")
# self.alert = AlertComponent(page)
# Действия:
def close_by_toolbar_button(self):
"""Закрывает окно кнопкой на тулбаре."""
self.click_toolbar_close_button()
def click_test_button(self):
"""Отсылка sms по указанному номеру телефона нажатием кнопки 'Тест'."""
close_button = self.get_button_by_name("test_button")
close_button.click()
def get_sms_phone(self) -> str:
"""Возвращает текущее значение поля 'Номер для СМС'."""
sms_phone_input_field = self.get_content_item("sms_phone_input")
return sms_phone_input_field.get_input_value()
def input_sms_phone(self, sms_phone: str) -> None:
"""Заполнение поля 'Номер для СМС'."""
sms_phone_input_field = self.get_content_item("sms_phone_input")
sms_phone_input_field.clear()
sms_phone_input_field.input_value(sms_phone)
# Проверки:
def check_content(self) -> None:
"""Проверяет наличие элементов окна.
"""
self.check_by_window_title()
self.check_toolbar_button_visibility("close")
self.check_toolbar_button_tooltip("close", "Закрыть")
sms_phone_input_field = self.get_content_item("sms_phone_input")
sms_phone_input_field.check_visibility("SMS phone input field is missing")
sms_phone_input_field.check_editable_input("SMS phone input field should be editable")
loc = self.page.locator(ModalWindowLocators.MODAL_WINDOW). \
locator("//input[@data-testid='SMS_TEST_CARD__text-field_integer__sms_phone']")
expect(loc).to_have_attribute("aria-label", "Номер для СМС")
self.check_button_visibility("test_button")
# def should_be_success_alert(self) -> None:
# """Проверяет наличие сообщения об успешной отправке тестового сообщения.
# Raises:
# AssertionError: Если тулбар отсутствует.
# """
# alert_type = self.alert.get_alert_type()
# assert alert_type == "success", f"Expected success alert, but got {alert_type} alert"
# self.alert.check_alert_presence('\nТестовое сообщение\nотправлено\n')
# self.alert.check_alert_absence('\nТестовое сообщение\nотправлено\n')

View File

@ -74,9 +74,3 @@ class ViewTaskModalWindow(ModalWindowComponent):
""" Проверка соответствия заголовка таблицы ожидаемому""" """ Проверка соответствия заголовка таблицы ожидаемому"""
self.task_stages_table.check_table_headers(actual_headers, expected_headers) self.task_stages_table.check_table_headers(actual_headers, expected_headers)
def check_stages_table_row_highlighting(self, row_index: int) -> None:
"""Проверяет выделение указанной строки таблицы.
"""
self.task_stages_table.check_row_highlighting(self.task_stages_table_locator, row_index)

View File

@ -1,172 +0,0 @@
"""Модуль контейнера для пересоздания сертификата во вкладке 'Сертификаты'.
Содержит класс для работы с формой для пересоздания
сертификата во вкладке 'Сертификаты' через Playwright.
"""
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.certificate_locators import CertificateLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.tooltip_button_element import TooltipButton
from components.toolbar_custom_component import CustomToolbar
from components.base_component import BaseComponent
logger = get_logger("REISSUE_CRTIFICATE_FORM")
class ReissueCertificateForm(BaseComponent):
"""Компонент формы для пересоздания сертификата во вкладке 'Сертификаты'.
Предоставляет методы для взаимодействия с элементами
формы для пересоздания сертификата во вкладке 'Сертификаты'.
"""
def __init__(self, page: Page):
"""Инициализирует компонент формы для пересоздания сертификата во вкладке 'Сертификаты'.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
button_locator = page.locator(CertificateLocators.FORM_CONTAINER).get_by_role("button")
self.button_reissue = TooltipButton(page, button_locator, "button_reissue")
self.toolbar_info = CustomToolbar(page)
# поля блока 'Идентификация CA'
identification_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Идентификация CA')
self.identification_title = Text(page, identification_title_locator, "identification_title")
self.identification_cert_name = TextInput(page, CertificateLocators.FIELD_INPUT_CERT_NAME,
"identification_cert_name_field")
self.identification_organization = TextInput(page, CertificateLocators.FIELD_INPUT_ORGANIZATION,
"identification_organization_field")
self.identification_org_unit = TextInput(page, CertificateLocators.FIELD_INPUT_ORG_UNIT,
"identification_org_unit_field")
# поля блока 'Адрес / Местонахождение'
location_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Адрес / Местонахождение')
self.location_title = Text(page, location_title_locator, "location_title")
self.location_country = TextInput(page, CertificateLocators.FIELD_INPUT_COUNTRY, "location_country_field")
self.location_state = TextInput(page, CertificateLocators.FIELD_INPUT_STATE, "location_state_field")
self.location_city = TextInput(page, CertificateLocators.FIELD_INPUT_LOC, "location_city_field")
# Действия:
def get_identification_fields_values(self) -> dict:
"""Возвращает текущее значение полей блока 'Идентификация CA'.
Returns:
dict : Текущее значение полей блока 'Идентификация CA'.
"""
values = {}
values.update({"CN": self.identification_cert_name.get_input_value().strip()})
values.update({"O": self.identification_organization.get_input_value().strip()})
values.update({"OU": self.identification_org_unit.get_input_value().strip()})
return values
def get_location_fields_values(self) -> dict:
"""Возвращает текущее значение полей блока 'Адрес / Местонахождение'.
Returns:
dict : Текущее значение полей блока блока 'Адрес / Местонахождение'.
"""
values = {}
values.update({"C": self.location_country.get_input_value().strip()})
values.update({"ST": self.location_state.get_input_value().strip()})
values.update({"L": self.location_city.get_input_value().strip()})
return values
def input_identification_cert_name_field(self, value: str) -> None:
"""Заполнение поля 'Имя Сертификата' блока 'Идентификация CA'"""
self.identification_cert_name.clear()
self.identification_cert_name.input_value(value)
def input_identification_organization_field(self, value: str) -> None:
"""Заполнение поля 'Организация' блока 'Идентификация CA'"""
self.identification_organization.clear()
self.identification_organization.input_value(value)
def input_identification_org_unit_field(self, value: str) -> None:
"""Заполнение поля 'Подразделение' блока 'Идентификация CA'"""
self.identification_org_unit.clear()
self.identification_org_unit.input_value(value)
def input_location_country_field(self, value: str) -> None:
"""Заполнение поля 'Страна' блока 'Адрес / Местонахождение'"""
self.location_country.clear()
self.location_country.input_value(value)
def input_location_state_field(self, value: str) -> None:
"""Заполнение поля 'Регион / Область' блока 'Адрес / Местонахождение'"""
self.location_state.clear()
self.location_state.input_value(value)
def input_location_city_field(self, value: str) -> None:
"""Заполнение поля 'Город' блока 'Адрес / Местонахождение'"""
self.location_city.clear()
self.location_city.input_value(value)
def _get_label_for_input_field(self, field_locator: str) -> str:
div_loc = f"//div[contains(@class, 'flex')][.{field_locator}]"
label = self.page.locator(div_loc).locator("//preceding-sibling::div[1]").locator("//input")
return label.input_value()
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов формы."""
self.button_reissue.check_visibility("Reissue certificate button is missing")
assert self.button_reissue.is_disabled(), "Reissue certificate button should be disabled"
self.button_reissue.check_tooltip_with_text("Пересоздание сертификата (CA)")
# Проверка информационного тулбара
self.toolbar_info.check_toolbar_presence(['Создание нового сертификата',
'Приведет к замене корневого сертификата системы'])
# проверка наличия всех полей формы
self.identification_title.check_visibility("Title 'Идентификация CA' is missing")
cert_name_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_CERT_NAME).strip()
assert cert_name_label == 'ИМЯ СЕРТИФИКАТА (CN)', f"Unexpected field name {cert_name_label} has got"
self.identification_cert_name.check_visibility("Field certificate name input is missing")
organization_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_ORGANIZATION).strip()
assert organization_label == 'ОРГАНИЗАЦИЯ (О)', f"Unexpected field name {organization_label} has got"
self.identification_organization.check_visibility("Field organization input is missing")
org_unit_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_ORG_UNIT).strip()
assert org_unit_label == 'ПОДРАЗДЕЛЕНИЕ (OU)', f"Unexpected field name {org_unit_label} has got"
self.identification_org_unit.check_visibility("Field organization unit input is missing")
self.location_title.check_visibility("Title 'Адрес / Местонахождение' is missing")
country_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_COUNTRY).strip()
assert country_label == 'СТРАНА (С)', f"Unexpected field name {country_label} has got"
self.location_country.check_visibility("Field country input is missing")
state_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_STATE).strip()
assert state_label == 'РЕГИОН / ОБЛАСТЬ (ST)', f"Unexpected field name {state_label} has got"
self.location_state.check_visibility("Field state input is missing")
city_label = self._get_label_for_input_field(CertificateLocators.FIELD_INPUT_LOC).strip()
assert city_label == 'ГОРОД (l)', f"Unexpected field name {city_label} has got"
self.location_city.check_visibility("Field city input is missing")
def is_reissue_button_disabled(self) -> bool:
"""Проверяет доступность кнопки перевыпуска сертификата."""
return self.button_reissue.is_disabled()

View File

@ -44,7 +44,6 @@ class SelectionBarComponent(BaseComponent):
# При нажатии на панель появляется выпадающий список с параметрами фильтрации для выбора # При нажатии на панель появляется выпадающий список с параметрами фильтрации для выбора
self.selected_values_list = DropdownList(self.page) self.selected_values_list = DropdownList(self.page)
print(self.selection_bar_locator)
# Действия: # Действия:
def clear_selections(self) -> None: def clear_selections(self) -> None:
@ -55,10 +54,6 @@ class SelectionBarComponent(BaseComponent):
clear_button_locator = self.selection_bar_locator.locator( clear_button_locator = self.selection_bar_locator.locator(
SelectionBarLocators.CLEAR_SELECTION_BUTTON SelectionBarLocators.CLEAR_SELECTION_BUTTON
) )
if clear_button_locator.count() == 0:
clear_button_locator = self.selection_bar_locator.locator("../..").locator(
SelectionBarLocators.CLEAR_SELECTION_BUTTON
)
clear_button_locator.click() clear_button_locator.click()
def get_available_options(self) -> list[str]: def get_available_options(self) -> list[str]:
@ -91,13 +86,8 @@ class SelectionBarComponent(BaseComponent):
def get_selection_bar_title(self) -> str: def get_selection_bar_title(self) -> str:
"""Возвращает название панели выбора значения""" """Возвращает название панели выбора значения"""
title_text = ""
title_locator = self.selection_bar_locator.locator(SelectionBarLocators.TITLE_LOCATOR) title_locator = self.selection_bar_locator.locator(SelectionBarLocators.TITLE_LOCATOR)
if title_locator.count() > 0: return title_locator.text_content()
title_text = title_locator.text_content()
else:
title_text = self.selection_bar_locator.get_attribute("placeholder")
return title_text
def get_selected_values(self) -> list[str]: def get_selected_values(self) -> list[str]:
"""Возвращает список выбранных значений""" """Возвращает список выбранных значений"""
@ -105,11 +95,6 @@ class SelectionBarComponent(BaseComponent):
selected_values_locator = self.selection_bar_locator.locator( selected_values_locator = self.selection_bar_locator.locator(
SelectionBarLocators.PARAMETERS_SELECTED SelectionBarLocators.PARAMETERS_SELECTED
) )
if selected_values_locator.count() == 0:
selected_values_locator = self.selection_bar_locator.locator("../..").locator(
SelectionBarLocators.PARAMETERS_SELECTED
)
print(selected_values_locator)
selected_values = selected_values_locator.all_inner_texts() selected_values = selected_values_locator.all_inner_texts()
return selected_values[0].splitlines() return selected_values[0].splitlines()
@ -167,16 +152,7 @@ class SelectionBarComponent(BaseComponent):
self.selection_bar_locator.click(force=True) self.selection_bar_locator.click(force=True)
# Ждем появления выпадающего списка # Ждем появления выпадающего списка
if self.page.locator(SelectionBarLocators.LIST_ACTIVE).count() == 0: self.wait_for_timeout(1500)
assert False, "Values list is empty"
if self.page.locator(SelectionBarLocators.LIST_ACTIVE).count() > 1:
self.page.locator(SelectionBarLocators.LIST_ACTIVE).last.wait_for(state="attached")
else:
self.page.locator(SelectionBarLocators.LIST_ACTIVE).wait_for(state="attached")
#self.page.locator(SelectionBarLocators.LIST_ACTIVE).wait_for(state="attached")
# self.wait_for_timeout(1500)
def select_value(self, name: str) -> None: def select_value(self, name: str) -> None:
"""Выбор значения из списка""" """Выбор значения из списка"""
@ -185,6 +161,7 @@ class SelectionBarComponent(BaseComponent):
self.selected_values_list.click_item_with_text(name) self.selected_values_list.click_item_with_text(name)
# Проверки: # Проверки:
def check_field_error_highlighted(self, field_name: str, field_locator: str) -> None: def check_field_error_highlighted(self, field_name: str, field_locator: str) -> None:
"""Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). """Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
@ -236,40 +213,3 @@ class SelectionBarComponent(BaseComponent):
assert not has_error, f"Field '{field_name}' is highlighted with error" assert not has_error, 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")
def check_field_visibility(self, msg: str) -> None:
"""Проверка видимости элемента на странице.
Args:
msg: сообщение об ошибке при неудачной проверке.
Raises:
AssertionError: если элемент не виден на странице.
"""
self.check_visibility(self.selection_bar_locator, msg)
def should_be_clear_selection_button(self) -> None:
"""Проверяет наличие кнопки отмены выбранного значения.
Raises:
AssertionError: Если кнопка отсутствует.
"""
clear_button_locator = self.selection_bar_locator.locator(
SelectionBarLocators.CLEAR_SELECTION_BUTTON
)
expect(clear_button_locator).to_be_visible(), "Clear selection button is missing"
def should_be_open_list_button(self) -> None:
"""Проверяет наличие кнопки раскрытия списка параметров.
Raises:
AssertionError: Если кнопка отсутствует.
"""
open_list_button_locator = self.selection_bar_locator.locator(
SelectionBarLocators.OPEN_PARAMETERS_LIST_BUTTON
)
expect(open_list_button_locator).to_be_visible(), "Open parameters list button is missing"

View File

@ -94,4 +94,4 @@ class SettingsFormComponent(BaseComponent):
""" """
self.toolbar.check_toolbar_presence_by_locator_and_title(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER, self.toolbar.check_toolbar_presence_by_locator_and_title(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER,
"Settings form toolbar is missing") "Session settings form toolbar is missing")

View File

@ -15,6 +15,7 @@ from components_derived.modal_change_password import ChangePasswordModalWindow
logger = get_logger("USER_CARD") logger = get_logger("USER_CARD")
class UserCard(BaseComponent): class UserCard(BaseComponent):
"""Компонент карточка. """Компонент карточка.
@ -35,37 +36,37 @@ class UserCard(BaseComponent):
# Обновленные локаторы согласно новой структуре карточки # Обновленные локаторы согласно новой структуре карточки
self.current_user_name = Text( self.current_user_name = Text(
page, page,
card_locator.locator("xpath=/div[@class='v-card__text']/div/div[1]"), # Изменено с div[2] на div[1] card_locator.locator("xpath=/div/div[1]"), # Изменено с div[2] на div[1]
"current user name" "current user name"
) )
self.current_user_role = Text( self.current_user_role = Text(
page, page,
card_locator.locator("xpath=/div[@class='v-card__text']/div/div[2]"), # Изменено с div[3] на div[2] card_locator.locator("xpath=/div/div[2]"), # Изменено с div[3] на div[2]
"current user role" "current user role"
) )
self.login_time = Text( self.login_time = Text(
page, page,
card_locator.locator("xpath=/div[@class='v-card__text']/div/div[3]"), # Изменено с div[4] на div[3] card_locator.locator("xpath=/div/div[3]"), # Изменено с div[4] на div[3]
"login time" "login time"
) )
self.session_time = Text( self.session_time = Text(
page, page,
card_locator.locator("xpath=/div[@class='v-card__text']/div/div[4]"), # Изменено с div[5] на div[4] card_locator.locator("xpath=/div/div[4]"), # Изменено с div[5] на div[4]
"session time" # Исправлено имя с "current user name" на "session time" "session time" # Исправлено имя с "current user name" на "session time"
) )
self.logout_button = Button( self.logout_button = Button(
page, page,
card_locator.locator(UserCardLocators.BUTTON_LOGOUT), page.get_by_role("button", name="Выйти"),
"logout button" "logout button"
) )
self.change_password_button = Button( self.change_password_button = Button(
page, page,
card_locator.locator(UserCardLocators.BUTTON_CHANGE_PASSWORD), page.get_by_role("button", name="Изменить пароль"),
"change password button" "change password button"
) )
self.close_button = Button( self.close_button = Button(
page, page,
card_locator.locator(UserCardLocators.BUTTON_CLOSE), page.get_by_role("button", name="Закрыть"),
"close button" "close button"
) )
@ -147,6 +148,6 @@ class UserCard(BaseComponent):
Raises: Raises:
AssertionError: Если карточка пользователя все еще открыта. AssertionError: Если карточка пользователя все еще открыта.
""" """
card_locator = self.page.locator(UserCardLocators.CARD_USER).locator("..") card_locator = self.page.locator(UserCardLocators.CARD_USER).locator("xpath=../..")
class_attr = card_locator.get_attribute('class') class_attr = card_locator.get_attribute('class')
assert 'menuable__content__active' not in class_attr, "User card should be closed" assert 'menuable__content__active' not in class_attr, "User card should be closed"

View File

@ -1,267 +0,0 @@
"""Модуль контейнера для отображения сертификата во вкладке 'Сертификаты'.
Содержит класс для работы с формой для отображения данных
сертификата во вкладке 'Сертификаты' через Playwright.
"""
from pathlib import Path
import os
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.certificate_locators import CertificateLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.tooltip_button_element import TooltipButton
from components.base_component import BaseComponent
logger = get_logger("VIEW_CRTIFICATE_FORM")
class ViewCertificateForm(BaseComponent):
"""Компонент формы для отображения данных сертификата во вкладке 'Сертификаты'.
Предоставляет методы для взаимодействия с элементами
формы для отображения данных сертификата во вкладке 'Сертификаты'.
"""
def __init__(self, page: Page):
"""Инициализирует компонент формы для отображения данных сертификата во вкладке 'Сертификаты'.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page)
button_locator = page.locator(CertificateLocators.FORM_CONTAINER).get_by_role("button")
self.button_export = TooltipButton(page, button_locator, "button_export")
# поля блока 'Основная информация'
base_info_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Основная информация')
self.base_info_title = Text(page, base_info_title_locator, "base_info_title")
self.base_info_version = TextInput(page, CertificateLocators.FIELD_VERSION, "base_info_version_field")
self.base_info_serial_number = TextInput(page, CertificateLocators.FIELD_SERIAL_NUMBER,
"base_info_serial_number_field")
self.base_info_signature_algorithm = TextInput(page, CertificateLocators.FIELD_SIGNATURE_ALGORITHM,
"base_info_signature_algorithm_field")
# поля блока 'Срок действия'
validity_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Срок действия')
self.validity_title = Text(page, validity_title_locator, "validity_title")
self.validity = TextInput(page, CertificateLocators.FIELD_VALIDITY, "validity_validity_field")
self.validity_not_before = TextInput(page, CertificateLocators.FIELD_NOT_BEFORE, "validity_not_before_field")
self.validity_not_after = TextInput(page, CertificateLocators.FIELD_NOT_AFTER, "validity_not_after_field")
# поля блока 'Издатель / Субъект'
subject_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Издатель / Субъект')
self.subject_title = Text(page, subject_title_locator, "subject_title")
self.subject_cert_name = TextInput(page, CertificateLocators.FIELD_CERT_NAME, "subject_cert_name_field")
self.subject_organization = TextInput(page, CertificateLocators.FIELD_ORGANIZATION,
"subject_organization_field")
self.subject_org_unit = TextInput(page, CertificateLocators.FIELD_ORG_UNIT, "subject_org_unit_field")
self.subject_country = TextInput(page, CertificateLocators.FIELD_COUNTRY, "subject_country_field")
self.subject_state = TextInput(page, CertificateLocators.FIELD_STATE, "subject_state_field")
self.subject_location = TextInput(page, CertificateLocators.FIELD_LOC, "subject_location_field")
# поля блока 'Ключ и отпечаток'
fingerprint_title_locator = page.locator(CertificateLocators.BLOCK_HEADER_TEXT). \
filter(has_text='Ключ и отпечаток')
self.fingerprint_title = Text(page, fingerprint_title_locator, "fingerprint_title")
self.fingerprint_public_key = TextInput(page, CertificateLocators.FIELD_PUBLIC_KEY_FINGERPRINT,
"fingerprint_public_key_field")
self.fingerprint_algorithm = TextInput(page, CertificateLocators.FIELD_ALGORITHM,
"fingerprint_algorithm_field")
self.fingerprint_key_size = TextInput(page, CertificateLocators.FIELD_KEY_SIZE,
"fingerprint_key_size_field")
# Действия:
def get_certificate(self) -> dict:
""" Возвращает значания полей отображаемого сертификата"""
certificate = {}
base_info_dict = {}
val = self.base_info_version.get_input_value().strip()
base_info_dict.update({"version": val})
val = self.base_info_serial_number.get_input_value().strip()
base_info_dict.update({"serialNumber": val})
val = self.base_info_signature_algorithm.get_input_value().strip()
base_info_dict.update({"signatureAlgorithm": val})
validity_dict = {}
val = self.validity.get_input_value().strip()
validity_dict.update({"status": val})
val = self.validity_not_before.get_input_value().strip()
validity_dict.update({"notBefore": val})
val = self.validity_not_after.get_input_value().strip()
validity_dict.update({"notAfter": val})
fingerprint_dict = {}
val = self.fingerprint_public_key.get_input_value().strip()
fingerprint_dict.update({"publicKeyFingerprint": val})
val = self.fingerprint_algorithm.get_input_value().strip()
fingerprint_dict.update({"algorithm": val})
val = self.fingerprint_key_size.get_input_value().strip()
fingerprint_dict.update({"keySize": int(val)})
subject_dict = {}
if self.subject_country.get_locator().count() != 0:
val = self.subject_country.get_input_value().strip()
subject_dict.update({"C": val})
if self.subject_state.get_locator().count() != 0:
val = self.subject_state.get_input_value().strip()
subject_dict.update({"ST": val})
if self.subject_location.get_locator().count() != 0:
val = self.subject_location.get_input_value().strip()
subject_dict.update({"L": val})
if self.subject_organization.get_locator().count() != 0:
val = self.subject_organization.get_input_value().strip()
subject_dict.update({"O": val})
if self.subject_org_unit.get_locator().count() != 0:
val = self.subject_org_unit.get_input_value().strip()
subject_dict.update({"OU": val})
if self.subject_cert_name.get_locator().count() != 0:
val = self.subject_cert_name.get_input_value().strip()
subject_dict.update({"CN": val})
certificate["baseInfo"] = base_info_dict
certificate["validity"] = validity_dict
certificate["fingerprint"] = fingerprint_dict
certificate["subject"] = subject_dict
return certificate
def export_certificate(self) -> str:
"""Нажатие кнопки 'Экспорт сертификата (CA)' в форме отображения сертификата и
скачивание текущего корневого сертификата.
Returns:
str : Полный путь к скачанному файлу.
"""
path_to_download = Path.home() / "Downloads"
self.button_export.check_visibility("Export certificate button is missing")
with self.page.expect_download() as download_info:
self.button_export.click()
download = download_info.value
download_error = download.failure()
assert not download_error, f"Download certificate error: {download_error}"
file_to_download = os.path.join(path_to_download, download.suggested_filename)
download.save_as(file_to_download)
assert os.path.exists(file_to_download), f"The certificate file '{file_to_download}' not found"
assert os.path.getsize(file_to_download) > 0, f"The certificate file '{file_to_download}' is empty"
return file_to_download
def _get_label_for_input_field(self, field_locator: str) -> str:
div_loc = f"//div[contains(@class, 'flex')][.{field_locator}]"
label = self.page.locator(div_loc).locator("//preceding-sibling::div[1]").locator("//input")
return label.input_value()
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов формы."""
self.button_export.check_visibility("Export certificate button is missing")
self.button_export.check_tooltip_with_text("Экспорт сертификата CA")
# проверка наличия всех полей формы
self.base_info_title.check_visibility("Title 'Основная информация' is missing")
version_label = self._get_label_for_input_field(CertificateLocators.FIELD_VERSION).strip()
assert version_label == 'ВЕРСИЯ (Version)', f"Unexpected field name {version_label} has got"
self.base_info_version.check_visibility("Field version value is missing")
serial_number_label = self._get_label_for_input_field(CertificateLocators.FIELD_SERIAL_NUMBER).strip()
assert serial_number_label == 'СЕРИЙНЫЙ НОМЕР (Serial Number)',\
f"Unexpected field name {serial_number_label} has got"
self.base_info_serial_number.check_visibility("Field serial number value is missing")
signature_algorithm_label = self._get_label_for_input_field(CertificateLocators.FIELD_SIGNATURE_ALGORITHM). \
strip()
assert signature_algorithm_label == 'АЛГОРИТМ ПОДПИСИ (Signature Algorithm)',\
f"Unexpected field name {signature_algorithm_label} has got"
self.base_info_signature_algorithm.check_visibility("Field signature algorithm value is missing")
self.validity_title.check_visibility("Title 'Срок действия' is missing")
validity_label = self._get_label_for_input_field(CertificateLocators.FIELD_VALIDITY).strip()
assert validity_label == 'СТАТУС (Validity)',\
f"Unexpected field name {validity_label} has got"
self.validity.check_visibility("Field validity value is missing")
validity_not_before_label = self._get_label_for_input_field(CertificateLocators.FIELD_NOT_BEFORE).strip()
assert validity_not_before_label == 'ДЕЙСТВИТЕЛЕН С (Not Before)',\
f"Unexpected field name {validity_not_before_label} has got"
self.validity_not_before.check_visibility("Field validity not before value is missing")
validity_not_after_label = self._get_label_for_input_field(CertificateLocators.FIELD_NOT_AFTER).strip()
assert validity_not_after_label == 'ДЕЙСТВИТЕЛЕН ДО (Not After)',\
f"Unexpected field name {validity_not_after_label} has got"
self.validity_not_after.check_visibility("Field validity not after value is missing")
self.subject_title.check_visibility("Title 'Издатель / Субъект' is missing")
if self.page.locator(CertificateLocators.FIELD_CERT_NAME).count() != 0:
cert_name_label = self._get_label_for_input_field(CertificateLocators.FIELD_CERT_NAME).strip()
assert cert_name_label == 'ИМЯ СЕРТИФИКАТА (CN)',\
f"Unexpected field name {cert_name_label} has got"
self.subject_cert_name.check_visibility("Field certificate name value is missing")
if self.page.locator(CertificateLocators.FIELD_ORGANIZATION).count() != 0:
organization_label = self._get_label_for_input_field(CertificateLocators.FIELD_ORGANIZATION).strip()
assert organization_label == 'ОРГАНИЗАЦИЯ (О)',\
f"Unexpected field name {organization_label} has got"
self.subject_organization.check_visibility("Field organization value is missing")
if self.page.locator(CertificateLocators.FIELD_ORG_UNIT).count() != 0:
org_unit_label = self._get_label_for_input_field(CertificateLocators.FIELD_ORG_UNIT).strip()
assert org_unit_label == 'ПОДРАЗДЕЛЕНИЕ (OU)',\
f"Unexpected field name {org_unit_label} has got"
self.subject_org_unit.check_visibility("Field organization unit value is missing")
if self.page.locator(CertificateLocators.FIELD_COUNTRY).count() != 0:
country_label = self._get_label_for_input_field(CertificateLocators.FIELD_COUNTRY).strip()
assert country_label == 'СТРАНА (С)',\
f"Unexpected field name {country_label} has got"
self.subject_country.check_visibility("Field country value is missing")
if self.page.locator(CertificateLocators.FIELD_STATE).count() != 0:
state_label = self._get_label_for_input_field(CertificateLocators.FIELD_STATE).strip()
assert state_label == 'РЕГИОН / ОБЛАСТЬ (ST)',\
f"Unexpected field name {state_label} has got"
self.subject_state.check_visibility("Field state value is missing")
if self.page.locator(CertificateLocators.FIELD_LOC).count() != 0:
location_label = self._get_label_for_input_field(CertificateLocators.FIELD_LOC).strip()
assert location_label == 'ГОРОД (l)',\
f"Unexpected field name {location_label} has got"
self.subject_location.check_visibility("Field location value is missing")
self.fingerprint_title.check_visibility("Title 'Ключ и отпечаток' is missing")
public_key_label = self._get_label_for_input_field(CertificateLocators.FIELD_PUBLIC_KEY_FINGERPRINT).strip()
assert public_key_label == 'ПУБЛИЧНЫЙ ОТПЕЧАТОК (PublicKeyFingerprint)',\
f"Unexpected field name {public_key_label} has got"
self.fingerprint_public_key.check_visibility("Field public key value is missing")
algorithm_label = self._get_label_for_input_field(CertificateLocators.FIELD_ALGORITHM).strip()
assert algorithm_label == 'АЛГОРИТМ (Algorithm)',\
f"Unexpected field name {algorithm_label} has got"
self.fingerprint_algorithm.check_visibility("Field algorithm value is missing")
key_size_label = self._get_label_for_input_field(CertificateLocators.FIELD_KEY_SIZE).strip()
assert key_size_label == 'ДЛИНА КЛЮЧА (Key Size)',\
f"Unexpected field name {key_size_label} has got"
self.fingerprint_key_size.check_visibility("Field key size value is missing")

View File

@ -20,7 +20,7 @@ class Environment:
DEVELOP: str = 'develop' DEVELOP: str = 'develop'
URLS: Dict[str, str] = { URLS: Dict[str, str] = {
TEST: 'https://192.168.236.12/', TEST: 'https://192.168.2.76/',
DEVELOP: 'https://192.168.2.69/' DEVELOP: 'https://192.168.2.69/'
} }

View File

@ -31,13 +31,4 @@ class TabButton(BaseElement):
# (Методы действий будут добавлены по мере необходимости) # (Методы действий будут добавлены по мере необходимости)
# Проверки: # Проверки:
def is_active(self) -> bool: # (Методы проверок будут добавлены по мере необходимости)
""" Проверяет является ли кнопка-tab активной """
tab_locator = self.get_locator()
attributes = tab_locator.get_attribute("class")
is_active_tab = False
if "v-tabs__item--active" in attributes:
is_active_tab = True
return is_active_tab

View File

@ -67,7 +67,7 @@ class TooltipButton(BaseElement):
""" """
# Наведение на элемент для отображения подсказки # Наведение на элемент для отображения подсказки
self.locator.hover(force=True) self.locator.hover()
# Получение элемента подсказки # Получение элемента подсказки
tooltip = self.page.locator(ButtonLocators.TOOLTIP) tooltip = self.page.locator(ButtonLocators.TOOLTIP)
@ -82,8 +82,3 @@ class TooltipButton(BaseElement):
f"Текст подсказки не соответствует ожидаемому. " f"Текст подсказки не соответствует ожидаемому. "
f"Ожидалось: '{expected_text}', получено: '{actual_text}'" f"Ожидалось: '{expected_text}', получено: '{actual_text}'"
) )
def is_disabled(self) -> bool:
""" Возвращает значение, отключена ли кнопка (является скрытой) """
return self.locator.is_disabled()

View File

@ -1,469 +0,0 @@
"""Базовый модуль для работы с формами стойки."""
import time
from dataclasses import dataclass
from typing import Optional, List, Dict, Any, Tuple
from abc import ABC, abstractmethod
from playwright.sync_api import Page
from tools.logger import get_logger
from elements.text_input_element import TextInput
from components.base_component import BaseComponent
from components.dropdown_list_component import DropdownList
logger = get_logger("BASE_RACK_FORM")
logger.setLevel("INFO")
@dataclass
class BaseRackData:
"""Базовый класс для хранения данных стойки."""
# Основные поля
name: str = ""
serial: str = ""
inventory: str = ""
comment: str = ""
# Combobox поля
cable_entry: str = ""
state: str = ""
depth: str = ""
usize: str = ""
# Combobox поля (справочники)
owner: str = ""
service_org: str = ""
project: str = ""
class BaseRackForm(BaseComponent, ABC):
"""Базовый компонент для работы с формами стойки."""
# Маппинг текстовых полей (должен быть переопределен в наследниках)
TEXT_FIELDS_MAPPING: Dict[str, Tuple[str, str]] = {}
TEXT_FIELDS_LOCATORS: Dict[str, str] = {}
# Маппинг combobox полей (должен быть переопределен в наследниках)
COMBOBOX_FIELDS_MAPPING: Dict[str, Tuple[str, str, str]] = {}
COMBOBOX_FIELDS_LOCATORS: Dict[str, str] = {}
# Дополнительные типы полей (checkbox и т.д.) - опционально
CHECKBOX_FIELDS_MAPPING: Dict[str, Tuple[str, str]] = {}
CHECKBOX_FIELDS_LOCATORS: Dict[str, str] = {}
def __init__(self, page: Page, form_container_locator: str) -> None:
"""Инициализирует базовый компонент формы стойки.
Args:
page: Экземпляр страницы Playwright
form_container_locator: Локатор контейнера формы
"""
super().__init__(page)
self.page = page
self.form_container_locator = form_container_locator
self.content_items: Dict[str, Any] = {}
self.available_fields = None
# Инициализация полей формы
self._init_form_fields()
def _init_form_fields(self) -> None:
"""Инициализирует все поля формы."""
container_locator = self.page.locator(self.form_container_locator)
if container_locator.count() > 0:
self.available_fields = self.get_input_fields_locators(container_locator)
self._init_text_fields()
self._init_combobox_fields()
self._init_checkbox_fields()
def _init_text_fields(self) -> None:
"""Инициализирует текстовые поля формы."""
for field_label, (attr_name, widget_name) in self.TEXT_FIELDS_MAPPING.items():
locator = self.TEXT_FIELDS_LOCATORS.get(field_label)
if not locator:
continue
self._init_single_text_field(field_label, locator, widget_name)
def _init_single_text_field(self, field_label: str, locator: str, widget_name: str) -> None:
"""Инициализирует одно текстовое поле."""
try:
element = self.page.locator(locator).first
if element.count() > 0 and element.is_visible():
field_input = TextInput(self.page, element, widget_name)
self.content_items[widget_name] = field_input
logger.debug(f"Initialized text field: '{field_label}'")
except Exception as e:
logger.error(f"Error initializing text field '{field_label}': {e}")
def _init_combobox_fields(self) -> None:
"""Инициализирует combobox поля формы."""
for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items():
locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_label)
if not locator:
continue
self._init_single_combobox_field(field_label, locator, input_name, list_name)
def _init_single_combobox_field(
self, field_label: str, locator: str, input_name: str, list_name: str
) -> None:
"""Инициализирует одно combobox поле."""
try:
element = self.page.locator(locator).first
if element.count() > 0 and element.is_visible():
field_input = TextInput(self.page, element, input_name)
self.content_items[input_name] = field_input
self.content_items[list_name] = DropdownList(self.page)
logger.debug(f"Initialized combobox field: '{field_label}'")
except Exception as e:
logger.error(f"Error initializing combobox field '{field_label}': {e}")
def _init_checkbox_fields(self) -> None:
"""Инициализирует checkbox поля формы (опционально)."""
if not self.CHECKBOX_FIELDS_MAPPING:
return
for field_label, (attr_name, widget_name) in self.CHECKBOX_FIELDS_MAPPING.items():
locator = self.CHECKBOX_FIELDS_LOCATORS.get(field_label)
if not locator:
continue
self._init_single_checkbox_field(field_label, locator, widget_name)
def _init_single_checkbox_field(self, field_label: str, locator: str, widget_name: str) -> None:
"""Инициализирует одно checkbox поле."""
try:
checkbox_input = self.page.locator(locator).first
if checkbox_input.count() == 0:
logger.debug(f"Checkbox '{field_label}' not found")
return
# Импортируем здесь чтобы избежать циклических импортов
from elements.checkbox_element import Checkbox
checkbox = Checkbox(self.page, checkbox_input, widget_name)
self.content_items[widget_name] = checkbox
logger.debug(f"Initialized checkbox field: '{field_label}'")
except Exception as e:
logger.error(f"Error initializing checkbox '{field_label}': {e}")
def get_content_item(self, item_name: str) -> Any:
"""Возвращает элемент контента по имени."""
return self.content_items.get(item_name)
def clear_field(self, field_name: str) -> None:
"""Очищает указанное поле."""
logger.debug(f"Clearing field: '{field_name}'")
# Проверяем, не является ли поле чекбоксом
if field_name in self.CHECKBOX_FIELDS_LOCATORS:
logger.debug(f"Field '{field_name}' is a checkbox, skipping clear operation")
return
# Получаем локатор поля
locator = self._get_field_locator(field_name)
if not locator:
logger.warning(f"Unknown field: {field_name}")
return
field_element = self.page.locator(locator).first
if field_element.count() == 0:
logger.debug(f"Field '{field_name}' not found")
return
# Очистка в зависимости от типа поля
if field_name in self.TEXT_FIELDS_LOCATORS:
self._clear_text_field(field_element, field_name)
elif field_name in self.COMBOBOX_FIELDS_LOCATORS:
self._clear_combobox_field(field_element, field_name)
def _get_field_locator(self, field_name: str) -> Optional[str]:
"""Получает локатор поля по его названию."""
if field_name in self.COMBOBOX_FIELDS_LOCATORS:
return self.COMBOBOX_FIELDS_LOCATORS[field_name]
elif field_name in self.TEXT_FIELDS_LOCATORS:
return self.TEXT_FIELDS_LOCATORS[field_name]
elif field_name in self.CHECKBOX_FIELDS_LOCATORS:
return self.CHECKBOX_FIELDS_LOCATORS[field_name]
return None
def _clear_text_field(self, field_element, field_name: str) -> None:
"""Очищает текстовое поле."""
try:
field_element.click()
field_element.page.keyboard.press("Control+A")
field_element.page.keyboard.press("Backspace")
self.wait_for_timeout(200)
logger.debug(f"Text field '{field_name}' cleared")
except Exception as e:
logger.debug(f"Could not clear text field '{field_name}': {e}")
def _clear_combobox_field(self, field_element, field_name: str) -> None:
"""Очищает combobox поле."""
try:
parent_container = field_element.locator(
"xpath=ancestor::div[contains(@class, 'v-input')]"
).first
if parent_container.count() == 0:
logger.debug(f"Parent container not found for field '{field_name}'")
return
clear_button = parent_container.locator(
".v-input__icon--clear button, .v-input__icon--append button, i.mdi-close-circle, i.mdi-close"
).first
if clear_button.count() > 0 and clear_button.is_visible():
clear_button.click()
self.wait_for_timeout(300)
logger.debug(f"Combobox field '{field_name}' cleared")
else:
logger.debug(f"Clear button not found for field '{field_name}'")
except Exception as e:
logger.debug(f"Error clearing combobox field '{field_name}': {e}")
def _scroll_to_element_in_dropdown(self, value: str) -> bool:
"""Скроллит выпадающий список до элемента с нужным текстом."""
logger.debug(f"Scrolling to find element with text: '{value}'")
dropdown_menu = self.page.locator("div.menuable__content__active").first
if dropdown_menu.count() == 0:
logger.error("Active dropdown menu not found")
return False
max_attempts = 10
attempts = 0
while attempts < max_attempts:
visible_items = dropdown_menu.locator("a.v-list__tile, div[role='listitem']").all()
if visible_items:
for item in visible_items:
item_text = item.text_content() or ""
if value in item_text:
logger.debug(f"Found element with text '{value}'")
item.scroll_into_view_if_needed()
self.wait_for_timeout(300)
return True
last_item = visible_items[-1]
last_item_text = last_item.text_content() or ""
logger.debug(f"Scrolling to last visible item: '{last_item_text}'")
last_item.scroll_into_view_if_needed()
self.wait_for_timeout(500)
else:
dropdown_menu.evaluate("(el) => el.scrollTop += 200")
self.wait_for_timeout(300)
attempts += 1
logger.warning(f"Element with text '{value}' not found after {max_attempts} attempts")
return False
def _fill_text_fields(self, rack_data: BaseRackData, results: Dict[str, int]) -> None:
"""Заполняет текстовые поля."""
for field_label, (attr_name, field_name) in self.TEXT_FIELDS_MAPPING.items():
value = getattr(rack_data, attr_name, "")
if not value or not str(value).strip():
continue
self._fill_single_text_field(field_label, field_name, value, results)
def _fill_single_text_field(
self, field_label: str, field_name: str, value: str, results: Dict[str, int]
) -> None:
"""Заполняет одно текстовое поле."""
try:
input_field = self.get_content_item(field_name)
if input_field:
input_field.input_value(value)
results["text_fields_filled"] += 1
logger.debug(f"Field '{field_label}' filled: '{value}'")
except Exception as e:
logger.error(f"Error filling field '{field_label}': {e}")
def _fill_combobox_fields(self, rack_data: BaseRackData, results: Dict[str, int]) -> None:
"""Заполняет combobox поля."""
for field_label, (attr_name, input_name, list_name) in self.COMBOBOX_FIELDS_MAPPING.items():
value = getattr(rack_data, attr_name, "")
if not value or not str(value).strip():
continue
self._fill_single_combobox_field(field_label, input_name, list_name, value, results)
def _fill_single_combobox_field(
self, field_label: str, input_name: str, list_name: str, value: str, results: Dict[str, int]
) -> None:
"""Заполняет одно combobox поле."""
try:
combobox_field = self.get_content_item(input_name)
if not combobox_field:
logger.warning(f"Field '{field_label}' input not found")
return
combobox_field.click(force=True)
self.wait_for_timeout(1000)
if not self._scroll_to_element_in_dropdown(value):
logger.error(f"Could not find element with text '{value}' after scrolling")
self.page.mouse.click(10, 10)
self.wait_for_timeout(300)
return
dropdown_menu = self.page.locator("div.menuable__content__active").first
item_locator = self._find_dropdown_item(dropdown_menu, value)
if item_locator and item_locator.count() > 0:
item_locator.scroll_into_view_if_needed()
self.wait_for_timeout(300)
item_locator.click()
results["combobox_fields_filled"] += 1
logger.debug(f"Field '{field_label}' set: '{value}'")
self.wait_for_timeout(500)
else:
logger.error(f"Item with text '{value}' not found in dropdown for field '{field_label}'")
self.page.mouse.click(10, 10)
self.wait_for_timeout(300)
except Exception as e:
logger.error(f"Error filling combobox '{field_label}': {e}")
self.page.mouse.click(10, 10)
def _find_dropdown_item(self, dropdown_menu, value: str):
"""Находит элемент в выпадающем списке."""
item_locator = dropdown_menu.locator(f"a.v-list__tile:has-text('{value}')").first
if item_locator.count() == 0:
item_locator = dropdown_menu.locator(f"span:has-text('{value}')").first
if item_locator.count() == 0:
item_locator = dropdown_menu.locator(f"div[role='listitem']:has-text('{value}')").first
return item_locator
def _fill_checkbox_fields(self, rack_data: BaseRackData, results: Dict[str, int]) -> None:
"""Заполняет checkbox поля (опционально)."""
if not hasattr(self, 'CHECKBOX_FIELDS_MAPPING'):
return
for field_label, (attr_name, widget_name) in self.CHECKBOX_FIELDS_MAPPING.items():
value = getattr(rack_data, attr_name, None)
if value is None:
continue
self._fill_single_checkbox_field(field_label, widget_name, value, results)
def _fill_single_checkbox_field(
self, field_label: str, widget_name: str, value: bool, results: Dict[str, int]
) -> None:
"""Заполняет одно checkbox поле."""
try:
checkbox = self.get_content_item(widget_name)
if not checkbox:
logger.warning(f"Checkbox '{field_label}' not found")
return
if value:
checkbox.check(force=True)
logger.debug(f"Checkbox '{field_label}' checked")
else:
checkbox.uncheck(force=True)
logger.debug(f"Checkbox '{field_label}' unchecked")
results["checkboxes_set"] += 1
except Exception as e:
logger.error(f"Error setting checkbox '{field_label}': {e}")
@abstractmethod
def fill_rack_data(self, rack_data: BaseRackData) -> Dict[str, int]:
"""Абстрактный метод для заполнения данных стойки."""
pass
def is_field_highlighted_as_error(self, field_name: str) -> bool:
"""Проверяет, подсвечено ли поле как ошибочное."""
# Для чекбоксов не проверяем ошибки
if field_name in self.CHECKBOX_FIELDS_LOCATORS:
return False
locator = self._get_field_locator(field_name)
if not locator:
return False
field_element = self.page.locator(locator).first
if field_element.count() == 0:
logger.debug(f"Field '{field_name}' not found")
return False
parent_input = field_element.locator(
"xpath=ancestor::div[contains(@class, 'v-input')]"
).first
if parent_input.count() > 0:
class_attr = parent_input.get_attribute("class") or ""
is_error = "v-input--error" in class_attr or "error--text" in class_attr
logger.debug(f"Field '{field_name}' error state: {is_error}")
return is_error
return False
def verify_required_fields_highlighted(self, field_names: List[str]) -> Dict[str, bool]:
"""Проверяет, что указанные поля подсвечены как обязательные."""
results = {}
for field_name in field_names:
results[field_name] = self.is_field_highlighted_as_error(field_name)
logger.debug(f"Field '{field_name}' highlighted: {results[field_name]}")
return results
def wait_for_field_error(self, field_name: str, timeout: int = 5000) -> bool:
"""Ожидает появления подсветки ошибки на поле."""
if field_name in self.CHECKBOX_FIELDS_LOCATORS:
return False
start_time = time.time()
while (time.time() - start_time) * 1000 < timeout:
if self.is_field_highlighted_as_error(field_name):
return True
self.wait_for_timeout(200)
return False
def get_field_value(self, field_name: str) -> Optional[str]:
"""Получает значение поля."""
# Для чекбоксов
if field_name in self.CHECKBOX_FIELDS_LOCATORS:
for field_label, (attr_name, widget_name) in self.CHECKBOX_FIELDS_MAPPING.items():
if attr_name == field_name or field_label == field_name:
checkbox = self.get_content_item(widget_name)
if checkbox:
return str(checkbox.is_checked())
return None
# Для текстовых полей
if field_name in self.TEXT_FIELDS_LOCATORS:
for field_label, (attr_name, widget_name) in self.TEXT_FIELDS_MAPPING.items():
if attr_name == field_name or field_label == field_name:
input_field = self.get_content_item(widget_name)
if input_field:
return input_field.get_input_value()
return None
# Для combobox полей
return self._get_combobox_value(field_name)
def _get_combobox_value(self, field_name: str) -> Optional[str]:
"""Получает значение combobox поля."""
locator = self.COMBOBOX_FIELDS_LOCATORS.get(field_name)
if not locator:
for field_label, (attr_name, input_name, _) in self.COMBOBOX_FIELDS_MAPPING.items():
if attr_name == field_name or field_label == field_name:
input_field = self.get_content_item(input_name)
if input_field:
selections = input_field.element.locator(
"xpath=ancestor::div[contains(@class, 'v-select__selections')]"
).first
if selections.count() > 0:
value_span = selections.locator("span").first
return value_span.text_content() or ""
return None
element = self.page.locator(locator).first
if element.count() > 0:
selections = element.locator(
"xpath=ancestor::div[contains(@class, 'v-select__selections')]"
).first
if selections.count() > 0:
value_span = selections.locator("span").first
return value_span.text_content() or ""
return None

View File

@ -1,77 +0,0 @@
# forms/rack_create_form.py
"""Модуль для работы с формой создания стойки."""
from dataclasses import dataclass
from typing import Dict
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from forms.base_rack_form import BaseRackForm, BaseRackData
logger = get_logger("CREATE_RACK_FORM")
@dataclass
class CreateRackData(BaseRackData):
"""Класс для хранения данных создаваемой стойки."""
pass # Используем все поля из базового класса
class CreateRackForm(BaseRackForm):
"""Компонент для работы с формой создания стойки."""
# Маппинг текстовых полей
TEXT_FIELDS_MAPPING = {
"Имя": ("name", "name_input"),
"Комментарий": ("comment", "comment_input"),
"Серийный номер": ("serial", "serial_input"),
"Инвентарный номер": ("inventory", "inventory_input"),
}
# Маппинг combobox полей
COMBOBOX_FIELDS_MAPPING = {
"Ввод кабеля": ("cable_entry", "cable_entry_input", "cable_entry_list"),
"Состояние": ("state", "state_input", "state_list"),
"Высота в юнитах": ("usize", "usize_input", "usize_list"),
"Глубина (мм)": ("depth", "depth_input", "depth_list"),
"Владелец": ("owner", "owner_input", "owner_list"),
"Обслуживающая организация": ("service_org", "service_input", "service_list"),
"Проект/Титул": ("project", "project_input", "project_list")
}
# Локаторы для текстовых полей
TEXT_FIELDS_LOCATORS = {
"Имя": RackLocators.CREATE_RACK_FORM_FIELD_NAME,
"Комментарий": RackLocators.CREATE_RACK_FORM_FIELD_COMMENT,
"Серийный номер": RackLocators.CREATE_RACK_FORM_FIELD_SERIAL,
"Инвентарный номер": RackLocators.CREATE_RACK_FORM_FIELD_INVENTORY,
}
# Локаторы для combobox полей
COMBOBOX_FIELDS_LOCATORS = {
"Высота в юнитах": RackLocators.CREATE_RACK_FORM_SELECT_USIZE,
"Глубина (мм)": RackLocators.CREATE_RACK_FORM_SELECT_DEPTH,
"Ввод кабеля": RackLocators.CREATE_RACK_FORM_SELECT_CABLE_INPUT,
"Состояние": RackLocators.CREATE_RACK_FORM_SELECT_CONDITION_TYPE,
"Владелец": RackLocators.CREATE_RACK_FORM_SELECT_OWNER,
"Обслуживающая организация": RackLocators.CREATE_RACK_FORM_SELECT_SERVICE_PROVIDER,
"Проект/Титул": RackLocators.CREATE_RACK_FORM_SELECT_PROJECT,
}
def __init__(self, page: Page) -> None:
"""Инициализирует компонент формы создания стойки."""
super().__init__(page, RackLocators.CREATE_RACK_FORM_CONTAINER)
def fill_rack_data(self, rack_data: CreateRackData) -> Dict[str, int]:
"""Заполняет поля формы создания стойки."""
results = {
"text_fields_filled": 0,
"combobox_fields_filled": 0,
}
self._fill_text_fields(rack_data, results)
self._fill_combobox_fields(rack_data, results)
logger.info(f"Filled {results['text_fields_filled']} text fields and "
f"{results['combobox_fields_filled']} combobox fields")
return results

View File

@ -1,96 +0,0 @@
# forms/rack_edit_form.py
"""Модуль для работы с формой редактирования стойки."""
from dataclasses import dataclass
from typing import Optional, Dict
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from forms.base_rack_form import BaseRackForm, BaseRackData
logger = get_logger("EDIT_RACK_FORM")
@dataclass
class EditRackData(BaseRackData):
"""Класс для хранения данных редактируемой стойки."""
# Дополнительное поле для формы редактирования
allocated_power: str = ""
ventilation_panel: Optional[bool] = None
class EditRackForm(BaseRackForm):
"""Компонент для работы с формой редактирования стойки."""
# Маппинг текстовых полей
TEXT_FIELDS_MAPPING = {
"Имя": ("name", "name_input"),
"Комментарий": ("comment", "comment_input"),
"Серийный номер": ("serial", "serial_input"),
"Инвентарный номер": ("inventory", "inventory_input"),
"Выделенная мощность (Вт/ВА)": ("allocated_power", "power_input"),
}
# Маппинг combobox полей
COMBOBOX_FIELDS_MAPPING = {
"Ввод кабеля": ("cable_entry", "cable_entry_input", "cable_entry_list"),
"Состояние": ("state", "state_input", "state_list"),
"Глубина (мм)": ("depth", "depth_input", "depth_list"),
"Высота в юнитах": ("usize", "usize_input", "usize_list"),
"Владелец": ("owner", "owner_input", "owner_list"),
"Обслуживающая организация": ("service_org", "service_input", "service_list"),
"Проект/Титул": ("project", "project_input", "project_list")
}
# Маппинг checkbox полей
CHECKBOX_FIELDS_MAPPING = {
"Вентиляционная панель": ("ventilation_panel", "ventilation_checkbox"),
}
# Локаторы для текстовых полей
TEXT_FIELDS_LOCATORS = {
"Имя": RackLocators.EDIT_RACK_FORM_FIELD_NAME,
"Комментарий": RackLocators.EDIT_RACK_FORM_FIELD_COMMENT,
"Серийный номер": RackLocators.EDIT_RACK_FORM_FIELD_SERIAL,
"Инвентарный номер": RackLocators.EDIT_RACK_FORM_FIELD_INVENTORY,
"Выделенная мощность (Вт/ВА)": RackLocators.EDIT_RACK_FORM_FIELD_POWER,
}
# Локаторы для combobox полей
COMBOBOX_FIELDS_LOCATORS = {
"Ввод кабеля": RackLocators.EDIT_RACK_FORM_SELECT_CABLE_INPUT,
"Состояние": RackLocators.EDIT_RACK_FORM_SELECT_CONDITION_TYPE,
"Глубина (мм)": RackLocators.EDIT_RACK_FORM_SELECT_DEPTH,
"Высота в юнитах": RackLocators.EDIT_RACK_FORM_SELECT_USIZE,
"Владелец": RackLocators.EDIT_RACK_FORM_SELECT_OWNER,
"Обслуживающая организация": RackLocators.EDIT_RACK_FORM_SELECT_SERVICE_PROVIDER,
"Проект/Титул": RackLocators.EDIT_RACK_FORM_SELECT_PROJECT,
}
# Локаторы для checkbox полей
CHECKBOX_FIELDS_LOCATORS = {
"Вентиляционная панель": RackLocators.EDIT_RACK_FORM_CHECKBOX_VENTILATION,
}
def __init__(self, page: Page) -> None:
"""Инициализирует компонент формы редактирования стойки."""
super().__init__(page, RackLocators.EDIT_RACK_FORM)
def fill_rack_data(self, rack_data: EditRackData) -> Dict[str, int]:
"""Заполняет поля формы редактирования стойки."""
results = {
"text_fields_filled": 0,
"combobox_fields_filled": 0,
"checkboxes_set": 0
}
self._fill_text_fields(rack_data, results)
self._fill_combobox_fields(rack_data, results)
self._fill_checkbox_fields(rack_data, results)
logger.info(f"Filled {results['text_fields_filled']} text fields, "
f"{results['combobox_fields_filled']} combobox fields, "
f"{results['checkboxes_set']} checkboxes")
return results

View File

@ -1,48 +0,0 @@
"""Модуль backup_tab_locators содержит локаторы элементов страницы 'Резервное копирование'.
Класс RackLocators хранит XPath/CSS локаторы для взаимодействия
с элементами интерфейса вкладки в тестах.
"""
class BackupTabLocators:
"""Класс для хранения локаторов элементов страницы 'Резервное копирование'.
Содержит локаторы в формате XPath/CSS для поиска элементов
"""
# Кнопки на тулбаре
BUTTON_EDIT_TOOLBAR = "//button[@data-testid='BACKUP_PANEL__btn__edit']"
BUTTON_SAVE_TOOLBAR = "//button[@data-testid='BACKUP_PANEL__btn__submit']"
BUTTON_CANCEL_TOOLBAR = "//button[@data-testid='BACKUP_PANEL__btn__cancel']"
# Кнопки раздела 'Инвентаризация'
BUTTON_INVENTORY_CREATE_COPY = "//button[@data-testid='BACKUP_PANEL__btn__createCopy_cmdb']"
BUTTON_INVENTORY_UPLOAD_COPY = "//button[@data-testid='BACKUP_PANEL__btn__upload_cmdb']"
BUTTON_INVENTORY_RESTORE_COPY = "//button[@data-testid='BACKUP_PANEL__btn__restore_cmdb']"
BUTTON_INVENTORY_DOWNLOAD_COPY = "//button[@data-testid='BACKUP_PANEL__btn__download_cmdb']"
# Набор полей 'Инвентаризация/Параметры планировщика'
INPUT_INVENTORY_BACKUP_CREATION_TIME = "//input[@data-testid='BACKUP_PANEL__text-field__auto_backup_cmdb']"
INPUT_INVENTORY_BACKUP_NUMBERS = "//input[@data-testid='BACKUP_PANEL__text-field__backup_limitation_cmdb']"
# Кнопки раздела 'Потоковые данные'
BUTTON_STREAMING_DATA_CREATE_COPY = "//button[@data-testid='BACKUP_PANEL__btn__createCopy_streaming_data']"
BUTTON_STREAMING_DATA_UPLOAD_COPY = "//button[@data-testid='BACKUP_PANEL__btn__upload_streaming_data']"
BUTTON_STREAMING_DATA_RESTORE_COPY = "//button[@data-testid='BACKUP_PANEL__btn__restore_streaming_data']"
BUTTON_STREAMING_DATA_DOWNLOAD_COPY = "//button[@data-testid='BACKUP_PANEL__btn__download_streaming_data']"
# Поля ввода данных для различных категорий раздела 'Потоковые данные'
INPUT_AUDIT_TIME_PERIOD = "//input[@data-testid='BACKUP_PANEL__text-field__data_limitation_default_audit']"
INPUT_AUDIT_TIME_PERIOD_INTERVAL = "//input[@data-testid='BACKUP_PANEL__select__interval_limitation_default_audit']"
INPUT_LOGS_TIME_PERIOD = "//input[@data-testid='BACKUP_PANEL__text-field__data_limitation_logs_logs']"
INPUT_LOGS_TIME_PERIOD_INTERVAL ="//input[@data-testid='BACKUP_PANEL__select__interval_limitation_logs_logs']"
INPUT_METRICS_TIME_PERIOD = "//input[@data-testid='BACKUP_PANEL__text-field__data_limitation_metrics_metrics']"
INPUT_METRICS_TIME_PERIOD_INTERVAL = "//input[@data-testid='BACKUP_PANEL__select__interval_limitation_metrics_metrics']"
INPUT_SYSLOG_TIME_PERIOD = "//input[@data-testid='BACKUP_PANEL__text-field__data_limitation_syslog_syslog']"
INPUT_SYSLOG_TIME_PERIOD_INTERVAL = "//input[@data-testid='BACKUP_PANEL__select__interval_limitation_syslog_syslog']"
INPUT_TASKS_TIME_PERIOD = "//input[@data-testid='BACKUP_PANEL__text-field__data_limitation_tasks_tasks']"
INPUT_TASKS_TIME_PERIOD_INTERVAL = "//input[@data-testid='BACKUP_PANEL__select__interval_limitation_tasks_tasks']"
# Набор полей 'Потоковые данные/Параметры планировщика'
INPUT_STREAMING_DATA_BACKUP_CREATION_TIME = "//input[@data-testid='BACKUP_PANEL__text-field__auto_backup_streaming_data']"

View File

@ -13,7 +13,6 @@ class ButtonLocators:
- Кнопка удаления сессии - Кнопка удаления сессии
""" """
BUTTON_LICENSE_UPDATE = "//button[@data-testid='LICENSE__btn__setLicense']" BUTTON_LICENSE_UPDATE = "//div[@class='scrollarea__footer']//button"
TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]"
BUTTON_DELETE_SESSION = "button.v-btn--icon svg[fill='#4caf50']" BUTTON_DELETE_SESSION = "button.v-btn--icon svg[fill='#4caf50']"

View File

@ -1,63 +0,0 @@
"""Модуль certificate_locators содержит локаторы элементов вкладки 'Сертификаты'.
Класс ToolbarLocators предоставляет XPath локаторы для взаимодействия
с элементами тулбара и всплывающими подсказками.
"""
class CertificateLocators:
"""Локаторы элементов вкладки 'Сертификаты'.
Содержит XPath локаторы для поиска элементов.
"""
TOOLBAR_CASTOM = "//div[contains(@class, 'scrollarea__container')]//div[contains(@class,'toolbar_castom')]"
MAIN_CONTAINER = f"{TOOLBAR_CASTOM}/ancestor::div[4]"
MAIN_CONTAINER_HEADER = f"{MAIN_CONTAINER}//div[contains(@class, 'scrollarea__header')]"
MAIN_CONTAINER_BODY = f"{MAIN_CONTAINER}//div[contains(@class, 'scrollarea__body')]"
TAB_CERTIFICATE_CA = f"{MAIN_CONTAINER_HEADER}//a[contains(@class, 'v-tabs__item') and contains(.,'Сертификат CA')]"
TAB_REISSUE_CA = f"{MAIN_CONTAINER_HEADER}//a[contains(@class, 'v-tabs__item') and contains(., 'Пересоздание CA')]"
TAB_IMPORT_CA = f"{MAIN_CONTAINER_HEADER}//a[contains(@class, 'v-tabs__item') and contains(., 'import ca (p12)')]"
FORM_CONTAINER = f"{MAIN_CONTAINER_BODY}//div[contains(@class, 'scrollarea__body')]"
BLOCK_HEADER_TEXT = f"{FORM_CONTAINER}//span[@class='body-2']"
# поля блока 'Сертификат CA/Основная информация'
FIELD_VERSION = "//input[@data-testid='SERTIFICATES-CA__text-field__baseInfo.version']"
FIELD_SERIAL_NUMBER = "//input[@data-testid='SERTIFICATES-CA__text-field__baseInfo.serialNumber']"
FIELD_SIGNATURE_ALGORITHM = "//input[@data-testid='SERTIFICATES-CA__text-field__baseInfo.signatureAlgorithm']"
# поля блока 'Сертификат CA/Срок действия'
FIELD_VALIDITY = "//input[@data-testid='SERTIFICATES-CA__text-field__validity.status']"
FIELD_NOT_BEFORE = "//input[@data-testid='SERTIFICATES-CA__text-field__validity.notBefore']"
FIELD_NOT_AFTER = "//input[@data-testid='SERTIFICATES-CA__text-field__validity.notAfter']"
# поля блока 'Сертификат CA/Издатель / Субъект'
FIELD_CERT_NAME = "//input[@data-testid='SERTIFICATES-CA__text-field__subject.CN']"
FIELD_ORGANIZATION = "//input[@data-testid='SERTIFICATES-CA__text-field__subject.O']"
FIELD_ORG_UNIT = "//input[@data-testid='SERTIFICATES-CA__text-field__subject.OU']"
FIELD_COUNTRY = "//input[@data-testid='SERTIFICATES-CA__text-field__subject.C']"
FIELD_STATE = "//input[@data-testid='SERTIFICATES-CA__text-field__subject.ST']"
FIELD_LOC = "//input[@data-testid='SERTIFICATES-CA__text-field__subject.L']"
# поля блока 'Сертификат CA/Ключ и отпечаток'
FIELD_PUBLIC_KEY_FINGERPRINT = "//input[@data-testid='SERTIFICATES-CA__text-field__fingerprint.publicKeyFingerprint']"
FIELD_ALGORITHM = "//input[@data-testid='SERTIFICATES-CA__text-field__fingerprint.algorithm']"
FIELD_KEY_SIZE = "//input[@data-testid='SERTIFICATES-CA__text-field__fingerprint.keySize']"
# поля блока 'Пересоздание CA/Идентификация CA'
FIELD_INPUT_CERT_NAME = "//input[@data-testid='SERTIFICATES-REISSUE__text-field__publisher.cn']"
FIELD_INPUT_ORGANIZATION = "//input[@data-testid='SERTIFICATES-REISSUE__text-field__publisher.o']"
FIELD_INPUT_ORG_UNIT = "//input[@data-testid='SERTIFICATES-REISSUE__text-field__publisher.ou']"
# поля блока 'Пересоздание CA/Адрес / Местонахождение'
FIELD_INPUT_COUNTRY = "//input[@data-testid='SERTIFICATES-REISSUE__text-field__publisher.c']"
FIELD_INPUT_STATE = "//input[@data-testid='SERTIFICATES-REISSUE__text-field__publisher.st']"
FIELD_INPUT_LOC = "//input[@data-testid='SERTIFICATES-REISSUE__text-field__publisher.l']"
# поля блока 'Импорт CA'
FIELD_INPUT_PASSWORD = "//input[@data-testid='SERTIFICATES-IMPORT__text-field__pass']"
BUTTON_IMPORT = "//button[@data-testid='SERTIFICATES-IMPORT__btn__upload_p12']"

View File

@ -16,5 +16,7 @@ class ConfirmLocators:
CONFIRM = "//div[contains(@class, 'v-dialog--active')]" CONFIRM = "//div[contains(@class, 'v-dialog--active')]"
TITLE = f"{CONFIRM}//div[contains(@class, 'v-card__title')]" TITLE = f"{CONFIRM}//div[contains(@class, 'v-card__title')]"
#TITLE = "//div[@class='v-card__title']/h3"
BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']" BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']"
#TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']"
TEXT = f"{CONFIRM}//div[contains(@class, 'v-card__text')]" TEXT = f"{CONFIRM}//div[contains(@class, 'v-card__text')]"

View File

@ -19,27 +19,23 @@ class EventPanelLocators:
TAB_AUDIT (str): кнопки Аудит. TAB_AUDIT (str): кнопки Аудит.
BUTTONS_EVENT (str): блока кнопок-счетчиков событий. BUTTONS_EVENT (str): блока кнопок-счетчиков событий.
BUTTON_USER (str): кнопки текущего пользователя. BUTTON_USER (str): кнопки текущего пользователя.
CONTAINER_ACTIONS_TAB (str): контейнера для отображения событий вкладки Действия.
CONTAINER_EVENTS_TAB (str): контейнера для отображения событий вкладки События.
CONTAINER_MAINTENANCE_EVENTS (str): контейнера для отображения событий обслуживания.
CONTAINER_SYSTEM_LOG_EVENTS (str): контейнера с событиями Системного журнала. CONTAINER_SYSTEM_LOG_EVENTS (str): контейнера с событиями Системного журнала.
CONTAINER_AUDIT_EVENTS (str): контейнера для отображения событий аудита.
""" """
AREA_EVENTS = "#app > div.application--wrap > div > div:nth-child(1)" AREA_EVENTS = "#app > div.application--wrap > div > div:nth-child(3)"
BUTTON_EXPAND_LESS = "//button[contains(@data-testid, 'BASELINE__btn__toolbar_close')]" BUTTON_EXPAND_LESS = "//button[contains(@data-testid, 'BASELINE__btn__toolbar_close')]"
BUTTON_EXPAND_MORE = "//button[contains(@data-testid, 'BASELINE__btn__toolbar_open')]" BUTTON_EXPAND_MORE = "//button[contains(@data-testid, 'BASELINE__btn__toolbar_open')]"
TABS_TOOLBAR = "//div[@data-testid='BASELINE__tabs__toolbar']"
TAB_STATES = "//div[@data-testid='BASELINE__states_tab__toolbar']" TAB_STATES = "//div[@data-testid='BASELINE__states_tab__toolbar']"
TAB_ACTIONS = "//div[@data-testid='BASELINE__actions_tab__toolbar']" TAB_ACTIONS = "//div[@data-testid='BASELINE__actions_tab__toolbar']"
TAB_EVENTS = "//div[@data-testid='BASELINE__events_tab__toolbar']" TAB_EVENTS = "//div[@data-testid='BASELINE__events_tab__toolbar']"
TAB_MAINTENANCE = "//div[@data-testid='BASELINE__service_tab__toolbar']" TAB_MAINTENANCE = "//div[@data-testid='BASELINE__service_tab__toolbar']"
TAB_SYSTEM_LOG = "//div[@data-testid='BASELINE__system journal_tab__toolbar']" TAB_SYSTEM_LOG = "//div[@data-testid='BASELINE__system journal_tab__toolbar']"
TAB_AUDIT = "//div[@data-testid='BASELINE__audit_tab__toolbar']" TAB_AUDIT = "//div[@data-testid='BASELINE__audit_tab__toolbar']"
TAB_INFORMATION_SECURITY = "//div[@data-testid='BASELINE__information security_tab__toolbar']"
BUTTONS_EVENT = "//button[@data-testid='BASELINE__btn__user']/preceding-sibling::div//span[contains(@class, 'v-tooltip')]" BUTTONS_EVENT = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]//span[contains(@class, 'v-tooltip')]"
BUTTON_USER = "//button[@data-testid='BASELINE__btn__user']" BUTTON_USER = "//button[@data-testid='BASELINE__btn__user']"
@ -59,3 +55,4 @@ class EventPanelLocators:
CONTAINER_MAINTENANCE_EVENTS = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(4)" CONTAINER_MAINTENANCE_EVENTS = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(4)"
CONTAINER_SYSTEM_LOG_EVENTS = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(5)" CONTAINER_SYSTEM_LOG_EVENTS = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(5)"
CONTAINER_AUDIT_EVENTS = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(6)" CONTAINER_AUDIT_EVENTS = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(6)"
CONTAINER_INFORMATION_SECURITY = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(6)"

View File

@ -10,6 +10,10 @@ class InputLocators:
Содержит XPath локаторы для: Содержит XPath локаторы для:
LICENSE_ID_UPDATE (str): поля ввода идентификатора лицензии в подвале LICENSE_ID_UPDATE (str): поля ввода идентификатора лицензии в подвале
""" """
LICENSE_ID_UPDATE = "//div[@class='v-input__control']//textarea[@data-testid='LICENSE__textarea__licenseKey']" LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea"

View File

@ -13,4 +13,4 @@ class JsonContainerLocators:
""" """
CONTAINER = "//div[contains(@class,'jv-container')]" CONTAINER = "//div[contains(@class,'jv-container')]"
SCROLL_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/../following-sibling::div//div[contains(@class,'scrollarea__body')]" SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]"

View File

@ -23,28 +23,8 @@ class ModalWindowLocators:
MODAL_WINDOW_TITLE = f"{MODAL_WINDOW}//div[contains(@class, 'v-toolbar__title')]" MODAL_WINDOW_TITLE = f"{MODAL_WINDOW}//div[contains(@class, 'v-toolbar__title')]"
MODAL_WINDOW_TEXT_FIELD_INPUT = f"{MODAL_WINDOW}//input" MODAL_WINDOW_TEXT_FIELD_INPUT = f"{MODAL_WINDOW}//input"
INPUT_FORM_USER_DATA = f"{MODAL_WINDOW}//form[@class='v-form']" INPUT_FORM_USER_DATA = "//form[@class='v-form']"
INPUT_FORM_USER_DATA_FIELD_NAME = "//input[@data-testid='USER_CARD__text-field__name']" TEXT_FIELD_INPUT_FORM_USER_DATA = "xpath=div[2]/div/div/div/div/input"
INPUT_FORM_USER_DATA_FIELD_ROLE = "//input[@data-testid='USER_CARD__select__role']" MENU_INPUT_FORM_USER_DATA = "//div[contains(@class, 'menuable__content__active')]"
INPUT_FORM_USER_DATA_FIELD_PASSWORD = "//input[@data-testid='USER_CARD__text-field__password']"
INPUT_FORM_USER_DATA_FIELD_COMMENT = "//input[@data-testid='USER_CARD__text-field__comment']"
INPUT_FORM_USER_DATA_FIELD_EMAIL = "//input[@data-testid='USER_CARD__text-field__email']"
INPUT_FORM_USER_DATA_FIELD_SMS = "//input[@data-testid='USER_CARD__text-field__sms_phone']"
INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED = "//input[@data-testid='USER_CARD__checkbox__blocked']"
INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE = "//input[@data-testid='USER_CARD__checkbox__push_active']"
# TEXT_FIELD_INPUT_FORM_USER_DATA = "div[2]/div/div/div/div/input"
MENU_ACTIVE_INPUT_FORM = "//div[contains(@class, 'menuable__content__active')]"
MENU_ACTIVE_ITEMS = "//div[@role='list']//div[@role='listitem']"
LABEL_INPUT_FORM_USER_DATA = "//label[contains(@class,'v-label')]/span" LABEL_INPUT_FORM_USER_DATA = "//label[contains(@class,'v-label')]/span"
TASK_MODAL_WINDOW = "//div[@data-testid='BASELINE__dialog-drag__modal_0']"
CHANDE_PASSWORD_WINDOW_CURRENT_PASSWORD = "//input[@data-testid='CHANGE_PASS_CARD__text-field__current_password']"
CHANDE_PASSWORD_WINDOW_NEW_PASSWORD = "//input[@data-testid='CHANGE_PASS_CARD__text-field__new_password']"
CHANDE_PASSWORD_WINDOW_CHECK_PASSWORD = "//input[@data-testid='CHANGE_PASS_CARD__text-field__check_password']"
CHANDE_PASSWORD_WINDOW_BUTTON_SAVE = "//button[@data-testid='CHANGE_PASS_CARD__btn__save']"
CHANDE_PASSWORD_WINDOW_BUTTON_CANCEL = "//button[@data-testid='CHANGE_PASS_CARD__btn__cancel']"

View File

@ -29,6 +29,3 @@ class NavigationPanelLocators:
NODE_ROOT = "//div[contains(@class,'v-treeview-node__root')]" NODE_ROOT = "//div[contains(@class,'v-treeview-node__root')]"
NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]" NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]"
TOGGLE_BUTTON = "//i[contains(@class,'v-treeview-node__toggle')]" TOGGLE_BUTTON = "//i[contains(@class,'v-treeview-node__toggle')]"
BUTTON_EXPAND_WORKAREA = "//button[@data-testid='BASELINE__btn__leftBarMini']"
BUTTON_REDUCE_WORKAREA = "//button[@data-testid='BASELINE__btn__!leftBarMini']"

View File

@ -28,54 +28,33 @@ class RackLocators:
ACTIVE_TAB = ("//div[@data-testid='CABINET_SHOW__tabs']" ACTIVE_TAB = ("//div[@data-testid='CABINET_SHOW__tabs']"
"//a[contains(@class, 'v-tabs__item--active')]") "//a[contains(@class, 'v-tabs__item--active')]")
# ================ ЛОКАТОРЫ ДЛЯ ФОРМЫ СОЗДАНИЯ СТОЙКИ =================== # Контейнер формы создания/редактирования стойки
FORM_INPUT_CONTAINER = "//div[contains(@class, 'flex xs6 pa-0')]"
# Контейнер формы создания стойки
CREATE_RACK_FORM_CONTAINER = "//div[contains(@class, 'flex xs6 pa-0')]"
# Text
CREATE_RACK_FORM_FIELD_NAME = "[data-testid='create-location-bar__text-field__name']"
CREATE_RACK_FORM_FIELD_COMMENT = "[data-testid='create-location-bar__text-field__comment']"
CREATE_RACK_FORM_FIELD_SERIAL = "[data-testid='create-location-bar__text-field__serial_number']"
CREATE_RACK_FORM_FIELD_INVENTORY = "[data-testid='create-location-bar__text-field__inventory_number']"
# Сombobox
CREATE_RACK_FORM_SELECT_USIZE = "[data-testid='create-location-bar__select__usize']"
CREATE_RACK_FORM_SELECT_DEPTH = "[data-testid='create-location-bar__select__depth']"
CREATE_RACK_FORM_SELECT_CABLE_INPUT = "[data-testid='create-location-bar__select__cable_input']"
CREATE_RACK_FORM_SELECT_CONDITION_TYPE = "[data-testid='create-location-bar__select__condition_type']"
CREATE_RACK_FORM_SELECT_OWNER = "[data-testid='create-location-bar__select__owner']"
CREATE_RACK_FORM_SELECT_SERVICE_PROVIDER = "[data-testid='create-location-bar__select__service_provider']"
CREATE_RACK_FORM_SELECT_PROJECT = "[data-testid='create-location-bar__select__project']"
# ================ ЛОКАТОРЫ ДЛЯ ФОРМЫ РЕДАКТИРОВАНИЯ СТОЙКИ ===================
# Форма редактирования стойки в модальном окне # Форма редактирования стойки в модальном окне
EDIT_RACK_FORM = "[data-testid='cabinet-bar__cabinet-form']" RACK_EDIT_FORM = "[data-testid='cabinet-bar__cabinet-form']"
# Text # Локаторы полей формы
EDIT_RACK_FORM_FIELD_NAME = "[data-testid='cabinet-bar__main__text-field__name']" INPUT_FORM_RACK_DATA = f"{RACK_EDIT_FORM}"
EDIT_RACK_FORM_FIELD_COMMENT = "[data-testid='cabinet-bar__main__text-field__comment']" INPUT_FORM_RACK_DATA_FIELD_NAME = "[data-testid='cabinet-bar__main__text-field__name']"
EDIT_RACK_FORM_FIELD_SERIAL = "[data-testid='cabinet-bar__main__text-field__serial_number']" INPUT_FORM_RACK_DATA_FIELD_COMMENT = "[data-testid='cabinet-bar__main__text-field__comment']"
EDIT_RACK_FORM_FIELD_INVENTORY = "[data-testid='cabinet-bar__main__text-field__inventory_number']" INPUT_FORM_RACK_DATA_FIELD_SERIAL = "[data-testid='cabinet-bar__main__text-field__serial_number']"
EDIT_RACK_FORM_FIELD_POWER = "[data-testid='cabinet-bar__main__text-field__allocated_power']" INPUT_FORM_RACK_DATA_FIELD_INVENTORY = "[data-testid='cabinet-bar__main__text-field__inventory_number']"
INPUT_FORM_RACK_DATA_FIELD_POWER = "[data-testid='cabinet-bar__main__text-field__allocated_power']"
# Сombobox # Локаторы для combobox полей
EDIT_RACK_FORM_SELECT_CABLE_INPUT = "[data-testid='cabinet-bar__select_enum__select-field__cable_input']" INPUT_FORM_RACK_DATA_FIELD_CABLE_ENTRY = "[data-testid='cabinet-bar__select_enum__select-field__cable_input']"
EDIT_RACK_FORM_SELECT_CONDITION_TYPE = "[data-testid='cabinet-bar__select_enum__select-field__condition_type']" INPUT_FORM_RACK_DATA_FIELD_CONDITION_TYPE = "[data-testid='cabinet-bar__select_enum__select-field__condition_type']"
EDIT_RACK_FORM_SELECT_DEPTH = "[data-testid='cabinet-bar__select_enum__select-field__depth']" INPUT_FORM_RACK_DATA_FIELD_DEPTH = "[data-testid='cabinet-bar__select_enum__select-field__depth']"
EDIT_RACK_FORM_SELECT_USIZE = "[data-testid='cabinet-bar__select_enum__select-field__usize']" INPUT_FORM_RACK_DATA_FIELD_USIZE = "[data-testid='cabinet-bar__select_enum__select-field__usize']"
EDIT_RACK_FORM_SELECT_OWNER = "[data-testid='cabinet-bar__select__select-field__owner']" INPUT_FORM_RACK_DATA_FIELD_OWNER = "[data-testid='cabinet-bar__select__select-field__owner']"
EDIT_RACK_FORM_SELECT_SERVICE_PROVIDER = "[data-testid='cabinet-bar__select__select-field__service_provider']" INPUT_FORM_RACK_DATA_FIELD_SERVICE_PROVIDER = "[data-testid='cabinet-bar__select__select-field__service_provider']"
EDIT_RACK_FORM_SELECT_PROJECT = "[data-testid='cabinet-bar__select__select-field__project']" INPUT_FORM_RACK_DATA_FIELD_PROJECT = "[data-testid='cabinet-bar__select__select-field__project']"
# Checkbox # Чекбоксы
EDIT_RACK_FORM_CHECKBOX_VENTILATION = "[data-testid='cabinet-bar__main__checkbox__available_ventilation_panel'] input[type='checkbox']" INPUT_FORM_RACK_DATA_CHECKBOX_VENTILATION = "[data-testid='cabinet-bar__main__checkbox__available_ventilation_panel'] input[type='checkbox']"
EDIT_RACK_FORM_CHECKBOX_VENTILATION_LABEL = "label:has-text('Вентиляционная панель')" INPUT_FORM_RACK_DATA_CHECKBOX_VENTILATION_LABEL = "label:has-text('Вентиляционная панель')"
EDIT_RACK_FORM_DATA_CHECKBOX_VENTILATION_CONTAINER = "[data-testid='cabinet-bar__main__checkbox__available_ventilation_panel']" INPUT_FORM_RACK_DATA_CHECKBOX_VENTILATION_CONTAINER = "[data-testid='cabinet-bar__main__checkbox__available_ventilation_panel']"
# ================ ЛОКАТОРЫ ДЛЯ ВЫПАДАЮЩИХ СПИСКОВ ===================
# Локаторы для меню combobox # Локаторы для меню combobox
MENU_ACTIVE_RACK_FORM = "//div[contains(@class, 'menuable__content__active')]" MENU_ACTIVE_RACK_FORM = "//div[contains(@class, 'menuable__content__active')]"

View File

@ -19,9 +19,8 @@ class SelectionBarLocators:
PARAMETERS_SELECTED = "div.v-select__selections" PARAMETERS_SELECTED = "div.v-select__selections"
# Локаторы для элементов выпадающего списка # Локаторы для элементов выпадающего списка
LIST_ACTIVE = "//div[contains(@class, 'menuable__content__active')]"
LISTBOX = "//div[@role='list']" LISTBOX = "//div[@role='list']"
LIST_ITEMS = "//div[contains(@class, 'menuable__content__active')]//div[@role='list']//div[@role='listitem']" LIST_ITEMS = "//div[@role='list']//div[@role='listitem']"
# Локатор для родительского контейнера поля ввода # Локатор для родительского контейнера поля ввода
INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]" INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]"

View File

@ -18,7 +18,7 @@ class SettingsFormLocators:
SETTTINGS_FORM_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]" SETTTINGS_FORM_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]"
SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]" SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]"
SETTINGS_FORM_INPUT_FORM_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/../following-sibling::div" SETTINGS_FORM_INPUT_FORM_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/following-sibling::div"
SETTINGS_FORM_INPUT_FIELD = "div.v-text-field__slot > input" SETTINGS_FORM_INPUT_FIELD = "div.v-text-field__slot > input"
SETTINGS_FORM_INPUT_VALUE_SUFFIX = ".v-text-field__suffix" SETTINGS_FORM_INPUT_VALUE_SUFFIX = ".v-text-field__suffix"
@ -26,5 +26,3 @@ class SettingsFormLocators:
DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]" DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]"
SELECTED_VALUES = "//div[@class='v-select__selections']" SELECTED_VALUES = "//div[@class='v-select__selections']"
CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear" CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear"
PUSH_NOTIFICATIONS_BUTTON_SUBMIT = "//button[@data-testid='PUSH_NOTIFICATIONS__btn__submit']"

View File

@ -9,13 +9,15 @@ class UserCardLocators:
Содержит XPath локаторы для: Содержит XPath локаторы для:
CARD_USER (str): карточки текущего пользователя. CARD_USER (str): карточки текущего пользователя.
BUTTON_LOGOUT (str): кнопка выхода из приложения. DIALOG_USER_SETTINGS (str): окна просмотра сессионных данных пользователей.
BUTTON_CHANGE_PASSWORD (str): кнопка открытия окна смены пароля. HEADER_DIALOG_USER_SETTINGS (str): строки с заголовком окна и кнопкой закрытия.
BUTTON_CLOSE (str): кнопка закрытия окна текущего пользователя. TITLE_DIALOG_USER_SETTINGS (str): заголовка окна.
TABLE_WORK_AREA (str): таблицы с сессионными данными пользователей.
""" """
# CARD_USER = "//div[@class='v-card__text']" CARD_USER = "//div[@class='v-card__text']"
CARD_USER = "//div[@data-testid='BASELINE__card__user']"
BUTTON_LOGOUT = "//button[@data-testid='BASELINE__btn__user.menu__logout']" DIALOG_USER_SETTINGS = "//div[@class='dialog-drag']"
BUTTON_CHANGE_PASSWORD = "//button[@data-testid='BASELINE__btn__user.menu__change_password']" HEADER_DIALOG_USER_SETTINGS = "xpath=/div[@class='dialog-header']"
BUTTON_CLOSE = "//button[@data-testid='BASELINE__btn__user.menu__close']" TITLE_DIALOG_USER_SETTINGS = "xpath=/div[@class='dialog-header']/div[@class='title']"
TABLE_WORK_AREA = "//div[@class='dialog-body']//table"

File diff suppressed because it is too large Load Diff

View File

@ -1,178 +0,0 @@
"""Модуль вкладки 'Сертификаты'.
Содержит класс CertificatesTab для работы с вкладкой 'Сертификаты'.
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
"""
from playwright.sync_api import Page
from locators.certificate_locators import CertificateLocators
from elements.tab_button_element import TabButton
from components.toolbar_custom_component import CustomToolbar
from components.alert_component import AlertComponent
from components_derived.view_certificate_form import ViewCertificateForm
from components_derived.reissue_certificate_form import ReissueCertificateForm
from components_derived.import_certificate_form import ImportCertificateForm
from pages.base_page import BasePage
class CertificatesTab(BasePage):
"""Класс для работы с вкладкой 'Сертификаты'.
Предоставляет методы для взаимодействия с вкладкой 'Сертификаты'.
Args:
page: Экземпляр страницы Playwright.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки настройки резервного копирования."""
super().__init__(page)
self.toolbar_main = CustomToolbar(page)
self.toolbar_secondary = CustomToolbar(page)
self.tab_button_certificate = TabButton(page, CertificateLocators.TAB_CERTIFICATE_CA, "tab_button_certificate")
self.tab_button_reissue = TabButton(page, CertificateLocators.TAB_REISSUE_CA, "tab_button_reissue")
self.tab_button_import = TabButton(page, CertificateLocators.TAB_IMPORT_CA, "tab_button_import")
self.view_certificate_form = ViewCertificateForm(page)
self.reissue_certificate_form = ReissueCertificateForm(page)
self.import_certificate_form = ImportCertificateForm(page)
self.alert = AlertComponent(page)
# Действия:
def click_certificate_tab_button(self) -> None:
"""Выполняет нажатие tab-кнопки 'Сертификат CA'."""
self.tab_button_certificate.check_visibility("'Сертификат CA' tab button is missing")
self.tab_button_certificate.click()
assert self.tab_button_certificate.is_active(), "'Сертификат CA' tab button should be active"
def click_reissue_tab_button(self) -> None:
"""Выполняет нажатие tab-кнопки 'Пересоздание CA'."""
self.tab_button_reissue.check_visibility("'Пересоздание CA' tab button is missing")
self.tab_button_reissue.click()
assert self.tab_button_reissue.is_active(), "'Пересоздание CA' tab button should be active"
def click_import_tab_button(self) -> None:
"""Выполняет нажатие tab-кнопки 'Import ca (p12)'."""
self.tab_button_import.check_visibility("'Import ca (p12)' tab button is missing")
self.tab_button_import.click()
assert self.tab_button_import.is_active(), "'Import ca (p12)' tab button should be active"
def get_certificate(self) -> dict:
""" Возвращает значания полей отображаемого сертификата"""
return self.view_certificate_form.get_certificate()
def get_identification_fields_values(self) -> dict:
"""Возвращает текущее значение полей блока 'Идентификация CA' формы для пересоздания сертификата.
Returns:
dict : Текущее значение полей блока 'Идентификация CA' формы для пересоздания сертификата.
"""
return self.reissue_certificate_form.get_identification_fields_values()
def get_location_fields_values(self) -> dict:
"""Возвращает текущее значение полей блока 'Адрес / Местонахождение' формы для пересоздания сертификата.
Returns:
dict : Текущее значение полей блока блока 'Адрес / Местонахождение' формы для пересоздания сертификата.
"""
return self.reissue_certificate_form.get_location_fields_values()
def get_password_field_value(self) -> str:
"""Возвращает текущее значение поля 'Пароль' формы импорта сертификата.
Returns:
str : Текущее значение поля 'Пароль' формы импорта сертификата.
"""
return self.import_certificate_form.get_password_field_value()
def export_certificate(self) -> str:
"""Нажатие кнопки 'Экспорт сертификата (CA)' в форме отображения сертификата и
скачивание текущего корневого сертификата.
Returns:
str : Полный путь к скачанному файлу.
"""
return self.view_certificate_form.export_certificate()
def input_identification_cert_name_field(self, value: str) -> None:
"""Заполнение поля 'Имя Сертификата' блока 'Идентификация CA' формы для пересоздания сертификата"""
self.reissue_certificate_form.input_identification_cert_name_field(value)
def input_identification_organization_field(self, value: str) -> None:
"""Заполнение поля 'Организация' блока 'Идентификация CA' формы для пересоздания сертификата"""
self.reissue_certificate_form.input_identification_organization_field(value)
def input_identification_org_unit_field(self, value: str) -> None:
"""Заполнение поля 'Подразделение' блока 'Идентификация CA' формы для пересоздания сертификата"""
self.reissue_certificate_form.input_identification_org_unit_field(value)
def input_location_country_field(self, value: str) -> None:
"""Заполнение поля 'Страна' блока 'Адрес / Местонахождение' формы для пересоздания сертификата"""
self.reissue_certificate_form.input_location_country_field(value)
def input_location_state_field(self, value: str) -> None:
"""Заполнение поля 'Регион / Область' блока 'Адрес / Местонахождение' формы для пересоздания сертификата"""
self.reissue_certificate_form.input_location_state_field(value)
def input_location_city_field(self, value: str) -> None:
"""Заполнение поля 'Город' блока 'Адрес / Местонахождение' формы для пересоздания сертификата"""
self.reissue_certificate_form.input_location_city_field(value)
def input_password_field(self, value: str) -> None:
"""Заполнение поля 'Пароль' формы импорта сертификата"""
self.import_certificate_form.input_password_field(value)
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов страницы."""
self.toolbar_main.check_toolbar_presence(['Сертификаты'])
self.toolbar_secondary.check_toolbar_presence(['Центр сертификации (CA)',
'Управление корневым сертификатом системы'])
self.click_certificate_tab_button()
self.view_certificate_form.check_content()
self.click_reissue_tab_button()
self.reissue_certificate_form.check_content()
self.click_import_tab_button()
self.import_certificate_form.check_content()
def check_alert(self, alert_type: str, alert_text: str) -> None:
"""Проверяет наличие alert заданного типа и текста."""
actual_alert_type = self.alert.get_alert_type()
assert actual_alert_type == alert_type, f"Got unexpected alert type {actual_alert_type}"
self.alert.check_alert_presence(alert_text)
self.alert.check_alert_absence(alert_text)
def is_import_button_disabled(self) -> bool:
"""Проверяет доступность кнопки импорта сертификата."""
return self.import_certificate_form.is_import_button_disabled()
def is_reissue_button_disabled(self) -> bool:
"""Проверяет доступность кнопки перевыпуска сертификата."""
return self.reissue_certificate_form.is_reissue_button_disabled()

View File

@ -1,484 +0,0 @@
"""Модуль вкладки настройки E-mail уведомлений.
Содержит класс SMSNotificationsSettings для работы с вкладкой настройки E-mail уведомлений.
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
"""
import re
from playwright.sync_api import Page
from locators.settings_form_locators import SettingsFormLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.tooltip_button_element import TooltipButton
from elements.checkbox_element import Checkbox
from components.toolbar_component import ToolbarComponent
from components.alert_component import AlertComponent
from components_derived.settings_form_component import SettingsFormComponent
from components_derived.selection_bar_component import SelectionBarComponent
from components_derived.modal_send_test_email import SendTestEmailModalWindow
from pages.base_page import BasePage
class EmailNotificationsSettingsTab(BasePage):
"""Класс для работы с вкладкой настройки E-mail уведомлений.
Предоставляет методы для взаимодействия с вкладкой настройки E-mail уведомлений.
Args:
page: Экземпляр страницы Playwright.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки настройки E-mail уведомлений."""
super().__init__(page)
self.toolbar = ToolbarComponent(page, "e-mail")
toolbar_button_edit = self.page.get_by_role("navigation").filter(has_text=re.compile("e-mail")). \
locator("//button[@data-testid='NOTIFICATIONS_SMTP__btn__edit']")
self.toolbar.add_tooltip_button(toolbar_button_edit, "edit")
toolbar_button_save = self.page.get_by_role("navigation").filter(has_text=re.compile("e-mail")). \
locator("//button[@data-testid='NOTIFICATIONS_SMTP__btn__submit']")
self.toolbar.add_tooltip_button(toolbar_button_save, "save")
toolbar_button_cancel = self.page.get_by_role("navigation").filter(has_text=re.compile("e-mail")). \
locator("//button[@data-testid='NOTIFICATIONS_SMTP__btn__cancelEdit']")
self.toolbar.add_tooltip_button(toolbar_button_cancel, "cancel")
# Форма для отображения/редактирования общих полей настроек
self.common_settings = SettingsFormComponent(page)
self.common_settings.add_toolbar_title(" Общие ")
container_locator = self.page.locator("//nav[contains(@class, 'active v-toolbar')]"). \
filter(has_text=" Общие ").locator("//following-sibling::div")
self.common_input_fields_locators = self.common_settings.get_input_fields_locators(container_locator)
loc = self.common_input_fields_locators.get("Сервер")
loc_server_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__common_text-field__smtp_host']")
server_setting_input = TextInput(page, loc_server_input, "server_setting_input")
self.common_settings.add_content_item("server_setting_input", server_setting_input)
loc = self.common_input_fields_locators.get("Порт")
loc_port_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__common_text-field__smtp_port']")
port_setting_input = TextInput(page, loc_port_input, "port_setting_input")
self.common_settings.add_content_item("port_setting_input", port_setting_input)
loc = self.common_input_fields_locators.get("Отправитель")
loc_sender_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__common_text-field__from_email']")
sender_setting_input = TextInput(page, loc_sender_input, "sender_setting_input")
self.common_settings.add_content_item("sender_setting_input", sender_setting_input)
field_locator = container_locator.locator("//div[@data-testid='NOTIFICATIONS_SMTP__common_checkbox__active']")
# Метка "Активировать"
label_activate_locator = field_locator.locator("//label").get_by_text("Активировать")
label_activate = Text(page, label_activate_locator, "checkbox_activate_label")
self.common_settings.add_content_item("checkbox_activate_label", label_activate)
# Чекбокс "Активировать"
checkbox_activate = Checkbox(page,
field_locator.locator("//input[@data-testid='NOTIFICATIONS_SMTP__common_checkbox__active']"),
"activate"
)
self.common_settings.add_content_item("checkbox_activate", checkbox_activate)
# Форма для отображения/редактирования общих настроек TLS
self.tls_settings = SettingsFormComponent(page)
self.tls_settings.add_toolbar_title(" TLS ")
container_locator = self.page.locator("//nav[contains(@class, 'active v-toolbar')]"). \
filter(has_text=" TLS ").locator("//following-sibling::div")
self.tls_input_fields_locators = self.tls_settings.get_input_fields_locators(container_locator)
loc = self.tls_input_fields_locators.get("Использовать TLS-туннель")
self.tls_settings.add_content_item("tunnel_setting_selector", SelectionBarComponent(page, loc))
field_locator = container_locator. \
locator("//div[@data-testid='NOTIFICATIONS_SMTP__TLS_checkbox__reject_unauthorized']")
# Метка "Принимать самоподписанные сертификаты"
label_accept_certificates_locator = field_locator. \
locator("//label").get_by_text("Принимать самоподписанные сертификаты")
label_accept_certificates = Text(page, label_accept_certificates_locator, "checkbox_accept_certificates_label")
self.tls_settings.add_content_item("checkbox_accept_certificates_label", label_accept_certificates)
# Чекбокс "Принимать самоподписанные сертификаты"
checkbox_accept_certificates = Checkbox(page,
field_locator.locator("//input[@data-testid='NOTIFICATIONS_SMTP__TLS_checkbox__reject_unauthorized']"),
"accept_certificates"
)
self.tls_settings.add_content_item("checkbox_accept_certificates", checkbox_accept_certificates)
# Форма для отображения/редактирования полей настроек аутентификации
self.auth_settings = SettingsFormComponent(page)
self.auth_settings.add_toolbar_title(" Аутентификация ")
container_locator = self.page.locator("//nav[contains(@class, 'active v-toolbar')]"). \
filter(has_text=" Аутентификация ").locator("//following-sibling::div")
self.auth_input_fields_locators = self.auth_settings.get_input_fields_locators(container_locator)
loc = self.auth_input_fields_locators.get("Метод авторизации")
self.auth_settings.add_content_item("auth_method_setting_selector", SelectionBarComponent(page, loc))
loc = self.auth_input_fields_locators.get("Имя пользователя")
loc_user_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__auth_text-field__login']")
user_setting_input = TextInput(page, loc_user_input, "user_setting_input")
self.auth_settings.add_content_item("user_setting_input", user_setting_input)
loc = self.auth_input_fields_locators.get("Пароль")
loc_password_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__auth_text-field__password']")
password_setting_input = TextInput(page, loc_password_input, "password_password_input")
self.auth_settings.add_content_item("password_setting_input", password_setting_input)
loc = self.auth_input_fields_locators.get("Домен")
loc_domain_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__auth_text-field__domain']")
domain_setting_input = TextInput(page, loc_domain_input, "domain_setting_input")
self.auth_settings.add_content_item("domain_setting_input", domain_setting_input)
loc = self.auth_input_fields_locators.get("Рабочая станция")
loc_workstation_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMTP__auth_text-field__workstation']")
workstation_setting_input = TextInput(page, loc_workstation_input, "workstation_setting_input")
self.auth_settings.add_content_item("workstation_setting_input", workstation_setting_input)
# Кнопка 'Тест'
self.test_button = TooltipButton(page,
page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
locator("//button[@data-testid='NOTIFICATIONS_SMTP__common_btn__test']"),
"test_button")
self.alert = AlertComponent(page)
# Действия:
def check_checkbox_activate(self):
"""Включает чек-бокс Активировать."""
self.common_settings.get_content_item("checkbox_activate").check(force=True)
def uncheck_checkbox_activate(self):
"""Выключает чек-бокс Активировать."""
self.common_settings.get_content_item("checkbox_activate").uncheck(force=True)
def check_checkbox_accept_certificates(self):
"""Включает чек-бокс Принимать самоподписанные сертификаты."""
self.tls_settings.get_content_item("checkbox_accept_certificates").check(force=True)
def uncheck_checkbox_accept_certificates(self):
"""Выключает чек-бокс Принимать самоподписанные сертификаты."""
self.tls_settings.get_content_item("checkbox_accept_certificates").uncheck(force=True)
def click_cancel_button(self) -> None:
"""Нажатие кнопки 'Отменить' на тулбаре."""
self.toolbar.check_button_visibility("cancel")
self.toolbar.get_button_by_name("cancel").click()
def click_edit_button(self) -> None:
"""Нажатие кнопки 'Редактировать' на тулбаре."""
self.toolbar.check_button_visibility("edit")
self.toolbar.get_button_by_name("edit").click()
def click_save_button(self) -> None:
"""Нажатие кнопки 'Сохранить' на тулбаре."""
self.toolbar.check_button_visibility("save")
self.toolbar.get_button_by_name("save").click()
def click_test_button(self) -> SendTestEmailModalWindow:
"""Нажатие кнопки 'Тест' в форме ввода настроек."""
self.should_be_test_button()
self.test_button.click()
return SendTestEmailModalWindow(self.page)
def clear_auth_method_settings(self) -> None:
"""Удаление ранее выбранных значений"""
auth_method_selector = self.auth_settings.get_content_item("auth_method_setting_selector")
auth_method_selector.clear_selections()
def clear_tls_tunnel_settings(self) -> None:
"""Удаление ранее выбранных значений"""
tls_tunnel_selector = self.tls_settings.get_content_item("tunnel_setting_selector")
tls_tunnel_selector.clear_selections()
def get_auth_settings_values(self) -> dict:
"""Возвращает текущее значение полей настроек 'Аутентификация'.
Returns:
dict : Текущее значение полей настроек 'Аутентификация'.
"""
values = {}
auth_method_selector = self.auth_settings.get_content_item("auth_method_setting_selector")
if auth_method_selector:
val = auth_method_selector.get_selected_values()
values.update({"Метод авторизации": val[0]})
else:
values.update({"Метод авторизации": ""})
field = self.auth_settings.get_content_item("user_setting_input")
values.update({"Имя пользователя": field.get_input_value().strip()})
field = self.auth_settings.get_content_item("password_setting_input")
values.update({"Пароль": field.get_input_value().strip()})
field = self.auth_settings.get_content_item("domain_setting_input")
values.update({"Домен": field.get_input_value().strip()})
field = self.auth_settings.get_content_item("workstation_setting_input")
values.update({"Рабочая станция": field.get_input_value().strip()})
return values
def get_common_settings_values(self) -> dict:
"""Возвращает текущее значение полей настроек 'Общие'.
Returns:
dict : Текущее значение полей настроек 'Общие'.
"""
values = {}
field = self.common_settings.get_content_item("server_setting_input")
values.update({"Сервер": field.get_input_value().strip()})
field = self.common_settings.get_content_item("port_setting_input")
values.update({"Порт": field.get_input_value().strip()})
field = self.common_settings.get_content_item("sender_setting_input")
values.update({"Отправитель": field.get_input_value().strip()})
return values
def get_tls_tunnel_setting_value(self) -> str | None:
"""Возвращает текущее значение поля 'Использовать TLS-туннель'"""
tls_tunnel_settings = None
tls_tunnel_selector = self.tls_settings.get_content_item("tunnel_setting_selector")
if tls_tunnel_selector:
values = tls_tunnel_selector.get_selected_values()
tls_tunnel_settings = values[0]
return tls_tunnel_settings
def input_server(self, text: str) -> None:
"""Заполнение поля 'Сервер' настроек 'Общие'."""
message_input = self.common_settings.get_content_item("server_setting_input")
message_input.clear()
message_input.input_value(text)
def input_port(self, text: str) -> None:
"""Заполнение поля 'Порт' настроек 'Общие'."""
message_input = self.common_settings.get_content_item("port_setting_input")
message_input.clear()
message_input.input_value(text)
def input_sender(self, text: str) -> None:
"""Заполнение поля 'Отправитель' настроек 'Общие'."""
message_input = self.common_settings.get_content_item("sender_setting_input")
message_input.clear()
message_input.input_value(text)
def input_user_name(self, text: str) -> None:
"""Заполнение поля 'Имя пользователя' настроек 'Аутентификация'."""
message_input = self.auth_settings.get_content_item("user_setting_input")
message_input.clear()
message_input.input_value(text)
def input_password(self, text: str) -> None:
"""Заполнение поля 'Пароль' настроек 'Аутентификация'."""
message_input = self.auth_settings.get_content_item("password_setting_input")
message_input.clear()
message_input.input_value(text)
def input_domain(self, text: str) -> None:
"""Заполнение поля 'Домен' настроек 'Аутентификация'."""
message_input = self.auth_settings.get_content_item("domain_setting_input")
message_input.clear()
message_input.input_value(text)
def input_workstation(self, text: str) -> None:
"""Заполнение поля 'Рабочая станция' настроек 'Аутентификация'."""
message_input = self.auth_settings.get_content_item("workstation_setting_input")
message_input.clear()
message_input.input_value(text)
def select_auth_method_setting(self, auth_method_setting: str) -> None:
"""Выбирает заданное значение поля 'Метод авторизации' из списка"""
auth_method_selector = self.auth_settings.get_content_item("auth_method_selector")
if auth_method_selector:
auth_method_selector.open_values_list()
auth_method_selector.select_value(auth_method_setting)
def select_tls_tunnel_setting(self, tls_tunnel_setting: str) -> None:
"""Выбирает заданное значение поля 'Использовать TLS-туннель' из списка"""
tls_tunnel_selector = self.tls_settings.get_content_item("tunnel_setting_selector")
if tls_tunnel_selector:
tls_tunnel_selector.open_values_list()
tls_tunnel_selector.select_value(tls_tunnel_setting)
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов страницы."""
self.should_be_toolbar()
self._check_common_settings_content()
self._check_tls_settings_content()
self._check_auth_settings_content()
self.should_be_test_button()
tooltip_text = self.test_button.get_tooltip_text()
assert tooltip_text == "Тест", "Should be 'Тест' tooltip for test button"
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если тулбар или кнопка тулбара отсутствуют.
"""
loc = self.page.get_by_role("navigation").filter(
has_text=re.compile("e-mail")).locator("div").nth(1)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'E-MAIL' is missing")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Редактировать")
self.toolbar.get_button_by_name("edit").click()
self.toolbar.check_button_visibility("save")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("save", "Сохранить")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.get_button_by_name("cancel").click()
self.toolbar.check_button_visibility("edit")
def should_be_test_button(self) -> None:
"""Проверяет наличие кнопки 'Тест'.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.test_button.check_visibility("Test button is missing")
def should_be_success_alert(self) -> None:
"""Проверяет наличие сообщения об успешном обновлении полей настроек .
Raises:
AssertionError: Если тулбар отсутствует.
"""
alert_type = self.alert.get_alert_type()
assert alert_type == "success", f"Expected success alert, but got {alert_type} alert"
self.alert.check_alert_presence('\nПараметры успешно\nобновлены\n')
self.alert.check_alert_absence('\nПараметры успешно\nобновлены\n')
def _check_common_settings_content(self):
"""Проверяет наличие и корректность всех элементов формы ввода настроек 'Общие'."""
expected_input_field_names = ["Сервер", "Порт", "Отправитель"]
self.common_settings.should_be_toolbar()
actual_input_field_names = self.common_input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
for name in self.common_settings.content_items.keys():
item = self.common_settings.get_content_item(name)
item.check_visibility(
f"E-mail notifications Common settings input form item with name '{name}' is missing"
)
if name == "checkbox_activate_label":
item.check_have_text(
"Активировать",
"Label 'Активировать' is missing"
)
if name == "checkbox_activate":
is_activate_checked = item.is_checked()
assert is_activate_checked, (
"Checkbox 'Активировать' should be checked"
)
def _check_tls_settings_content(self):
"""Проверяет наличие и корректность всех элементов формы ввода настроек 'TLS'."""
expected_input_field_names = ["Использовать TLS-туннель"]
self.tls_settings.should_be_toolbar()
actual_input_field_names = self.tls_input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
for name in self.tls_settings.content_items.keys():
item = self.tls_settings.get_content_item(name)
if name == "tunnel_setting_selector":
item.check_field_visibility(
f"E-mail notifications TLS settings input form item with name '{name}' is missing"
)
item.should_be_clear_selection_button()
item.should_be_open_list_button()
if name == "checkbox_accept_certificates_label":
item.check_visibility(
f"E-mail notifications TLS settings input form item with name '{name}' is missing"
)
item.check_have_text(
"Принимать самоподписанные сертификаты",
"Label 'Принимать самоподписанные сертификаты' is missing"
)
if name == "checkbox_accept_certificates":
item.check_visibility(
f"E-mail notifications TLS settings input form item with name '{name}' is missing"
)
is_accept_certificates_checked = item.is_checked()
assert is_accept_certificates_checked, (
"Checkbox 'Принимать самоподписанные сертификаты' should be checked by default"
)
def _check_auth_settings_content(self):
"""Проверяет наличие и корректность всех элементов формы ввода настроек 'Аутентификация'."""
expected_input_field_names = ["Метод авторизации", "Имя пользователя", "Пароль",
"Домен", "Рабочая станция"]
self.auth_settings.should_be_toolbar()
actual_input_field_names = self.auth_input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
for name in self.auth_settings.content_items.keys():
item = self.auth_settings.get_content_item(name)
if name == "auth_method_setting_selector":
item.check_field_visibility(
f"E-mail notifications Auth settings input form item with name '{name}' is missing"
)
item.should_be_clear_selection_button()
item.should_be_open_list_button()
else:
item.check_visibility(
f"E-mail notifications Auth settings input form item with name '{name}' is missing"
)

View File

@ -1,211 +0,0 @@
"""Модуль вкладки настройки Keycloak Аутентификации.
Содержит класс KeycloakAuthSettings для работы с вкладкой настройки Keycloak Аутентификации.
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
"""
import re
from playwright.sync_api import Page
from locators.settings_form_locators import SettingsFormLocators
from elements.text_input_element import TextInput
from components.toolbar_component import ToolbarComponent
from components.alert_component import AlertComponent
from components_derived.settings_form_component import SettingsFormComponent
from pages.base_page import BasePage
class KeycloakAuthSettingsTab(BasePage):
"""Класс для работы с вкладкой настройки Keycloak Аутентификации.
Предоставляет методы для взаимодействия с вкладкой настройки Keycloak Аутентификации.
Args:
page: Экземпляр страницы Playwright.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки настройки Keycloak Аутентификации."""
super().__init__(page)
self.toolbar = ToolbarComponent(page, "KEYCLOAK")
toolbar_button_edit = self.page.get_by_role("navigation"). \
locator("//button[@data-testid='KEYCLOAK__btn__edit']")
self.toolbar.add_tooltip_button(toolbar_button_edit, "edit")
toolbar_button_save = self.page.get_by_role("navigation"). \
locator("//button[@data-testid='KEYCLOAK__btn__done']")
self.toolbar.add_tooltip_button(toolbar_button_save, "save")
toolbar_button_cancel = self.page.get_by_role("navigation"). \
locator("//button[@data-testid='KEYCLOAK__btn__close']")
self.toolbar.add_tooltip_button(toolbar_button_cancel, "cancel")
# Форма для отображения/редактирования полей настроек KEYCLOAK Аутентификации
self.settings_form = SettingsFormComponent(page)
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
loc = self.input_fields_locators.get("url")
loc_url_input = loc.locator("//input[@data-testid='KEYCLOAK__text-field__url']")
url_setting_input = TextInput(page, loc_url_input, "url_setting_input")
self.settings_form.add_content_item("url_setting_input", url_setting_input)
loc = self.input_fields_locators.get("url_token")
loc_url_token_input = loc.locator("//input[@data-testid='KEYCLOAK__text-field__url_token']")
url_token_setting_input = TextInput(page, loc_url_token_input, "url_token_setting_input")
self.settings_form.add_content_item("url_token_setting_input", url_token_setting_input)
loc = self.input_fields_locators.get("clientid")
loc_clientid_input = loc.locator("//input[@data-testid='KEYCLOAK__text-field__clientid']")
clientid_setting_input = TextInput(page, loc_clientid_input, "clientid_setting_input")
self.settings_form.add_content_item("clientid_setting_input", clientid_setting_input)
loc = self.input_fields_locators.get("clientsecret")
loc_clientsecret_input = loc.locator("//input[@data-testid='KEYCLOAK__text-field__clientsecret']")
clientsecret_setting_input = TextInput(page, loc_clientsecret_input, "clientsecret_setting_input")
self.settings_form.add_content_item("clientsecret_setting_input", clientsecret_setting_input)
loc = self.input_fields_locators.get("redirect_uri")
loc_redirect_uri_input = loc.locator("//input[@data-testid='KEYCLOAK__text-field__redirect_uri']")
redirect_uri_setting_input = TextInput(page, loc_redirect_uri_input, "redirect_uri_setting_input")
self.settings_form.add_content_item("redirect_uri_setting_input", redirect_uri_setting_input)
self.alert = AlertComponent(page)
# Действия:
def click_cancel_button(self) -> None:
"""Нажатие кнопки 'Отменить' на тулбаре."""
self.toolbar.check_button_visibility("cancel")
self.toolbar.get_button_by_name("cancel").click()
def click_edit_button(self) -> None:
"""Нажатие кнопки 'Редактировать' на тулбаре."""
self.toolbar.check_button_visibility("edit")
self.toolbar.get_button_by_name("edit").click()
def click_save_button(self) -> None:
"""Нажатие кнопки 'Сохранить' на тулбаре."""
self.toolbar.check_button_visibility("save")
self.toolbar.get_button_by_name("save").click()
def get_current_setting_values(self) -> dict:
"""Возвращает текущее значение полей настроек.
Returns:
str : Текущее значение полей настроек.
"""
values = {}
field = self.settings_form.get_content_item("url_setting_input")
values.update({"url": field.get_input_value().strip()})
field = self.settings_form.get_content_item("url_token_setting_input")
values.update({"url_token": field.get_input_value().strip()})
field = self.settings_form.get_content_item("clientid_setting_input")
values.update({"clientid": field.get_input_value().strip()})
field = self.settings_form.get_content_item("clientsecret_setting_input")
values.update({"clientsecret": field.get_input_value().strip()})
field = self.settings_form.get_content_item("redirect_uri_setting_input")
values.update({"redirect_uri": field.get_input_value().strip()})
return values
def input_url(self, text: str) -> None:
"""Заполнение поля 'URL'."""
message_input = self.settings_form.get_content_item("url_setting_input")
message_input.clear()
message_input.input_value(text)
def input_url_token(self, text: str) -> None:
"""Заполнение поля 'URL_TOKEN'."""
message_input = self.settings_form.get_content_item("url_token_setting_input")
message_input.clear()
message_input.input_value(text)
def input_clientid(self, text: str) -> None:
"""Заполнение поля 'CLIENTID'."""
message_input = self.settings_form.get_content_item("clientid_setting_input")
message_input.clear()
message_input.input_value(text)
def input_clientsecret(self, text: str) -> None:
"""Заполнение поля 'CLIENTSECRET'."""
message_input = self.settings_form.get_content_item("clientsecret_setting_input")
message_input.clear()
message_input.input_value(text)
def input_redirect_uri(self, text: str) -> None:
"""Заполнение поля 'REDIRECT_URI'."""
message_input = self.settings_form.get_content_item("redirect_uri_setting_input")
message_input.clear()
message_input.input_value(text)
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов страницы."""
expected_input_field_names = ["url", "url_token", "clientid",
"clientsecret", "redirect_uri"]
self.should_be_toolbar()
actual_input_field_names = self.input_fields_locators.keys()
for name in expected_input_field_names:
assert name in actual_input_field_names, \
f"Expected input field name {name} is missing in actual input field names"
for name in self.settings_form.content_items.keys():
item = self.settings_form.get_content_item(name)
item.check_visibility(
f"KEYCLOAK settings input form item with name '{name}' is missing"
)
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если тулбар или кнопка тулбара отсутствуют.
"""
loc = self.page.get_by_role("navigation").filter(
has_text=re.compile("KEYCLOAK")).locator("div").nth(1)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'KEYCLOAK' is missing")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Редактировать")
self.toolbar.get_button_by_name("edit").click()
self.toolbar.check_button_visibility("save")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("save", "Сохранить")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.get_button_by_name("cancel").click()
self.toolbar.check_button_visibility("edit")
def should_be_success_alert(self) -> None:
"""Проверяет наличие сообщения об успешном сохранении заданных параметров.
Raises:
AssertionError: Если тулбар отсутствует.
"""
alert_type = self.alert.get_alert_type()
assert alert_type == "success", f"Expected success alert, but got {alert_type} alert"
self.alert.check_alert_presence('\nПараметры успешно\n обновлены\n')
self.alert.check_alert_absence('\nПараметры успешно\n обновлены\n')

View File

@ -1,346 +0,0 @@
"""Модуль вкладки настройки LDAP Аутентификации.
Содержит класс LDAPAuthSettings для работы с вкладкой настройки LDAP Аутентификации.
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
"""
import re
from playwright.sync_api import Page
from locators.text_input_locators import TextInputLocators
from locators.settings_form_locators import SettingsFormLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.icon_element import Icon
from elements.checkbox_element import Checkbox
from components.toolbar_component import ToolbarComponent
from components.alert_component import AlertComponent
from components_derived.settings_form_component import SettingsFormComponent
from pages.base_page import BasePage
class LDAPAuthSettingsTab(BasePage):
"""Класс для работы с вкладкой настройки LDAP Аутентификации.
Предоставляет методы для взаимодействия с вкладкой настройки LDAP Аутентификации.
Args:
page: Экземпляр страницы Playwright.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки настройки LDAP Аутентификации."""
super().__init__(page)
self.toolbar = ToolbarComponent(page, "LDAP")
toolbar_button_edit = self.page.get_by_role("navigation").locator("//button[@data-testid='LDAP__btn__edit']")
self.toolbar.add_tooltip_button(toolbar_button_edit, "edit")
toolbar_button_save = self.page.get_by_role("navigation").locator("//button[@data-testid='LDAP__btn__submit']")
self.toolbar.add_tooltip_button(toolbar_button_save, "save")
toolbar_button_cancel = self.page.get_by_role("navigation"). \
locator("//button[@data-testid='LDAP__btn__cancelEdit']")
self.toolbar.add_tooltip_button(toolbar_button_cancel, "cancel")
# Форма для отображения/редактирования полей настроек LDAP Аутентификации
self.settings_form = SettingsFormComponent(page)
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
# Метка "tls"
label_tls_locator = container_locator.get_by_text("tls")
label_tls = Text(
page,
label_tls_locator,
"tls_checkbox_label"
)
self.settings_form.add_content_item("tls_checkbox_label", label_tls)
# Чекбокс "tls"
checkbox_tls = Checkbox(
page,
container_locator.locator("//input[@data-testid='LDAP__checkbox__tls']"),
"tls_checkbox"
)
self.settings_form.add_content_item("tls_checkbox", checkbox_tls)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
loc = self.input_fields_locators.get("ip")
loc_ip_input = loc.locator("//input[@data-testid='LDAP__text-field__ip']")
ip_setting_input = TextInput(page, loc_ip_input, "ip_setting_input")
self.settings_form.add_content_item("ip_setting_input", ip_setting_input)
loc = self.input_fields_locators.get("Порт")
loc_port_input = loc.locator("//input[@data-testid='LDAP__text-field__port']")
port_setting_input = TextInput(page, loc_port_input, "port_setting_input")
self.settings_form.add_content_item("port_setting_input", port_setting_input)
loc = self.input_fields_locators.get("Имя пользователя")
loc_user_input = loc.locator("//input[@data-testid='LDAP__text-field__login']")
user_setting_input = TextInput(page, loc_user_input, "user_setting_input")
self.settings_form.add_content_item("user_setting_input", user_setting_input)
loc = self.input_fields_locators.get("Пароль")
loc_password_input = loc.locator("//input[@data-testid='LDAP__text-field__password']")
password_setting_input = TextInput(page, loc_password_input, "password_setting_input")
self.settings_form.add_content_item("password_setting_input", password_setting_input)
loc = self.input_fields_locators.get("Домен")
loc_domain_input = loc.locator("//input[@data-testid='LDAP__text-field__domain']")
domain_setting_input = TextInput(page, loc_domain_input, "domain_setting_input")
self.settings_form.add_content_item("domain_setting_input", domain_setting_input)
loc = self.input_fields_locators.get("DN каталог пользователей")
loc_dn_catalog_input = loc.locator("//input[@data-testid='LDAP__text-field__base_dn']")
dn_catalog_setting_input = TextInput(page, loc_dn_catalog_input, "dn_catalog_setting_input")
self.settings_form.add_content_item("dn_catalog_setting_input", dn_catalog_setting_input)
loc = self.input_fields_locators.get("Атрибут авторизации")
loc_login_attr_input = loc.locator("//input[@data-testid='LDAP__text-field__login_attribute']")
login_attr_setting_input = TextInput(page, loc_login_attr_input, "login_attr_setting_input")
self.settings_form.add_content_item("login_attr_setting_input", login_attr_setting_input)
loc = self.input_fields_locators.get("Атрибут email")
loc_email_attr_input = loc.locator("//input[@data-testid='LDAP__text-field__email_attribute']")
email_attr_setting_input = TextInput(page, loc_email_attr_input, "email_attr_setting_input")
self.settings_form.add_content_item("email_attr_setting_input", email_attr_setting_input)
loc = self.input_fields_locators.get("Атрибут sms")
loc_sms_attr_input = loc.locator("//input[@data-testid='LDAP__text-field__sms_attribute']")
sms_attr_setting_input = TextInput(page, loc_sms_attr_input, "sms_attr_setting_input")
self.settings_form.add_content_item("sms_attr_setting_input", sms_attr_setting_input)
icon_locator = loc_password_input.locator("../..").locator(TextInputLocators.ICON_PASSWORD_HIDING)
password_hidden_icon = Icon(page, icon_locator,
"password hidden icon")
self.settings_form.add_content_item("password_hidden_icon", password_hidden_icon)
self.alert = AlertComponent(page)
# Действия:
def click_cancel_button(self) -> None:
"""Нажатие кнопки 'Отменить' на тулбаре."""
self.toolbar.check_button_visibility("cancel")
self.toolbar.get_button_by_name("cancel").click()
def click_edit_button(self) -> None:
"""Нажатие кнопки 'Редактировать' на тулбаре."""
self.toolbar.check_button_visibility("edit")
self.toolbar.get_button_by_name("edit").click()
def click_save_button(self) -> None:
"""Нажатие кнопки 'Сохранить' на тулбаре."""
self.toolbar.check_button_visibility("save")
self.toolbar.get_button_by_name("save").click()
def click_password_hidden_icon(self) -> None:
"""Нажатие на иконку скрытия пароля."""
self.settings_form.get_content_item("password_hidden_icon").click()
def check_checkbox_tls(self):
"""Включает чек-бокс tls."""
self.settings_form.get_content_item("tls_checkbox").check(force=True)
def uncheck_checkbox_tls(self):
"""Выключает чек-бокс tls."""
self.settings_form.get_content_item("tls_checkbox").uncheck(force=True)
def get_current_setting_values(self) -> dict:
"""Возвращает текущее значение полей настроек.
Returns:
str : Текущее значение полей настроек.
"""
values = {}
tls_checkbox = self.settings_form.get_content_item("tls_checkbox")
values.update({"tls checked": tls_checkbox.is_checked()})
field = self.settings_form.get_content_item("ip_setting_input")
values.update({"ip": field.get_input_value().strip()})
field = self.settings_form.get_content_item("port_setting_input")
values.update({"Порт": field.get_input_value().strip()})
field = self.settings_form.get_content_item("user_setting_input")
values.update({"Имя пользователя": field.get_input_value().strip()})
field = self.settings_form.get_content_item("password_setting_input")
values.update({"Пароль": field.get_input_value().strip()})
field = self.settings_form.get_content_item("domain_setting_input")
values.update({"Домен": field.get_input_value().strip()})
field = self.settings_form.get_content_item("dn_catalog_setting_input")
values.update({"DN каталог пользователей": field.get_input_value().strip()})
field = self.settings_form.get_content_item("login_attr_setting_input")
values.update({"Атрибут авторизации": field.get_input_value().strip()})
field = self.settings_form.get_content_item("email_attr_setting_input")
values.update({"Атрибут email": field.get_input_value().strip()})
field = self.settings_form.get_content_item("sms_attr_setting_input")
values.update({"Атрибут sms": field.get_input_value().strip()})
return values
def get_password_setting_value(self) -> str:
"""Возвращает текущее значение поля настроек 'Пароль'.
Returns:
str : Текущее отображение значения поля настроек 'Пароль'.
"""
input_field = self.settings_form.get_content_item("password_setting_input")
is_hidden_state = self.settings_form.get_content_item("password_hidden_icon").is_password_hidden()
password_value = input_field.get_input_value().strip()
if is_hidden_state:
password_value = "." * len(password_value)
return password_value
def input_ip(self, text: str) -> None:
"""Заполнение поля 'IP'."""
message_input = self.settings_form.get_content_item("ip_setting_input")
message_input.clear()
message_input.input_value(text)
def input_port(self, text: str) -> None:
"""Заполнение поля 'Порт'."""
message_input = self.settings_form.get_content_item("port_setting_input")
message_input.clear()
message_input.input_value(text)
def input_user(self, text: str) -> None:
"""Заполнение поля 'Имя пользователя'."""
message_input = self.settings_form.get_content_item("user_setting_input")
message_input.clear()
message_input.input_value(text)
def input_password(self, text: str) -> None:
"""Заполнение поля 'Пароль'."""
message_input = self.settings_form.get_content_item("password_setting_input")
message_input.clear()
message_input.input_value(text)
def input_domain(self, text: str) -> None:
"""Заполнение поля 'Домен'."""
message_input = self.settings_form.get_content_item("domain_setting_input")
message_input.clear()
message_input.input_value(text)
def input_dn_catalog(self, text: str) -> None:
"""Заполнение поля 'DN каталог пользователей'."""
message_input = self.settings_form.get_content_item("dn_catalog_setting_input")
message_input.clear()
message_input.input_value(text)
def input_login_attribute(self, text: str) -> None:
"""Заполнение поля 'Атрибут авторизации'."""
message_input = self.settings_form.get_content_item("login_attr_setting_input")
message_input.clear()
message_input.input_value(text)
def input_email_attribute(self, text: str) -> None:
"""Заполнение поля 'Атрибут email'."""
message_input = self.settings_form.get_content_item("email_attr_setting_input")
message_input.clear()
message_input.input_value(text)
def input_sms_attribute(self, text: str) -> None:
"""Заполнение поля 'Атрибут sms'."""
message_input = self.settings_form.get_content_item("sms_attr_setting_input")
message_input.clear()
message_input.input_value(text)
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов страницы."""
expected_input_field_names = ["ip", "Порт", "Имя пользователя", "Пароль",
"Домен", "DN каталог пользователей",
"Атрибут авторизации", "Атрибут email", "Атрибут sms"]
self.should_be_toolbar()
actual_input_field_names = self.input_fields_locators.keys()
for name in expected_input_field_names:
assert name in actual_input_field_names, \
f"Expected input field name {name} is missing in actual input field names"
for name in self.settings_form.content_items.keys():
item = self.settings_form.get_content_item(name)
item.check_visibility(
f"LDAP settings input form item with name '{name}' is missing"
)
if name == "password_hidden_icon":
is_hidden_state = item.is_password_hidden()
assert is_hidden_state, "Password hidden icon should be in hidden state"
hidden_password = self.get_password_setting_value()
self.click_password_hidden_icon()
is_hidden_state = item.is_password_hidden()
assert not is_hidden_state, "Password hidden icon should be in viewed state"
viewed_password = self.get_password_setting_value()
assert len(hidden_password) == len(viewed_password), \
"The lengths of hidden and viewed passwords should be equal"
self.click_password_hidden_icon()
is_hidden_state = item.is_password_hidden()
assert is_hidden_state, "Password hidden icon should be in hidden state"
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если тулбар или кнопка тулбара отсутствуют.
"""
loc = self.page.get_by_role("navigation").filter(
has_text=re.compile("LDAP")).locator("div").nth(1)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'LDAP' is missing")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Редактировать")
self.toolbar.get_button_by_name("edit").click()
self.toolbar.check_button_visibility("save")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("save", "Сохранить")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.get_button_by_name("cancel").click()
self.toolbar.check_button_visibility("edit")
def should_be_success_alert(self) -> None:
"""Проверяет наличие сообщения об успешном сохранении заданных параметров.
Raises:
AssertionError: Если тулбар отсутствует.
"""
alert_type = self.alert.get_alert_type()
assert alert_type == "success", f"Expected success alert, but got {alert_type} alert"
self.alert.check_alert_presence('\nПараметры успешно\n обновлены\n')
self.alert.check_alert_absence('\nПараметры успешно\n обновлены\n')

View File

@ -60,13 +60,13 @@ class LicenseTab(BasePage):
def scroll_json_container_up(self) -> None: def scroll_json_container_up(self) -> None:
"""Прокручивает JSON-контейнер вверх.""" """Прокручивает JSON-контейнер вверх."""
loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER) loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first
self.json_container.scroll_up(loc) self.json_container.scroll_up(loc)
def scroll_json_container_down(self) -> None: def scroll_json_container_down(self) -> None:
"""Прокручивает JSON-контейнер вниз.""" """Прокручивает JSON-контейнер вниз."""
loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER) loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first
self.json_container.scroll_down(loc) self.json_container.scroll_down(loc)
# Проверки: # Проверки:
@ -77,7 +77,7 @@ class LicenseTab(BasePage):
bool: Доступность прокрутки bool: Доступность прокрутки
""" """
loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER) loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first
return self.json_container.is_scrollable_vertically(loc) return self.json_container.is_scrollable_vertically(loc)
def check_content(self) -> None: def check_content(self) -> None:

View File

@ -205,7 +205,7 @@ class MainPage(BasePage):
item_name item_name
) )
def check_navigation_panel_item_visibility(self, item_name: str, parent=None) -> None: def check_navigation_panel_item_visibility(self, item_name: str) -> None:
"""Проверяет видимость элемента в панели навигации. """Проверяет видимость элемента в панели навигации.
Args: Args:
@ -214,7 +214,7 @@ class MainPage(BasePage):
self.navigation_panel.check_item_visibility( self.navigation_panel.check_item_visibility(
NavigationPanelLocators.PANEL_MAIN, NavigationPanelLocators.PANEL_MAIN,
item_name, parent item_name
) )
def check_subpanel_item_state(self, item_name: str, parent=None) -> str|None: def check_subpanel_item_state(self, item_name: str, parent=None) -> str|None:

View File

@ -4,12 +4,14 @@
Позволяет проверять состояние и взаимодействовать с элементами вкладки. Позволяет проверять состояние и взаимодействовать с элементами вкладки.
""" """
import re
from playwright.sync_api import Page from playwright.sync_api import Page
from locators.settings_form_locators import SettingsFormLocators from locators.settings_form_locators import SettingsFormLocators
from elements.text_input_element import TextInput from elements.text_input_element import TextInput
from components.toolbar_component import ToolbarComponent
from components.alert_component import AlertComponent from components.alert_component import AlertComponent
from components_derived.settings_form_component import SettingsFormComponent from components_derived.settings_form_component import SettingsFormComponent
from components.checkbox_group_component import CheckboxGroupComponent # Изменен импорт from components_derived.interactive_dropdown_list import InteractiveDropdownList
from pages.base_page import BasePage from pages.base_page import BasePage
@ -27,28 +29,35 @@ class PushNotificationsSettingsTab(BasePage):
super().__init__(page) super().__init__(page)
self.toolbar = ToolbarComponent(page, "Push уведомления")
# Форма для отображения/редактирования полей настроек Push уведомлений # Форма для отображения/редактирования полей настроек Push уведомлений
self.settings_form = SettingsFormComponent(page) self.settings_form = SettingsFormComponent(page)
self.settings_form.add_toolbar_title("Push уведомления") self.settings_form.add_toolbar_title("Общие")
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator) self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
print(self.input_fields_locators)
loc = self.input_fields_locators.get("Сообщение") loc = self.input_fields_locators.get("Сообщение")
loc_message_input = loc.locator("//input[@data-testid='PUSH_NOTIFICATIONS__text-field__message']") loc_message_input = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
message_setting_input = TextInput(page, loc_message_input, "message_setting_input") message_setting_input = TextInput(page, loc_message_input, "message_setting_input")
self.settings_form.add_content_item("message_setting_input", message_setting_input) self.settings_form.add_content_item("message_setting_input", message_setting_input)
loc = self.input_fields_locators.get("Пользователи") loc = self.input_fields_locators.get("Пользователи")
users_setting_input = TextInput(page, users_setting_input = TextInput(page,
loc. get_by_role("combobox"), loc. get_by_role("combobox"),
"users_setting_input") "users_setting_input")
# users_setting_input = TextInput(page,
# page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
# get_by_role("combobox"),
# "users_setting_input")
self.settings_form.add_content_item("users_setting_input", users_setting_input) self.settings_form.add_content_item("users_setting_input", users_setting_input)
# Используем новый компонент CheckboxGroupComponent self.settings_form.add_content_item("users_list", InteractiveDropdownList(page))
self.settings_form.add_content_item("users_checkbox_group", CheckboxGroupComponent(page))
self.settings_form.add_tooltip_button(page.locator(SettingsFormLocators.PUSH_NOTIFICATIONS_BUTTON_SUBMIT), self.settings_form.add_tooltip_button(page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_role("button", name='Отправить'),
"submit_button") "submit_button")
self.alert = AlertComponent(page) self.alert = AlertComponent(page)
@ -59,7 +68,7 @@ class PushNotificationsSettingsTab(BasePage):
selected_users = self.get_users_setting_value() selected_users = self.get_users_setting_value()
if len(selected_users) > 0: if len(selected_users) > 0:
clear_selection_button = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER).\ clear_selection_button = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_role("combobox").locator(SettingsFormLocators.CLEAR_SELECTION_BUTTON) get_by_role("combobox").locator(SettingsFormLocators.CLEAR_SELECTION_BUTTON)
clear_selection_button.click() clear_selection_button.click()
@ -86,7 +95,7 @@ class PushNotificationsSettingsTab(BasePage):
str : Текущее значение поля настроек 'Пользователи'. str : Текущее значение поля настроек 'Пользователи'.
""" """
users_setting_field_loc = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER).\ users_setting_field_loc = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_role("combobox").locator(SettingsFormLocators.SELECTED_VALUES) get_by_role("combobox").locator(SettingsFormLocators.SELECTED_VALUES)
return users_setting_field_loc.text_content().strip() return users_setting_field_loc.text_content().strip()
@ -104,10 +113,10 @@ class PushNotificationsSettingsTab(BasePage):
assert len(users) != 0, "Users list should not be empty" assert len(users) != 0, "Users list should not be empty"
self.settings_form.get_content_item("users_setting_input").click() self.settings_form.get_content_item("users_setting_input").click()
users_checkbox_group = self.settings_form.get_content_item("users_checkbox_group") users_list = self.settings_form.get_content_item("users_list")
for user in users: for user in users:
users_checkbox_group.uncheck_by_text(user) users_list.deselect_item_with_text(user)
# Закрываем выпадающий список (кликаем вне его) # Закрываем выпадающий список (кликаем вне его)
self.page.mouse.click(10, 10) self.page.mouse.click(10, 10)
@ -118,10 +127,10 @@ class PushNotificationsSettingsTab(BasePage):
assert len(users) != 0, "Users list should not be empty" assert len(users) != 0, "Users list should not be empty"
self.settings_form.get_content_item("users_setting_input").click() self.settings_form.get_content_item("users_setting_input").click()
users_checkbox_group = self.settings_form.get_content_item("users_checkbox_group") users_list = self.settings_form.get_content_item("users_list")
for user in users: for user in users:
users_checkbox_group.check_by_text(user) users_list.select_item_with_text(user)
# Закрываем выпадающий список (кликаем вне его) # Закрываем выпадающий список (кликаем вне его)
self.page.mouse.click(10, 10) self.page.mouse.click(10, 10)
@ -132,20 +141,19 @@ class PushNotificationsSettingsTab(BasePage):
expected_input_field_names = ["Сообщение", "Пользователи"] expected_input_field_names = ["Сообщение", "Пользователи"]
self.should_be_form_toolbar() self.should_be_toolbar()
self.settings_form.check_button_visibility("submit_button") self.should_be_form_toolbar()
self.settings_form.check_button_tooltip("submit_button", "Отправить Push уведомление")
actual_input_field_names = self.input_fields_locators.keys() actual_input_field_names = self.input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \ assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}" f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
for name in self.settings_form.content_items.keys(): for name in self.settings_form.content_items.keys():
if name == "users_checkbox_group": if name == "users_list":
self.settings_form.get_content_item("users_setting_input").click() self.settings_form.get_content_item("users_setting_input").click()
users_checkbox_group = self.settings_form.get_content_item(name) users_list = self.settings_form.get_content_item(name)
selected_users = users_checkbox_group.get_checked_items(SettingsFormLocators.DROPDOWN_LIST) selected_users = users_list.get_selected_items(SettingsFormLocators.DROPDOWN_LIST)
assert len(selected_users) == 0, "There should be no selected users" assert len(selected_users) == 0, "There should be no selected users"
else: else:
item = self.settings_form.get_content_item(name) item = self.settings_form.get_content_item(name)
@ -153,6 +161,19 @@ class PushNotificationsSettingsTab(BasePage):
f"Push notifications settings input form item with name '{name}' is missing" f"Push notifications settings input form item with name '{name}' is missing"
) )
self.settings_form.check_button_visibility("submit_button")
self.settings_form.check_button_tooltip("submit_button", "Отправить Push уведомление")
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы.
Raises:
AssertionError: Если тулбар или кнопка редактирования отсутствуют.
"""
loc = self.page.get_by_role("navigation").filter(
has_text=re.compile("Push уведомления")).locator("div").nth(1)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'Push уведомления' is missing")
def should_be_form_toolbar(self) -> None: def should_be_form_toolbar(self) -> None:
"""Проверяет наличие тулбара формы редактирования настроек. """Проверяет наличие тулбара формы редактирования настроек.
@ -166,7 +187,7 @@ class PushNotificationsSettingsTab(BasePage):
"""Проверяет наличие сообщения об успешной отправке push-уведомления. """Проверяет наличие сообщения об успешной отправке push-уведомления.
Raises: Raises:
AssertionError: Если alert отсутствует. AssertionError: Если тулбар отсутствует.
""" """
alert_type = self.alert.get_alert_type() alert_type = self.alert.get_alert_type()
@ -174,13 +195,3 @@ class PushNotificationsSettingsTab(BasePage):
self.alert.check_alert_presence('\nPush-уведомление\nуспешно отправлено\n') self.alert.check_alert_presence('\nPush-уведомление\nуспешно отправлено\n')
self.alert.check_alert_absence('\nPush-уведомление\nуспешно отправлено\n') self.alert.check_alert_absence('\nPush-уведомление\nуспешно отправлено\n')
def should_be_disabled_button(self) -> None:
"""Проверяет, что кнопка 'Отправить' отключена.
Raises:
AssertionError: Если кнопка включена.
"""
submit_button = self.settings_form.get_button_by_name("submit_button")
assert submit_button.is_disabled, "Submit button should be disabled"

View File

@ -56,8 +56,16 @@ class RackPage(BasePage):
# Действия # Действия
def click_edit_button(self) -> None: def click_edit_button(self) -> None:
""" Кликает на кнопку 'Изменить'.""" """
Кликает на кнопку 'Изменить'.
"""
logger.debug("Clicking on 'Edit' button...")
# Проверяем видимость кнопки
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Изменить")
# Кликаем на кнопку
self.toolbar.get_button_by_name("edit").click() self.toolbar.get_button_by_name("edit").click()
self.wait_for_timeout(1000) self.wait_for_timeout(1000)
@ -450,7 +458,7 @@ class RackPage(BasePage):
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()
def should_have_hide_rack_button(self) -> None: def should_have_hide_rack_button(self) -> None:

View File

@ -1,4 +1,4 @@
"""Модуль вкладки 'Статус компонентов'. """Модуль вкладки 'Статус обслуживания'.
Содержит класс ServiceStatusTab для работы с таблицей сервисов. Содержит класс ServiceStatusTab для работы с таблицей сервисов.
Позволяет проверять состояние и взаимодействовать с элементами вкладки. Позволяет проверять состояние и взаимодействовать с элементами вкладки.
@ -9,11 +9,12 @@ from playwright.sync_api import Page, Locator, expect
from elements.text_element import Text from elements.text_element import Text
from elements.button_element import Button from elements.button_element import Button
from components.table_component import TableComponent from components.table_component import TableComponent
from components.expand_button_component import ExpandButton
from pages.base_page import BasePage from pages.base_page import BasePage
class ServiceStatusTab(BasePage): class ServiceStatusTab(BasePage):
"""Класс для работы с вкладкой 'Статус компонентов'. """Класс для работы с вкладкой 'Статус обслуживания'.
Предоставляет методы для взаимодействия с таблицей сервисов и проверки Предоставляет методы для взаимодействия с таблицей сервисов и проверки
её состояния. её состояния.
@ -23,7 +24,7 @@ class ServiceStatusTab(BasePage):
""" """
def __init__(self, page: Page) -> None: def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки 'Статус компонентов'.""" """Инициализирует компоненты вкладки 'Статус обслуживания'."""
super().__init__(page) super().__init__(page)
@ -40,6 +41,7 @@ class ServiceStatusTab(BasePage):
self.update_button_locator, self.update_button_locator,
"update_button") "update_button")
self.expand_work_area_button = ExpandButton(page)
self.services_table = TableComponent(page) self.services_table = TableComponent(page)
# Действия: # Действия:
@ -126,11 +128,35 @@ class ServiceStatusTab(BasePage):
return self.services_table.get_rows_count(self.table_locator) return self.services_table.get_rows_count(self.table_locator)
def get_workarea_widht(self) -> float: def expand_tab(self) -> None:
"""Возвращает текущую ширину рабочей области вкладки.""" """Расширяет рабочую область складки."""
iframe_container_bounding_box = self.iframe_container_locator.evaluate("el => el.getBoundingClientRect()") iframe_container_bounding_box = self.iframe_container_locator.\
return iframe_container_bounding_box["width"] evaluate("el => el.getBoundingClientRect()")
widht_before = iframe_container_bounding_box["width"]
self.expand_work_area_button.expand()
iframe_container_bounding_box = self.iframe_container_locator.\
evaluate("el => el.getBoundingClientRect()")
widht_after = iframe_container_bounding_box["width"]
assert widht_before < widht_after,"Services statuses tab should be expanded"
def reduce_tab(self) -> None:
"""Сжимает рабочую область складки."""
iframe_container_bounding_box = self.iframe_container_locator.\
evaluate("el => el.getBoundingClientRect()")
widht_before = iframe_container_bounding_box["width"]
self.expand_work_area_button.reduce()
iframe_container_bounding_box = self.iframe_container_locator.\
evaluate("el => el.getBoundingClientRect()")
widht_after = iframe_container_bounding_box["width"]
assert widht_before > widht_after,"Services statuses tab should be reduced"
def scroll_services_tab_up(self) -> None: def scroll_services_tab_up(self) -> None:
"""Прокручивает содержимое вкладки вверх.""" """Прокручивает содержимое вкладки вверх."""
@ -252,11 +278,16 @@ class ServiceStatusTab(BasePage):
AssertionError: Если строка не выделена. AssertionError: Если строка не выделена.
""" """
# offsets_scales = self.get_offsets_scales() offsets_scales = self.get_offsets_scales()
self.services_table.check_mui_table_row_highlighting( self.services_table.check_mui_table_row_highlighting(
self.table_locator, self.table_locator,
row_index) row_index,
offsets_scales["offset_x"],
offsets_scales["offset_y"],
offsets_scales["scale_x"],
offsets_scales["scale_y"]
)
def should_be_tab_title(self) -> None: def should_be_tab_title(self) -> None:
"""Проверяет наличие заголовка вкладки. """Проверяет наличие заголовка вкладки.
@ -281,6 +312,15 @@ class ServiceStatusTab(BasePage):
"Service statuses table is missing" "Service statuses table is missing"
) )
def should_be_expand_work_area_button(self) -> None:
"""Проверяет наличие кнопки расширения/сжатия рабочей области вкладки.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.expand_work_area_button.should_be_button()
def should_be_update_button(self) -> None: def should_be_update_button(self) -> None:
"""Проверяет наличие кнопки 'Обновить'. """Проверяет наличие кнопки 'Обновить'.

View File

@ -27,48 +27,49 @@ class SessionSettingsTab(BasePage):
super().__init__(page) super().__init__(page)
self.toolbar = ToolbarComponent(page, "Время жизни сеанса") locator_button_1 = self.page.get_by_role("navigation").filter(
toolbar_button_edit = self.page.get_by_role("navigation").filter(has_text=re.compile("Время жизни сеанса")). \ has_text=re.compile("Настройки")
locator("//button[@data-testid='SESSION_SETTINGS__btn__edit']") ).get_by_role("button").nth(0)
self.toolbar.add_tooltip_button(toolbar_button_edit, "edit") locator_button_2 = self.page.get_by_role("navigation").filter(
has_text=re.compile("Настройки")
).get_by_role("button").nth(1)
toolbar_button_save = self.page.get_by_role("navigation").filter(has_text=re.compile("Время жизни сеанса")). \ self.toolbar = ToolbarComponent(page, "Настройки")
locator("//button[@data-testid='SESSION_SETTINGS__btn__submit']") self.toolbar.add_tooltip_button(locator_button_1, "edit")
self.toolbar.add_tooltip_button(toolbar_button_save, "save") self.toolbar.add_tooltip_button(locator_button_1, "save")
self.toolbar.add_tooltip_button(locator_button_2, "cancel")
toolbar_button_cancel = self.page.get_by_role("navigation").filter(has_text=re.compile("Время жизни сеанса")). \
locator("//button[@data-testid='SESSION_SETTINGS__btn__cancelEdit']")
self.toolbar.add_tooltip_button(toolbar_button_cancel, "cancel")
# Форма для отображения/редактирования полей настроек сессии пользователя # Форма для отображения/редактирования полей настроек сессии пользователя
self.settings_form = SettingsFormComponent(page) self.settings_form = SettingsFormComponent(page)
self.settings_form.add_toolbar_title("Время жизни сеанса")
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator) self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
# Используем локаторы для числовых полей # Используем локаторы для числовых полей
loc = self.input_fields_locators.get("Администратор") loc = self.input_fields_locators.get("Администратор")
loc_admin = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__administrator']") loc_admin = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
admin_setting = TextInput(page, loc_admin, "admin_setting") admin_setting = TextInput(page, loc_admin, "admin_setting")
self.settings_form.add_content_item("admin_setting", admin_setting) self.settings_form.add_content_item("admin_setting", admin_setting)
loc = self.input_fields_locators.get("Оператор") loc = self.input_fields_locators.get("Оператор")
loc_oper = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__operator']") loc_oper = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
operator_setting = TextInput(page, loc_oper, "operator_setting") operator_setting = TextInput(page, loc_oper, "operator_setting")
self.settings_form.add_content_item("operator_setting", operator_setting) self.settings_form.add_content_item("operator_setting", operator_setting)
loc = self.input_fields_locators.get("Контактное лицо") loc = self.input_fields_locators.get("Контактное лицо")
loc_manager = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__manager']") loc_manager = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
manager_setting = TextInput(page, loc_manager, "manager_setting") manager_setting = TextInput(page, loc_manager, "manager_setting")
self.settings_form.add_content_item("manager_setting", manager_setting) self.settings_form.add_content_item("manager_setting", manager_setting)
loc = self.input_fields_locators.get("Специалист информационной безопасности") loc = self.input_fields_locators.get("Специалист информационной безопасности")
loc_inform_secur_user = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__inform_secur_user']") loc_inform_secur_user = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
inform_secur_user_setting = TextInput(page, loc_inform_secur_user, "inform_secur_user_setting") inform_secur_user_setting = TextInput(page, loc_inform_secur_user, "inform_secur_user_setting")
self.settings_form.add_content_item("inform_secur_user_setting", inform_secur_user_setting) self.settings_form.add_content_item("inform_secur_user_setting", inform_secur_user_setting)
loc = self.input_fields_locators.get('$collector') loc = self.input_fields_locators.get('$collector')
loc_collector = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__$collector']") loc_collector = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first
collector_setting = TextInput(page, loc_collector, "collector_setting") collector_setting = TextInput(page, loc_collector, "collector_setting")
self.settings_form.add_content_item("collector_setting", collector_setting) self.settings_form.add_content_item("collector_setting", collector_setting)
@ -168,6 +169,8 @@ class SessionSettingsTab(BasePage):
field.input_value(value) field.input_value(value)
# temporararily # temporararily
self.click_cancel_button()
# self.click_save_button() # self.click_save_button()
# alert_type = self.alert.get_alert_type() # alert_type = self.alert.get_alert_type()
@ -181,14 +184,16 @@ class SessionSettingsTab(BasePage):
"""Скроллинг вниз формы настроек времени жизни сессии. """Скроллинг вниз формы настроек времени жизни сессии.
""" """
locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
has_text="Время жизни сеанса")
self.settings_form.scroll_down(locator) self.settings_form.scroll_down(locator)
def scroll_up(self) -> None: def scroll_up(self) -> None:
"""Скроллинг вверх формы настроек времени жизни сессии. """Скроллинг вверх формы настроек времени жизни сессии.
""" """
locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
has_text="Время жизни сеанса")
self.settings_form.scroll_up(locator) self.settings_form.scroll_up(locator)
# Проверки: # Проверки:
@ -202,6 +207,8 @@ class SessionSettingsTab(BasePage):
self.should_be_toolbar() self.should_be_toolbar()
self.should_be_toolbar_buttons() self.should_be_toolbar_buttons()
self.should_be_form_toolbar()
actual_input_field_names = self.input_fields_locators.keys() actual_input_field_names = self.input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \ assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}" f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
@ -214,8 +221,7 @@ class SessionSettingsTab(BasePage):
for name in actual_input_field_names: for name in actual_input_field_names:
# Для суффикса "минут" # Для суффикса "минут"
value_suffix_loc = self.input_fields_locators.get(name). \ value_suffix_loc = self.input_fields_locators.get(name).locator(SettingsFormLocators.SETTINGS_FORM_INPUT_VALUE_SUFFIX)
locator(SettingsFormLocators.SETTINGS_FORM_INPUT_VALUE_SUFFIX)
value_suffix = value_suffix_loc.text_content().strip() value_suffix = value_suffix_loc.text_content().strip()
assert value_suffix == "минут", f"Incorrect value suffix for field {name}" assert value_suffix == "минут", f"Incorrect value suffix for field {name}"
@ -223,7 +229,8 @@ class SessionSettingsTab(BasePage):
"""Проверка возможности вертикального скроллинга формы настроек времени жизни сессии. """Проверка возможности вертикального скроллинга формы настроек времени жизни сессии.
""" """
locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
has_text="Время жизни сеанса")
return self.settings_form.check_vertical_scrolling(locator) return self.settings_form.check_vertical_scrolling(locator)
def should_be_toolbar(self) -> None: def should_be_toolbar(self) -> None:
@ -233,8 +240,8 @@ class SessionSettingsTab(BasePage):
AssertionError: Если тулбар или кнопка редактирования отсутствуют. AssertionError: Если тулбар или кнопка редактирования отсутствуют.
""" """
loc = self.page.get_by_role("navigation").filter( loc = self.page.get_by_role("navigation").filter(
has_text=re.compile("Время жизни сеанса")).locator("div").nth(1) has_text=re.compile("Настройки")).locator("div").nth(1)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'Время жизни сеанса' is missing") self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'Настройки' is missing")
self.toolbar.check_button_visibility("edit") self.toolbar.check_button_visibility("edit")
def should_be_toolbar_buttons(self) -> None: def should_be_toolbar_buttons(self) -> None:
@ -256,6 +263,15 @@ class SessionSettingsTab(BasePage):
self.toolbar.get_button_by_name("cancel").click() self.toolbar.get_button_by_name("cancel").click()
self.toolbar.check_button_visibility("edit") self.toolbar.check_button_visibility("edit")
def should_be_form_toolbar(self) -> None:
"""Проверяет наличие тулбара формы редактирования настроек.
Raises:
AssertionError: Если тулбар отсутствует.
"""
self.settings_form.should_be_toolbar()
def verify_form_data(self, session_settings: dict) -> None: def verify_form_data(self, session_settings: dict) -> None:
"""Проверяет соответствие содержимого полей формы данным из БД. """Проверяет соответствие содержимого полей формы данным из БД.

View File

@ -1,217 +0,0 @@
"""Модуль вкладки настройки СМС уведомлений.
Содержит класс SMSNotificationsSettings для работы с вкладкой настройки СМС уведомлений.
Позволяет проверять состояние и взаимодействовать с элементами вкладки.
"""
import re
from playwright.sync_api import Page
from locators.text_input_locators import TextInputLocators
from locators.settings_form_locators import SettingsFormLocators
from elements.text_input_element import TextInput
from elements.icon_element import Icon
from components.toolbar_component import ToolbarComponent
from components_derived.settings_form_component import SettingsFormComponent
from components_derived.modal_send_test_sms import SendTestSMSModalWindow
from pages.base_page import BasePage
class SMSNotificationsSettingsTab(BasePage):
"""Класс для работы с вкладкой настройки СМС уведомлений.
Предоставляет методы для взаимодействия с вкладкой настройки СМС уведомлений.
Args:
page: Экземпляр страницы Playwright.
"""
def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки настройки СМС уведомлений."""
super().__init__(page)
self.toolbar = ToolbarComponent(page, "СМС")
toolbar_button_edit = self.page.get_by_role("navigation").filter(has_text=re.compile("СМС")). \
locator("//button[@data-testid='NOTIFICATIONS_SMS__btn__edit']")
self.toolbar.add_tooltip_button(toolbar_button_edit, "edit")
toolbar_button_save = self.page.get_by_role("navigation").filter(has_text=re.compile("СМС")). \
locator("//button[@data-testid='NOTIFICATIONS_SMS__btn__submit']")
self.toolbar.add_tooltip_button(toolbar_button_save, "save")
toolbar_button_cancel = self.page.get_by_role("navigation").filter(has_text=re.compile("СМС")). \
locator("//button[@data-testid='NOTIFICATIONS_SMS__btn__cancelEdit']")
self.toolbar.add_tooltip_button(toolbar_button_cancel, "cancel")
# Форма для отображения/редактирования полей настроек СМС уведомлений
self.settings_form = SettingsFormComponent(page)
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
loc = self.input_fields_locators.get("ip")
loc_message_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMS__text-field__ip']")
ip_setting_input = TextInput(page, loc_message_input, "ip_setting_input")
self.settings_form.add_content_item("ip_setting_input", ip_setting_input)
loc = self.input_fields_locators.get("Имя пользователя")
loc_user_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMS__text-field__login']")
user_setting_input = TextInput(page, loc_user_input, "user_setting_input")
self.settings_form.add_content_item("user_setting_input", user_setting_input)
loc = self.input_fields_locators.get("Пароль")
loc_password_input = loc.locator("//input[@data-testid='NOTIFICATIONS_SMS__text-field__password']")
password_setting_input = TextInput(page, loc_password_input, "password_setting_input")
self.settings_form.add_content_item("password_setting_input", password_setting_input)
icon_locator = loc_password_input.locator("../..").locator(TextInputLocators.ICON_PASSWORD_HIDING)
password_hidden_icon = Icon(page, icon_locator,
"password hidden icon")
self.settings_form.add_content_item("password_hidden_icon", password_hidden_icon)
self.settings_form.add_tooltip_button(page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
locator("//button[@data-testid='NOTIFICATIONS_SMS__btn__onSelect']"),
"test_button")
# Действия:
def click_cancel_button(self) -> None:
"""Нажатие кнопки 'Отменить' на тулбаре."""
self.toolbar.check_button_visibility("cancel")
self.toolbar.get_button_by_name("cancel").click()
def click_edit_button(self) -> None:
"""Нажатие кнопки 'Редактировать' на тулбаре."""
self.toolbar.check_button_visibility("edit")
self.toolbar.get_button_by_name("edit").click()
def click_save_button(self) -> None:
"""Нажатие кнопки 'Сохранить' на тулбаре."""
self.toolbar.check_button_visibility("save")
self.toolbar.get_button_by_name("save").click()
def click_password_hidden_icon(self) -> None:
"""Нажатие на иконку скрытия пароля."""
self.settings_form.get_content_item("password_hidden_icon").click()
def click_test_button(self) -> SendTestSMSModalWindow:
"""Нажатие кнопки 'Тест' в форме ввода настроек."""
self.settings_form.check_button_visibility("test_button")
self.settings_form.get_button_by_name("test_button").click()
return SendTestSMSModalWindow(self.page)
def get_ip_setting_value(self) -> str:
"""Возвращает текущее значение поля настроек 'IP'.
Returns:
str : Текущее значение поля настроек 'IP'.
"""
input_field = self.settings_form.get_content_item("ip_setting_input")
return input_field.get_input_value().strip()
def get_user_setting_value(self) -> str:
"""Возвращает текущее значение поля настроек 'Имя пользователя'.
Returns:
str : Текущее значение поля настроек 'Имя пользователя'.
"""
input_field = self.settings_form.get_content_item("user_setting_input")
return input_field.get_input_value().strip()
def get_password_setting_value(self) -> str:
"""Возвращает текущее значение поля настроек 'Пароль'.
Returns:
str : Текущее отображение значения поля настроек 'Пароль'.
"""
input_field = self.settings_form.get_content_item("password_setting_input")
is_hidden_state = self.settings_form.get_content_item("password_hidden_icon").is_password_hidden()
password_value = input_field.get_input_value().strip()
if is_hidden_state:
password_value = "." * len(password_value)
return password_value
def input_ip(self, text: str) -> None:
"""Заполнение поля 'IP'."""
message_input = self.settings_form.get_content_item("ip_setting_input")
message_input.clear()
message_input.input_value(text)
def input_user(self, text: str) -> None:
"""Заполнение поля 'Имя пользователя'."""
message_input = self.settings_form.get_content_item("user_setting_input")
message_input.clear()
message_input.input_value(text)
def input_password(self, text: str) -> None:
"""Заполнение поля 'Пароль'."""
message_input = self.settings_form.get_content_item("password_setting_input")
message_input.clear()
message_input.input_value(text)
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов страницы."""
expected_input_field_names = ["ip", "Имя пользователя", "Пароль"]
self.should_be_toolbar()
actual_input_field_names = self.input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
for name in self.settings_form.content_items.keys():
item = self.settings_form.get_content_item(name)
item.check_visibility(
f"SMS notifications settings input form item with name '{name}' is missing"
)
if name == "password_hidden_icon":
is_hidden_state = item.is_password_hidden()
assert is_hidden_state, "Password hidden icon should be in hidden state"
self.settings_form.check_button_visibility("test_button")
self.settings_form.check_button_tooltip("test_button", "Тест")
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если тулбар или кнопка тулбара отсутствуют.
"""
loc = self.page.get_by_role("navigation").filter(
has_text=re.compile("СМС")).locator("div").nth(1)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'СМС' is missing")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Редактировать")
self.toolbar.get_button_by_name("edit").click()
self.toolbar.check_button_visibility("save")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("save", "Сохранить")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.get_button_by_name("cancel").click()
self.toolbar.check_button_visibility("edit")
def should_be_test_button(self) -> None:
"""Проверяет наличие кнопки 'Тест'.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.settings_form.check_button_visibility("test_button")

View File

@ -32,18 +32,17 @@ class UsersTab(BasePage):
super().__init__(page) super().__init__(page)
locator_button_1 = self.page.get_by_role("navigation").filter(
has_text=re.compile("Пользователи")
).get_by_role("button").nth(0)
locator_button_2 = self.page.get_by_role("navigation").filter(
has_text=re.compile("Пользователи")
).get_by_role("button").nth(1)
self.toolbar = ToolbarComponent(page, "Пользователи") self.toolbar = ToolbarComponent(page, "Пользователи")
toolbar_button_edit = self.page.get_by_role("navigation").filter(has_text=re.compile("Пользователи")). \ self.toolbar.add_tooltip_button(locator_button_1, "edit")
locator("//button[@data-testid='USERS__btn__edit']") self.toolbar.add_tooltip_button(locator_button_1, "add_user")
self.toolbar.add_tooltip_button(toolbar_button_edit, "edit") self.toolbar.add_tooltip_button(locator_button_2, "close")
toolbar_button_add_user = self.page.get_by_role("navigation").filter(has_text=re.compile("Пользователи")). \
locator("//button[@data-testid='USERS__btn__onAdd']")
self.toolbar.add_tooltip_button(toolbar_button_add_user, "add_user")
toolbar_button_close = self.page.get_by_role("navigation").filter(has_text=re.compile("Пользователи")). \
locator("//button[@data-testid='USERS__btn__close']")
self.toolbar.add_tooltip_button(toolbar_button_close, "close")
self.users_table = TableComponent(page) self.users_table = TableComponent(page)
self.modal_windows = {} self.modal_windows = {}
@ -248,18 +247,6 @@ class UsersTab(BasePage):
assert False, f"Modal window with title '{title}' not found" assert False, f"Modal window with title '{title}' not found"
return modal_window return modal_window
def get_rows_count(self) -> int:
"""Возвращает количество строк в таблице пользователей (без заголовка).
Returns:
int: Количество строк с данными.
Raises:
AssertionError: Если таблица пуста.
"""
return self.users_table.get_rows_count(TableLocators.TABLE_WORK_AREA)
def open_add_user_window(self) -> None: def open_add_user_window(self) -> None:
"""Открывает окно добавления пользователя. """Открывает окно добавления пользователя.
@ -408,21 +395,6 @@ class UsersTab(BasePage):
if verify: if verify:
self.verify_users_table_content(table_content) self.verify_users_table_content(table_content)
def check_users_table_row_highlighting(self, row_index: int) -> None:
"""Проверяет выделение указанной строки таблицы.
Args:
row_index: Индекс проверяемой строки.
Raises:
AssertionError: Если строка не выделена.
"""
self.users_table.check_row_highlighting(
TableLocators.TABLE_WORK_AREA,
row_index
)
def should_be_toolbar(self) -> None: def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара. """Проверяет наличие тулбара.

View File

@ -8,8 +8,7 @@ from playwright.sync_api import Page
from tools.logger import get_logger from tools.logger import get_logger
from locators.table_locators import TableLocators from locators.table_locators import TableLocators
from locators.modal_window_locators import ModalWindowLocators from locators.modal_window_locators import ModalWindowLocators
from components_derived.modal_view_ztp_template import ViewZTPTemplateModalWindow from components_derived.modal_view_template import ViewTemplateModalWindow
from components.modal_window_component import ModalWindowComponent
from components.toolbar_component import ToolbarComponent from components.toolbar_component import ToolbarComponent
from components.table_component import TableComponent from components.table_component import TableComponent
from pages.base_page import BasePage from pages.base_page import BasePage
@ -44,16 +43,16 @@ class ZTPTemplatesTab(BasePage):
Args: Args:
title: Заголовок окна. title: Заголовок окна.
""" """
self.modal_windows[title] = ViewZTPTemplateModalWindow(self.page, title) self.modal_windows[title] = ViewTemplateModalWindow(self.page, title)
def get_modal_window(self, title: str) -> ViewZTPTemplateModalWindow: def get_modal_window(self, title: str):
"""Возвращает модальное окно по заголовку. """Возвращает модальное окно по заголовку.
Args: Args:
title: Заголовок окна. title: Заголовок окна.
Returns: Returns:
ViewZTPTemplateModalWindow: Экземпляр модального окна шаблона. ViewTemplateModalWindow: Экземпляр модального окна шаблона.
Raises: Raises:
AssertionError: Если окно не найдено. AssertionError: Если окно не найдено.
@ -92,14 +91,14 @@ class ZTPTemplatesTab(BasePage):
row_locator.click() row_locator.click()
# Создаем временный экземпляр модального окна для получения заголовка # Создаем временный экземпляр модального окна для получения заголовка
temp_modal = ViewZTPTemplateModalWindow(self.page, "") temp_modal = ViewTemplateModalWindow(self.page, "")
title = temp_modal.toolbar.get_toolbar_title_text( template_name = temp_modal.toolbar.get_toolbar_title_text(
ModalWindowLocators.MODAL_WINDOW_TITLE ModalWindowLocators.MODAL_WINDOW_TITLE
) )
# Добавляем модальное окно в коллекцию после открытия # Добавляем модальное окно в коллекцию после открытия
self.add_modal_window(title) self.add_modal_window(template_name)
return title return template_name
def close_modal_window_by_toolbar_button(self, title: str) -> None: def close_modal_window_by_toolbar_button(self, title: str) -> None:
"""Закрывает модальное окно через кнопку в тулбаре. """Закрывает модальное окно через кнопку в тулбаре.
@ -108,17 +107,7 @@ class ZTPTemplatesTab(BasePage):
title: Заголовок окна. title: Заголовок окна.
""" """
modal_window = self.get_modal_window(title) modal_window = self.get_modal_window(title)
modal_window.close_window_by_toolbar_button() modal_window.click_toolbar_close_button()
self.delete_modal_window(title)
def close_modal_window(self, title: str) -> None:
"""Закрывает модальное окно через кнопку 'Закрыть'.
Args:
title: Заголовок окна.
"""
modal_window = self.get_modal_window(title)
modal_window.close_window()
self.delete_modal_window(title) self.delete_modal_window(title)
def get_rows_count(self) -> int: def get_rows_count(self) -> int:
@ -142,27 +131,29 @@ class ZTPTemplatesTab(BasePage):
def scroll_modal_up(self) -> None: def scroll_modal_up(self) -> None:
"""Прокручивает содержимое модального окна вверх.""" """Прокручивает содержимое модального окна вверх."""
temp_modal = ModalWindowComponent(self.page) self.ztp_templates_table.scroll_up(
temp_modal.scroll_window_up() ModalWindowLocators.MODAL_WINDOW_SCROLL_CONTAINER
)
def scroll_modal_down(self) -> None: def scroll_modal_down(self) -> None:
"""Прокручивает содержимое модального окна вниз.""" """Прокручивает содержимое модального окна вниз."""
temp_modal = ModalWindowComponent(self.page) self.ztp_templates_table.scroll_down(
temp_modal.scroll_window_down() ModalWindowLocators.MODAL_WINDOW_SCROLL_CONTAINER
)
def check_ztp_templates_modal_content(self, title: str) -> None: def check_templates_modal_content(self, template_name: str) -> None:
"""Проверяет наличие и корректность элементов модального окна шаблона. """Проверяет наличие и корректность элементов модального окна шаблона.
Args: Args:
title: Имя шаблона для проверки заголовка окна. template_name: Имя шаблона для проверки заголовка окна.
Raises: Raises:
AssertionError: Если элементы окна некорректны. AssertionError: Если элементы окна некорректны.
""" """
modal_window = self.get_modal_window(title) modal_window = self.get_modal_window(template_name)
modal_window.check_content() modal_window.check_content()
def check_ztp_templates_table_content(self) -> None: def check_templates_table_content(self) -> None:
"""Проверяет содержимое таблицы шаблонов. """Проверяет содержимое таблицы шаблонов.
Проверяет заголовки и наличие данных в таблице. Проверяет заголовки и наличие данных в таблице.
@ -265,10 +256,11 @@ class ZTPTemplatesTab(BasePage):
Returns: Returns:
bool: True если скроллинг возможен, иначе False. bool: True если скроллинг возможен, иначе False.
""" """
temp_modal = ModalWindowComponent(self.page) return self.ztp_templates_table.is_scrollable_vertically(
return temp_modal.check_window_vertical_scrolling() ModalWindowLocators.MODAL_WINDOW_SCROLL_CONTAINER
)
def verify_template_data_with_api(self, title: str) -> None: def verify_template_data_with_api(self, template_name: str) -> None:
"""Проверяет соответствие данных модального окна данным из API. """Проверяет соответствие данных модального окна данным из API.
Процесс проверки: Процесс проверки:
@ -279,7 +271,7 @@ class ZTPTemplatesTab(BasePage):
5. Выбрасывает assertion при обнаружении расхождений 5. Выбрасывает assertion при обнаружении расхождений
Args: Args:
title: Имя шаблона для проверки (должно совпадать с id в API). template_name: Имя шаблона для проверки (должно совпадать с id в API).
Raises: Raises:
AssertionError: Если: AssertionError: Если:
@ -289,14 +281,14 @@ class ZTPTemplatesTab(BasePage):
- Имя шаблона в модальном окне не соответствует ожидаемому - Имя шаблона в модальном окне не соответствует ожидаемому
""" """
# Получаем модальное окно # Получаем модальное окно
modal_window = self.get_modal_window(title) modal_window = self.get_modal_window(template_name)
# Читаем данные модального окна # Читаем данные модального окна (метод теперь в ViewTemplateModalWindow)
actual_data = modal_window.get_modal_window_data() actual_data = modal_window.get_modal_window_data()
# Читаем данные из API # Читаем данные из API
encoded_title = title.replace(" ", "%20") encoded_template_name = template_name.replace(" ", "%20")
url = f"e-nms/DHCP/showOptPattern?template={encoded_title}" url = f"e-nms/DHCP/showOptPattern?template={encoded_template_name}"
response = self.send_get_api_request(url) response = self.send_get_api_request(url)
# Проверяем статус ответа # Проверяем статус ответа
@ -308,5 +300,5 @@ class ZTPTemplatesTab(BasePage):
response_data = response.json() response_data = response.json()
template_data = response_data['data'] template_data = response_data['data']
# Сравниваем actual_data с данными конкретного шаблона # Сравниваем actual_data с данными конкретного шаблона (метод теперь в ViewTemplateModalWindow)
modal_window.compare_modal_with_api_data(actual_data, template_data, title) modal_window.compare_modal_with_api_data(actual_data, template_data, template_name)

View File

@ -1,5 +1,5 @@
pytest -s -v --s="{'width': 300, 'height': 420}" test_navigation_panel.py pytest -s -v --s="{'width': 300, 'height': 420}" test_navigation_panel.py
pytest -s -v --s="{'width': 1500, 'height': 420}" test_services_table.py pytest -s -v --s="{'width': 1500, 'height': 420}" test_services_table.py
pytest -s -v --s="{'width': 300, 'height': 600}" test_json_container.py pytest -s -v --s="{'width': 300, 'height': 420}" test_json_container.py
pytest -s -v --s="{'width': 300, 'height': 420}" test_user_modal_window.py pytest -s -v --s="{'width': 300, 'height': 420}" test_user_modal_window.py
pytest -s -v --s="{'width': 800, 'height': 200}" test_session_settings.py pytest -s -v --s="{'width': 800, 'height': 200}" test_session_settings.py

View File

@ -54,10 +54,10 @@ class TestNavigationPanel:
# Действия: # Действия:
# Прокручиваем вверх и проверяем видимость элемента # Прокручиваем вверх и проверяем видимость элемента
mp.scroll_navigation_panel_up() mp.scroll_navigation_panel_up()
mp.check_navigation_panel_item_visibility("Панели") mp.check_navigation_panel_item_visibility("Панель приборов")
mp.wait_for_timeout(3000) mp.wait_for_timeout(3000)
# Прокручиваем вниз и проверяем видимость элемента Настройки/ZTP/Шаблоны # Прокручиваем вниз и проверяем видимость элемента Настройки/ZTP/Шаблоны
mp.scroll_navigation_panel_down() mp.scroll_navigation_panel_down()
mp.check_navigation_panel_item_visibility("Шаблоны") mp.check_navigation_panel_item_visibility("Шаблоны_2")
mp.wait_for_timeout(2000) mp.wait_for_timeout(2000)

View File

@ -28,7 +28,7 @@ class TestServiceStatusTable:
mp.should_be_navigation_panel() mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки") mp.click_main_navigation_panel_item("Настройки")
mp.click_subpanel_item("Обслуживание и диагностика") mp.click_subpanel_item("Обслуживание и диагностика")
mp.click_subpanel_item("Статус компонентов") mp.click_subpanel_item("Статус обслуживания")
def test_scrolling(self, browser: Page) -> None: def test_scrolling(self, browser: Page) -> None:
"""Проверяет прокрутку таблицы статусов сервисов. """Проверяет прокрутку таблицы статусов сервисов.

View File

@ -32,7 +32,7 @@ class TestSessionSettingsForm:
mp.click_subpanel_item("Настройки", parent="Сеансы") mp.click_subpanel_item("Настройки", parent="Сеансы")
def test_scrolling(self, browser: Page) -> None: def test_scrolling(self, browser: Page) -> None:
"""Проверяет прокрутку формы редактирования настроек. """Проверяет прокрутку таблицы статусов сервисов.
Args: Args:
browser: Экземпляр страницы Playwright. browser: Экземпляр страницы Playwright.
@ -55,5 +55,5 @@ class TestSessionSettingsForm:
sst.wait_for_timeout(3000) sst.wait_for_timeout(3000)
sst.scroll_up() sst.scroll_up()
sst.get_field_by_name('administrator').check_visibility("Text 'Администратор' should be visible") sst.should_be_form_toolbar()
sst.wait_for_timeout(2000) sst.wait_for_timeout(2000)

View File

@ -78,7 +78,7 @@ class TestUsersModalWindow:
ut = UsersTab(browser) ut = UsersTab(browser)
ut.open_add_user_window() ut.open_add_user_window()
modal_window = ut.get_modal_window("add_user") modal_window = ut.get_modal_window("add_local_user")
is_scrollable_vertically = modal_window.check_window_vertical_scrolling() is_scrollable_vertically = modal_window.check_window_vertical_scrolling()
assert is_scrollable_vertically, "Should be vertical scrolling" assert is_scrollable_vertically, "Should be vertical scrolling"

View File

@ -20,7 +20,7 @@ class TestDateInputComponent:
# @pytest.mark.develop # @pytest.mark.develop
def test_date_input_content(self, browser: Page) -> None: def test_date_input_content(self, browser: Page) -> None:
"""Проверяет содержимое компонента ввода даты. """Проверяет содержимое компонента ввода даты.
put
Args: Args:
browser: Экземпляр страницы Playwright. browser: Экземпляр страницы Playwright.
""" """
@ -61,8 +61,7 @@ class TestDateInputComponent:
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
# Temporarily due to error in UI if date_input.is_text_input_mode():
if not date_input.is_text_input_mode():
# выбираем 15 января 2024, проверяем результат # выбираем 15 января 2024, проверяем результат
expected_date = "15.01.2024" expected_date = "15.01.2024"
date_input.input_date(expected_date) date_input.input_date(expected_date)
@ -96,8 +95,7 @@ class TestDateInputComponent:
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
# Temporarily due to error in UI if date_input.is_text_input_mode():
if not date_input.is_text_input_mode():
try: try:
date_input.input_date("1.01.2020") date_input.input_date("1.01.2020")
except AssertionError as e: except AssertionError as e:
@ -171,8 +169,7 @@ class TestDateInputComponent:
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
# Temporarily due to error in UI if date_input.is_text_input_mode():
if not date_input.is_text_input_mode():
# выбираем 15 января 2024 10:11, проверяем результат # выбираем 15 января 2024 10:11, проверяем результат
date_input.input_date("15.01.2024") date_input.input_date("15.01.2024")
browser.wait_for_timeout(300) browser.wait_for_timeout(300)
@ -211,8 +208,7 @@ class TestDateInputComponent:
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
# Temporarily due to error in UI if date_input.is_text_input_mode():
if not date_input.is_text_input_mode():
assert False, "Should be date input mode by date picker" assert False, "Should be date input mode by date picker"
else: else:
# выбираем 16 января 2024 08:11, проверяем результат # выбираем 16 января 2024 08:11, проверяем результат
@ -249,27 +245,26 @@ class TestDateInputComponent:
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
# Temporarily due to error in UI if date_input.is_text_input_mode():
if not date_input.is_text_input_mode():
# выбираем 15 января 2024 10:11, проверяем результат # выбираем 15 января 2024 10:11, проверяем результат
date_input.input_date("15.01.2024") date_input.input_date("15.01.2024")
browser.wait_for_timeout(300) browser.wait_for_timeout(300)
try: try:
date_input.input_time("1:01") date_input.time_date("1:01")
except AssertionError as e: except AssertionError as e:
actual_err = f"{e}" actual_err = f"{e}"
expected_err = "Incorrect time format: should be 'hh:mm'" expected_err = "Incorrect time format: should be 'hh:mm'"
assert expected_err == actual_err, \ assert expected_err == actual_err, \
f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'"
try: try:
date_input.input_date("o2.01.2024") date_input.input_date("o2.01")
except AssertionError as e: except AssertionError as e:
actual_err = f"{e}" actual_err = f"{e}"
expected_err = "Incorrect day value o2 for selection" expected_err = "Incorrect hours value o2 for selection"
assert expected_err == actual_err, \ assert expected_err == actual_err, \
f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'"
try: try:
date_input.input_time("01:1") date_input.input_time("01:1")
@ -277,14 +272,14 @@ class TestDateInputComponent:
actual_err = f"{e}" actual_err = f"{e}"
expected_err = "Incorrect time format: should be 'hh:mm'" expected_err = "Incorrect time format: should be 'hh:mm'"
assert expected_err == actual_err, \ assert expected_err == actual_err, \
f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'"
try: try:
date_input.input_date("01.o1.2024") date_input.input_date("01:o1")
except AssertionError as e: except AssertionError as e:
actual_err = f"{e}" actual_err = f"{e}"
expected_err = "Incorrect month value o1 for selection" expected_err = "Incorrect minutes value o1 for selection"
assert expected_err == actual_err, \ assert expected_err == actual_err, \
f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'"
else: else:
assert False, "Should be text date input mode" assert False, "Should be text date input mode"

View File

@ -179,9 +179,6 @@ class TestDatePickerComponent:
browser.wait_for_timeout(300) browser.wait_for_timeout(300)
month_num = months.index(current_month) + 1 month_num = months.index(current_month) + 1
if month_num < 10:
expected_date = f"15.{month_num:02d}.{current_year}"
else:
expected_date = f"15.{month_num}.{current_year}" expected_date = f"15.{month_num}.{current_year}"
actual_date = date_input.get_date_field_value() actual_date = date_input.get_date_field_value()
assert expected_date == actual_date, \ assert expected_date == actual_date, \

View File

@ -1,115 +0,0 @@
"""Модуль тестов вкладки настройки Keycloak Аутентификации.
Содержит тесты для проверки корректности отображения
и функциональности элементов страницы настройки Keycloak Аутентификации.
"""
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.keycloak_settings_tab import KeycloakAuthSettingsTab
# @pytest.mark.smoke
class TestKeycloakAuthSettingsTab:
"""Набор тестов для вкладки настройки Keycloak Аутентификации.
Проверяет корректность отображения и функциональность элементов вкладки настройки LDAP Аутентификации.
"""
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход на вкладку настройки Keycloak Аутентификации через панель навигации
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Инициализация главной страницы
main_page = MainPage(browser)
# Проверка и взаимодействие с элементами навигации
main_page.should_be_navigation_panel()
main_page.click_main_navigation_panel_item("Настройки")
main_page.click_subpanel_item("Аутентификация")
main_page.click_subpanel_item("KEYCLOAK")
# @pytest.mark.develop
def test_keycloak_auth_settings_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки Keycloak Аутентификации.
Проверяет:
Наличие и корректность элементов интерфейса
"""
current_settings = {}
default_settings = {}
# Инициализация вкладки
keycloak_auth_settings_tab = KeycloakAuthSettingsTab(browser)
# запрос текущих установок
cur_settings_response = keycloak_auth_settings_tab.send_get_api_request("e-cmdb/api/keycloack")
if cur_settings_response.status == 200:
response_body = keycloak_auth_settings_tab.get_response_body(cur_settings_response)
if response_body:
current_settings = response_body[0].copy()
# запрос дефолтных значений
default_settings_response = keycloak_auth_settings_tab.send_get_api_request("e-cmdb/api/keycloack/meta")
if default_settings_response.status == 200:
response_body = keycloak_auth_settings_tab.get_response_body(default_settings_response)
if response_body:
default_settings = response_body["fields"].copy()
# Проверка элементов интерфейса
keycloak_auth_settings_tab.check_content()
# Получение и проверка отображаемых входных значений формы настроек
settings = keycloak_auth_settings_tab.get_current_setting_values()
value = settings["url"]
expected = current_settings["url"]
if expected is None:
expected = self._get_default_value("url", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'URL'"
value = settings["url_token"]
expected = current_settings["url_token"]
if expected is None:
expected = self._get_default_value("url_token", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'URL_TOKEN'"
value = settings["clientid"]
expected = current_settings["clientid"]
if expected is None:
expected = self._get_default_value("clientid", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'CLIENTID'"
value = settings["clientsecret"]
expected = current_settings["clientsecret"]
if expected is None:
expected = self._get_default_value("clientsecret", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'CLIENTSECRET'"
value = settings["redirect_uri"]
expected = current_settings["redirect_uri"]
if expected is None:
expected = self._get_default_value("redirect_uri", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'REDIRECT_URI'"
def _get_default_value(self, setting_name: str, default_settings: dict) -> str| None:
for setting in default_settings:
if setting["name"] == setting_name:
return setting["default"]
return None

View File

@ -1,143 +0,0 @@
"""Модуль тестов вкладки настройки LDAP Аутентификации.
Содержит тесты для проверки корректности отображения
и функциональности элементов страницы настройки LDAP Аутентификации.
"""
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.ldap_settings_tab import LDAPAuthSettingsTab
# @pytest.mark.smoke
class TestLDAPAuthSettingsTab:
"""Набор тестов для вкладки настройки LDAP Аутентификации.
Проверяет корректность отображения и функциональность элементов вкладки настройки LDAP Аутентификации.
"""
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход на вкладку настройки LDAP Аутентификации через панель навигации
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Инициализация главной страницы
main_page = MainPage(browser)
# Проверка и взаимодействие с элементами навигации
main_page.should_be_navigation_panel()
main_page.click_main_navigation_panel_item("Настройки")
main_page.click_subpanel_item("Аутентификация")
main_page.click_subpanel_item("LDAP")
# @pytest.mark.develop
def test_ldap_auth_settings_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки LDAP Аутентификации.
Проверяет:
Наличие и корректность элементов интерфейса
"""
current_settings = {}
default_settings = {}
# Инициализация вкладки
ldap_auth_settings_tab = LDAPAuthSettingsTab(browser)
# запрос текущих установок
cur_settings_response = ldap_auth_settings_tab.send_get_api_request("e-cmdb/api/ldap")
if cur_settings_response.status == 200:
response_body = ldap_auth_settings_tab.get_response_body(cur_settings_response)
if response_body:
current_settings = response_body[0].copy()
# запрос дефолтных значений
default_settings_response = ldap_auth_settings_tab.send_get_api_request("e-cmdb/api/ldap/meta")
if default_settings_response.status == 200:
response_body = ldap_auth_settings_tab.get_response_body(default_settings_response)
if response_body:
default_settings = response_body["fields"].copy()
# Проверка элементов интерфейса
ldap_auth_settings_tab.check_content()
# Получение и проверка отображаемых входных значений формы настроек
settings = ldap_auth_settings_tab.get_current_setting_values()
value = settings["ip"]
expected = current_settings["ip"]
if expected is None:
expected = self._get_default_value("ip", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'IP'"
value = settings["Порт"]
expected = current_settings["port"]
if expected is None:
expected = self._get_default_value("port", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'Порт'"
value = settings["Имя пользователя"]
expected = current_settings["login"]
if expected is None:
expected = self._get_default_value("login", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'Имя пользователя'"
value = settings["Пароль"]
expected = current_settings["password"]
if expected is None:
expected = self._get_default_value("password", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Пароль'"
value = settings["Домен"]
expected = current_settings["domain"]
if expected is None:
expected = self._get_default_value("domain", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Домен'"
value = settings["DN каталог пользователей"]
expected = current_settings["base_dn"]
if expected is None:
expected = self._get_default_value("base_dn", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'DN каталог пользователей'"
value = settings["Атрибут авторизации"]
expected = current_settings["login_attribute"]
if expected is None:
expected = self._get_default_value("login_attribute", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Атрибут авторизации'"
value = settings["Атрибут email"]
expected = current_settings["email_attribute"]
if expected is None:
expected = self._get_default_value("email_attribute", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Атрибут email'"
value = settings["Атрибут sms"]
expected = current_settings["sms_attribute"]
if expected is None:
expected = self._get_default_value("sms_attribute", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Атрибут sms'"
def _get_default_value(self, setting_name: str, default_settings: dict) -> str| None:
for setting in default_settings:
if setting["name"] == setting_name:
return setting["default"]
return None

View File

@ -0,0 +1,626 @@
"""Тест создания дочернего элемента 'Стойка'."""
import pytest
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from locators.rack_locators import RackLocators
from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData
from components_derived.frames.create_child_element_frame import CreateChildElementFrame
from pages.location_page import LocationPage
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.rack_page import RackPage
logger = get_logger("CREATE_RACK_ELEMENT_TEST")
logger.setLevel("INFO")
# @pytest.mark.smoke
class TestCreateRackElement:
"""Тест создания дочернего элемента типа 'Стойка'.
Тесты покрывают следующие сценарии:
1. test_create_rack_content: Проверяет содержимое формы создания стойки
2. test_create_rack_child_element: Проверяет создание дочернего элемента типа 'Стойка'
3. test_create_rack_with_duplicate_name: Проверяет создание стойки с дублирующимся именем
4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки
"""
# Инициализируем атрибуты
main_page: MainPage = None
location_page: LocationPage = None
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Мы на главной странице
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone")
# Создаем экземпляр страницы локации
self.location_page = LocationPage(browser)
@pytest.fixture
def cleanup_racks(self, browser: Page):
"""Фикстура для очистки созданных стоек."""
# Список для хранения созданных в тесте стоек
created_racks = []
yield created_racks
# После завершения теста удаляем созданные стойки
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()
# Создаем фрейм создания дочернего элемента
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()
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. Создаем экземпляр ModalRackEdit и используем его метод
rack_edit = ModalRackEdit(browser)
# Проверяем видимость кнопки удаления в модальном окне
rack_edit.check_toolbar_button_visibility("remove")
# Используем метод из ModalRackEdit для удаления
logger.debug("Clicking remove button...")
rack_edit.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('Создать дочерний элемент в')
logger.debug("Test completed successfully")
def test_create_rack_child_element(self, browser: Page, cleanup_racks) -> None:
"""Тест создания дочернего элемента типа 'Стойка'."""
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных стойки
rack_data = RackData(
name="Test-Rack-01",
height="42",
depth="1000",
serial="TEST123456",
inventory="INV-001",
comment="Тестовая стойка для автоматизации",
state="Введен в эксплуатацию",
cable_entry="сверху"
)
# Сохраняем имя стойки в переменную
rack_name = rack_data.name
cleanup_racks.append(rack_name)
# Заполняем данные стойки
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку "Добавить"
create_child_frame.click_add_button()
# 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")
def test_create_rack_content(self, browser: Page) -> None:
"""Тест проверки содержимого формы создания стойки."""
# Проверяем что кнопка "Создать" доступна
self.location_page.should_be_toolbar_buttons()
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Проверяем заголовок формы создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Проверяем что после выбора 'Стойка' появляются специфичные поля
rack_maker.check_rack_fields_presence()
logger.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")
rack_name = "Test-Rack-Duplicate"
# Проверяем, существует ли уже стойка с таким именем
if not self._check_rack_existance(browser, rack_name):
logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.")
self._create_rack(browser, rack_name)
logger.debug(f"First rack with name '{rack_name}' created successfully")
# Добавляем стойку в список для очистки
cleanup_racks.append(rack_name)
else:
logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one")
# Создаем вторую стойку с тем же именем
logger.debug(f"Attempting to create second rack with name '{rack_name}'")
# Нажимаем кнопку "Создать" на тулбаре
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="450"
)
# Пытаемся создать вторую стойку с тем же именем
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000)
# Проверяем наличие alert-окна с сообщением о дублирующемся имени
expected_alert_text = f"Имя {rack_name} уже используется"
create_child_frame.alert.check_alert_presence(expected_alert_text)
# Проверяем, что остались на странице создания (стойка не создана)
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Закрываем alert-окно с помощью кнопки закрытия
create_child_frame.wait_for_timeout(2000)
create_child_frame.alert.close_alert_by_text(expected_alert_text)
logger.debug("System prevented creating rack with duplicate name")
def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None:
"""Тест проверки обязательных полей при создании стойки.
Проверяет, что система корректно валидирует обязательные поля:
- Поле 'Высота в юнитах' должно быть заполнено
- Поле 'Глубина (мм)' должно быть заполнено
"""
# Текст сообщения alert-окна
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Тестовые данные
test_cases = [
{
"name": "Test 1: Required fields are not filled",
"data": {
"name": "Test-Rack-Required-01",
"height": "",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 2: Only 'Height in units' field is filled",
"data": {
"name": "Test-Rack-Required-02",
"height": "42",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 3: Only 'Depth (mm)' field is filled",
"data": {
"name": "Test-Rack-Required-03",
"height": "",
"depth": "1000",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
}
]
# Выполняем тестовые случаи
for test_case in test_cases:
logger.debug(test_case["name"])
self._perform_required_fields_test(
create_child_frame, rack_maker, test_case["data"])
logger.debug("System prevented creating rack with invalid required fields")
# 4. Тест: Заполняем все обязательные поля
logger.debug("Test 4: All required fields are filled")
# Генерируем уникальное имя для финального теста
final_rack_name = "Test-Rack-Required-04"
cleanup_racks.append(final_rack_name)
# **ВАЖНО: Очищаем поля перед заполнением**
logger.debug("Clearing fields before filling...")
# Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = create_child_frame.get_input_fields_locators(container_locator)
# Очищаем поле "Высота в юнитах" если оно заполнено
if "Высота в юнитах" in fields_locators:
if create_child_frame.is_field_filled("Высота в юнитах", container_locator):
logger.debug("Clearing 'Высота в юнитах' field...")
create_child_frame.clear_combobox_field("Высота в юнитах")
create_child_frame.wait_for_timeout(500)
# Очищаем поле "Глубина (мм)" если оно заполнено
if "Глубина (мм)" in fields_locators:
if create_child_frame.is_field_filled("Глубина (мм)", container_locator):
logger.debug("Clearing 'Глубина (мм)' field...")
create_child_frame.clear_combobox_field("Глубина (мм)")
create_child_frame.wait_for_timeout(500)
# Очищаем поле "Имя" если оно заполнено
if "Имя" in fields_locators:
if create_child_frame.is_field_filled("Имя", container_locator):
logger.debug("Clearing 'Имя' field...")
# Специальная обработка для текстового поля
field_container = fields_locators["Имя"]
input_field = field_container.locator("input").first
if input_field.count() > 0:
input_field.click()
input_field.press("Control+A")
input_field.press("Backspace")
create_child_frame.wait_for_timeout(500)
# Создаем объект данных стойки
rack_data = RackData(
name=final_rack_name,
height="42",
depth="1000"
)
# Заполняем все обязательные поля
rack_maker.fill_rack_data(rack_data)
# Проверяем, что ни одно поле не подсвечено цветом ошибки
create_child_frame.check_field_error_not_highlighted("Имя")
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.debug("No required fields are highlighted with error color - all fields filled correctly")
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(500)
# Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача)
# Проверяем наличие 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()
logger.debug("Required fields validation test completed successfully")
# Вспомогательные методы проверки
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}'")
# Обновляем навигационную панель
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
logger.debug(f"Rack with name '{rack_name}' found")
return True
logger.debug(f"Rack with name '{rack_name}' not found")
return False

View File

@ -1,481 +0,0 @@
"""Тест создания дочернего элемента 'Стойка'."""
import pytest
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from frames.create_element_frame import CreateElementFrame
from forms.create_rack_form import CreateRackForm, CreateRackData
from pages.location_page import LocationPage
from makers.edit_rack_maker import EditRackMaker
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.rack_page import RackPage
from components.alert_component import AlertComponent
logger = get_logger("CREATE_RACK_TEST")
logger.setLevel("INFO")
class TestCreateRack:
"""Тест создания дочернего элемента типа 'Стойка'."""
# Единое имя для тестовой стойки
TEST_RACK_NAME = "Test-Rack-Create"
# Для теста с дубликатом используем отдельное имя
DUPLICATE_RACK_NAME = "Test-Rack-Duplicate"
# Инициализируем атрибуты
main_page: MainPage = None
location_page: LocationPage = None
alert: AlertComponent = None
create_child_frame: CreateElementFrame = None
rack_form: CreateRackForm = None
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Args:
browser: Экземпляр страницы Playwright для взаимодействия с UI
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Мы на главной странице
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(2000)
self.main_page.click_main_navigation_panel_item("test-zone")
# Создаем экземпляр страницы локации
self.location_page = LocationPage(browser)
# Инициализируем компонент алертов
self.alert = AlertComponent(browser)
# Инициализируем фрейм создания дочернего элемента
self.create_child_frame = CreateElementFrame(browser)
# Инициализируем форму создания Стойки
self.rack_form = CreateRackForm(browser)
@pytest.fixture
def cleanup_racks(self, browser: Page):
"""Фикстура для очистки созданных стоек."""
created_racks = []
yield created_racks
# После завершения теста удаляем созданные стойки
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(browser, rack_name)
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(500)
def _create_rack(self, browser: Page, rack_data: CreateRackData) -> None:
"""Создает стойку с использованием унифицированного подхода.
Args:
browser: Страница Playwright
rack_data: Данные стойки для создания
"""
logger.debug(f"Creating rack with name '{rack_data.name}'")
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Нажимаем на плашку "Класс объекта учета"
self.create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
self.create_child_frame.select_object_class("Стойка")
# Создаем форму создания стойки
rack_form = CreateRackForm(browser)
# Заполняем данные стойки
fill_results = rack_form.fill_rack_data(rack_data)
logger.debug(f"Fill results: {fill_results}")
# Нажимаем кнопку создания
self.create_child_frame.click_add_button()
# Ждем появления alert с текстом
expected_alert_text = f"Успешно создано"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.info(f"Rack '{rack_data.name}' created successfully")
def _delete_rack(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
# Находим элемент стойки в навигационной панели
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)
# Проверяем и нажимаем кнопку "Изменить"
rack_page = RackPage(browser)
# Проверяем видимость и тултип кнопки
rack_page.should_be_toolbar_buttons()
# Кликаем на кнопку "Изменить"
rack_page.click_edit_button()
self.main_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, rack_name)
# Используем метод для удаления
rack_edit.click_remove_button()
# Проверяем уведомление об успешном удалении
expected_alert_text = "Успешно удалено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.info(f"Rack '{rack_name}' deleted successfully")
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}'")
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
logger.debug(f"Rack with name '{rack_name}' found")
return True
logger.debug(f"Rack with name '{rack_name}' not found")
return False
def test_create_rack_content(self, browser: Page) -> None:
"""Тест проверки содержимого формы создания стойки."""
self.main_page.wait_for_timeout(7000)
# Проверяем что кнопка "Создать" доступна
self.location_page.should_be_toolbar_buttons()
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Проверяем заголовок формы создания
self.create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Нажимаем на плашку "Класс объекта учета"
self.create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
self.create_child_frame.select_object_class("Стойка")
# Создаем форму создания стойки и проверяем наличие полей
rack_form = CreateRackForm(browser)
# Проверяем, что основные поля присутствуют
assert rack_form.get_content_item("name_input") is not None, "Name field not initialized"
assert rack_form.get_content_item("usize_input") is not None, "Height field not initialized"
assert rack_form.get_content_item("depth_input") is not None, "Depth field not initialized"
logger.debug("Rack-specific fields are displayed correctly")
self.create_child_frame.should_be_toolbar_buttons()
def test_create_rack(self, browser: Page, cleanup_racks) -> None:
"""Тест создания дочернего элемента типа 'Стойка'."""
logger.debug(f"Starting test with rack name: {self.TEST_RACK_NAME}")
# Создаем данные стойки с расширенным набором полей
rack_data = CreateRackData(
name=self.TEST_RACK_NAME,
usize="42",
depth="1000",
serial="TEST123456",
inventory="INV-001",
comment="Тестовая стойка для автоматизации",
cable_entry="сверху",
state="Введен в эксплуатацию",
# owner="Владелец",
# service_org="Обслуживающая организация",
# project="Проект/Титул"
)
# Сохраняем имя стойки для очистки
cleanup_racks.append(rack_data.name)
# Проверяем, существует ли уже стойка с таким именем
if self._check_rack_existance(browser, rack_data.name):
logger.warning(f"Rack '{rack_data.name}' already exists. Deleting it before creating new one...")
# Переходим к стойке для удаления
self.main_page.click_subpanel_item(rack_data.name, parent="test-zone")
self.main_page.wait_for_timeout(1000)
# Удаляем существующую стойку
self._delete_rack(browser, rack_data.name)
logger.debug(f"Existing rack '{rack_data.name}' deleted successfully")
# Создаем новую стойку
self._create_rack(browser, rack_data)
# Проверяем, что стойка создана и отображается
logger.debug(f"Verifying that rack '{rack_data.name}' was created...")
self.main_page.click_main_navigation_panel_item("test-zone")
rack_exists = self._check_rack_existance(browser, rack_data.name)
assert rack_exists, f"Rack '{rack_data.name}' should be visible in navigation panel after creation"
logger.debug(f"Rack '{rack_data.name}' is visible in navigation panel")
logger.debug("Test for creating 'Rack' child element completed successfully")
def test_create_rack_with_duplicate_name(self, browser: Page, cleanup_racks) -> None:
"""Тест создания стойки с уже существующим именем."""
logger.debug(f"Starting test for creating rack with duplicate name: {self.DUPLICATE_RACK_NAME}")
rack_name = self.DUPLICATE_RACK_NAME
# Создаем первую стойку если её нет
if not self._check_rack_existance(browser, rack_name):
logger.debug(f"Creating first rack with name '{rack_name}'")
first_rack_data = CreateRackData(
name=rack_name,
usize="42",
depth="1000"
)
self._create_rack(browser, first_rack_data)
cleanup_racks.append(rack_name)
# Пытаемся создать вторую стойку с тем же именем
logger.debug(f"Attempting to create second rack with name '{rack_name}'")
self.location_page.click_create_button()
# Нажимаем на плашку "Класс объекта учета"
self.create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
self.create_child_frame.select_object_class("Стойка")
rack_form = CreateRackForm(browser)
duplicate_rack_data = CreateRackData(
name=rack_name,
usize="42",
depth="450"
)
self.create_child_frame.check_toolbar_title('Создать дочерний элемент в')
rack_form.fill_rack_data(duplicate_rack_data)
self.create_child_frame.click_add_button()
expected_alert_text = f"Имя {rack_name} уже используется"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.debug("System prevented creating rack with duplicate name")
@pytest.mark.develop
def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None:
"""Тест проверки обязательных полей при создании стойки."""
logger.debug("Starting required fields validation test")
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
expected_alert_text_name = "Поле Имя должно быть заполнено"
self.main_page.click_main_navigation_panel_item("test-zone")
# Открываем форму создания
self.location_page.click_create_button()
# Нажимаем на плашку "Класс объекта учета"
self.create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
self.create_child_frame.select_object_class("Стойка")
rack_form = CreateRackForm(browser)
# ========== Тест 1: Обязательные поля имя, высота и глубина пустые ==========
logger.debug("Test 1: Both required fields (height, depth) are empty")
# Очищаем поля имя, высоты и глубины перед заполнением
rack_form.clear_field("Имя")
rack_form.clear_field("Высота в юнитах")
rack_form.clear_field("Глубина (мм)")
test_data_1 = CreateRackData(
name="",
usize="",
depth=""
)
rack_form.fill_rack_data(test_data_1)
self.create_child_frame.click_add_button()
# Проверяем alert для имени, высоты, глубины
self.alert.check_alert_presence(expected_alert_text_name, timeout=7000)
self.alert.check_alert_presence(expected_alert_text_height, timeout=7000)
self.alert.check_alert_presence(expected_alert_text_depth, timeout=7000)
# Проверяем, закрылся ли автоматически alert для имени, высоты, глубины
self.alert.check_alert_absence(expected_alert_text_name, timeout=7000)
self.alert.check_alert_absence(expected_alert_text_height, timeout=500)
self.alert.check_alert_absence(expected_alert_text_depth, timeout=500)
# Проверяем подсветку полей
field_status = rack_form.verify_required_fields_highlighted(["Высота в юнитах", "Глубина (мм)"])
logger.debug(f"Field status after test 1: {field_status}")
assert field_status.get("Высота в юнитах"), f"Height field should be highlighted, got: {field_status}"
assert field_status.get("Глубина (мм)"), f"Depth field should be highlighted, got: {field_status}"
# ========== Тест 2: Только высота заполнена ==========
logger.debug("Test 2: Only height field is filled")
# Очищаем поле глубины перед новым заполнением
rack_form.clear_field("Глубина (мм)")
test_data_2 = CreateRackData(
name=self.TEST_RACK_NAME,
usize="42",
depth=""
)
rack_form.fill_rack_data(test_data_2)
self.create_child_frame.click_add_button()
# Проверяем alert для глубины
self.alert.check_alert_presence(expected_alert_text_depth, timeout=7000)
# Проверяем, закрылся ли автоматически alert для глубины
self.alert.check_alert_absence(expected_alert_text_depth, timeout=7000)
# Проверяем подсветку полей
field_status = rack_form.verify_required_fields_highlighted(["Глубина (мм)"])
logger.debug(f"Field status after test 2: {field_status}")
assert field_status.get("Глубина (мм)"), f"Depth field should be highlighted, got: {field_status}"
# ========== Тест 3: Только глубина заполнена ==========
logger.debug("Test 3: Only depth field is filled")
# Очищаем поле высоты перед новым заполнением
rack_form.clear_field("Высота в юнитах")
test_data_3 = CreateRackData(
name=self.TEST_RACK_NAME,
usize="",
depth="1000"
)
rack_form.fill_rack_data(test_data_3)
self.create_child_frame.click_add_button()
# Проверяем alert для высоты
self.alert.check_alert_presence(expected_alert_text_height, timeout=7000)
# Проверяем, закрылся ли автоматически alert для высоты
self.alert.check_alert_absence(expected_alert_text_height, timeout=7000)
# Проверяем подсветку полей
field_status = rack_form.verify_required_fields_highlighted(["Высота в юнитах"])
logger.debug(f"Field status after test 3: {field_status}")
assert field_status.get("Высота в юнитах"), f"Height field should be highlighted, got: {field_status}"
# ========== Тест 4: Поле "Имя" не заполнено ==========
logger.debug("Test 4: Name field is empty")
# Очищаем поле имени
rack_form.clear_field("Имя")
test_data_4 = CreateRackData(
name="",
usize="42",
depth="1000"
)
rack_form.fill_rack_data(test_data_4)
self.create_child_frame.click_add_button()
# Проверяем alert для имени
self.alert.check_alert_presence(expected_alert_text_name, timeout=7000)
# Проверяем, закрылся ли автоматически alert для высоты
self.alert.check_alert_absence(expected_alert_text_name, timeout=7000)
logger.debug("Test 4 completed: System correctly validates empty name field")
# ========== Тест 5: Поле "Имя" не более 35 знаков ==========
# разработчик должен ограничить длину имени - не более 35 знаков

View File

@ -1,45 +1,144 @@
"""Модуль тестов редактирования стойки в модуле Объекты. """Модуль тестов вкладки 'Стойка' в модуле Объекты.
Содержит тесты для проверки функциональности Содержит тесты для проверки функциональности
редактирования стойки оборудования. работы со стойкой оборудования.
""" """
import os import os
import pytest import pytest
from playwright.sync_api import Page from playwright.sync_api import Page
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators from locators.navigation_panel_locators import NavigationPanelLocators
from frames.create_element_frame import CreateElementFrame
from forms.create_rack_form import CreateRackForm, CreateRackData
from makers.edit_rack_maker import EditRackMaker, EditRackData
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.location_page import LocationPage
from pages.rack_page import RackPage from pages.rack_page import RackPage
from components.alert_component import AlertComponent from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData
from components_derived.frames.create_child_element_frame import CreateChildElementFrame
from components_derived.modal_edit_rack import ModalEditRack, RackEditData
from tools.logger import get_logger
# Константы
RACK_NAME = "Test-Rack-Functionality"
logger = get_logger("RACK_EDIT_TESTS") # Инициализация логгера для всего модуля
logger = get_logger("RACK_TESTS")
logger.setLevel("INFO") logger.setLevel("INFO")
class TestRackTab:
"""Набор тестов для вкладки 'Стойка' в модуле Объекты.
class TestRackEdit: Проверяет корректность отображения, функциональность элементов интерфейса
"""Набор тестов для редактирования стойки в модуле Объекты. и переключение между вкладками стойки оборудования.
Проверяет функциональность редактирования различных вкладок стойки: Тесты покрывают следующие функциональные области:
1. Общая информация 1. test_rack_general_info_tab_fields - Заполнение полей вкладки 'Общая информация'
2. Изображение 2. test_rack_image_tab - Работа с вкладкой 'Изображение'
3. Правила доступа 3. test_rack_access_rules - Заполнение полей правил доступа
""" """
# Имя тестовой стойки
RACK_NAME = "Test-Rack-Edit"
# Инициализируем атрибуты # Инициализируем атрибуты
main_page: MainPage = None main_page: MainPage = None
location_page: LocationPage = None location_page: LocationPage = None
alert: AlertComponent = None
create_child_frame: CreateElementFrame = None def _check_rack_existance(self, browser: Page, rack_name: str) -> bool:
"""Проверяет существование стойки.
Args:
browser: Страница Playwright
rack_name: Имя стойки для проверки
Returns:
bool: True если стойка существует, False в противном случае
"""
# Обновляем навигационную панель
self.main_page.wait_for_timeout(500)
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
return True
return False
def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку.
Args:
browser: Страница Playwright
rack_name: Имя стойки для создания
"""
logger.debug(f"Creating rack: {rack_name}")
# Нажимаем кнопку "Создать" на тулбаре
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()
logger.info(f"Rack '{rack_name}' created successfully")
def _delete_rack_from_context_menu(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
# 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. Проверяем и нажимаем кнопку "Изменить"
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()
# 3. Создаем экземпляр ModalRackEditRack
rack_edit = ModalEditRack(browser, rack_name)
# Используем метод для удаления
rack_edit.click_remove_button()
logger.info(f"Rack '{rack_name}' deleted successfully")
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None: def setup(self, browser: Page) -> None:
@ -47,13 +146,11 @@ class TestRackEdit:
Выполняет: Выполняет:
1. Авторизацию в системе 1. Авторизацию в системе
2. Переход к локации test-zone 2. Создание стойки если она не существует
3. Инициализацию компонентов 3. Переход к стойке
4. Создание стойки если она не существует
5. Переход к стойке
Args: Args:
browser: Экземпляр страницы Playwright для взаимодействия с UI browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
""" """
# Авторизация в системе # Авторизация в системе
@ -72,35 +169,22 @@ class TestRackEdit:
# Создаем экземпляр страницы локации # Создаем экземпляр страницы локации
self.location_page = LocationPage(browser) self.location_page = LocationPage(browser)
# Инициализируем компонент алертов (вынесено в атрибуты класса)
self.alert = AlertComponent(browser)
# Инициализируем фрейм создания дочернего элемента (вынесено в атрибуты класса)
self.create_child_frame = CreateElementFrame(browser)
# Проверяем существование стойки # Проверяем существование стойки
if not self._check_rack_existance(browser, self.RACK_NAME): if not self._check_rack_existance(browser, RACK_NAME):
logger.info(f"Rack '{self.RACK_NAME}' does not exist. Creating...") self._create_rack(browser, RACK_NAME)
rack_data = CreateRackData(
name=self.RACK_NAME,
usize="42",
depth="1000"
)
self._create_rack(browser, rack_data)
self.main_page.wait_for_timeout(3000) self.main_page.wait_for_timeout(3000)
else: else:
logger.info(f"Rack '{self.RACK_NAME}' already exists") logger.info(f"Rack '{RACK_NAME}' already exists")
# Переходим к стойке для тестирования # Переходим к стойке для тестирования
self.main_page.click_subpanel_item(self.RACK_NAME, parent="test-zone") self.main_page.click_subpanel_item(RACK_NAME, parent="test-zone")
self.main_page.wait_for_timeout(3000) self.main_page.wait_for_timeout(3000)
@pytest.fixture(scope="class", autouse=True) @pytest.fixture(scope="class", autouse=True)
def cleanup_rack(self, browser: Page): def cleanup_rack(self, browser: Page):
"""Фикстура для очистки созданной стойки после ВСЕХ тестов класса. """Фикстура для очистки созданной стойки после ВСЕХ тестов класса.
Выполняется один раз после завершения всех тестов класса TestRackEdit. Выполняется один раз после завершения всех тестов класса TestRackTab.
Удаляет созданную стойку. Удаляет созданную стойку.
Args: Args:
@ -110,8 +194,6 @@ class TestRackEdit:
# Тесты выполняются здесь # Тесты выполняются здесь
yield yield
logger.debug(f"Cleaning up rack: {self.RACK_NAME}")
# Переходим на главную страницу и в нужную зону # Переходим на главную страницу и в нужную зону
login_page = LoginPage(browser) login_page = LoginPage(browser)
login_page.do_login() login_page.do_login()
@ -125,160 +207,37 @@ class TestRackEdit:
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(1000) self.main_page.wait_for_timeout(1000)
# Инициализируем компонент алертов для cleanup
self.alert = AlertComponent(browser)
# Проверяем существование стойки # Проверяем существование стойки
if self._check_rack_existance(browser, self.RACK_NAME): if self._check_rack_existance(browser, RACK_NAME):
# Переходим на страницу стойки # Переходим на страницу стойки
self.main_page.click_subpanel_item(self.RACK_NAME, parent="test-zone") self.main_page.click_subpanel_item(RACK_NAME, parent="test-zone")
self.main_page.wait_for_timeout(2000) self.main_page.wait_for_timeout(2000)
# Удаляем стойку # Удаляем стойку
self._delete_rack(browser, self.RACK_NAME) self._delete_rack_from_context_menu(browser, RACK_NAME)
# Дополнительная проверка # Дополнительная проверка
self.main_page.click_subpanel_item("test-zone") self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(1000) self.main_page.wait_for_timeout(1000)
def _check_rack_existance(self, browser: Page, rack_name: str) -> bool: #@pytest.mark.develop
"""Проверяет существование стойки.
Args:
browser: Страница Playwright
rack_name: Имя стойки для проверки
Returns:
bool: True если стойка существует, False в противном случае
"""
logger.debug(f"Checking existence of rack with name '{rack_name}'")
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
logger.debug(f"Rack with name '{rack_name}' found")
return True
logger.debug(f"Rack with name '{rack_name}' not found")
return False
def _create_rack(self, browser: Page, rack_data: CreateRackData) -> None:
"""Создает стойку.
Args:
browser: Страница Playwright
rack_data: Данные стойки для создания
"""
logger.debug(f"Creating rack with name '{rack_data.name}'")
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Нажимаем на плашку "Класс объекта учета"
self.create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
self.create_child_frame.select_object_class("Стойка")
# Создаем форму создания стойки
rack_form = CreateRackForm(browser)
# Заполняем данные стойки
fill_results = rack_form.fill_rack_data(rack_data)
logger.debug(f"Fill results: {fill_results}")
# Нажимаем кнопку создания
self.create_child_frame.click_add_button()
# Ждем появления alert с текстом
expected_alert_text = f"Успешно создано"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.info(f"Rack '{rack_data.name}' created successfully")
def _delete_rack(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
# Находим элемент стойки в навигационной панели
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)
# Проверяем и нажимаем кнопку "Изменить"
rack_page = RackPage(browser)
# Проверяем видимость и тултип кнопки
rack_page.should_be_toolbar_buttons()
# Кликаем на кнопку "Изменить"
rack_page.click_edit_button()
self.main_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, rack_name)
# Используем метод для удаления
rack_edit.click_remove_button()
# Проверяем уведомление об успешном удалении
expected_alert_text = "Успешно удалено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.info(f"Rack '{rack_name}' deleted successfully")
def test_rack_general_info_tab_fields(self, browser: Page) -> None: def test_rack_general_info_tab_fields(self, browser: Page) -> None:
"""Тест заполнения полей вкладки 'Общая информация' стойки.""" """Тест заполнения полей вкладки 'Общая информация' стойки."""
logger.debug(f"Starting general info tab test for rack: {self.RACK_NAME}")
rack_page = RackPage(browser) rack_page = RackPage(browser)
# Переходим в режим редактирования # Переходим в режим редактирования
rack_page.click_edit_button() rack_page.click_edit_button()
rack_page.wait_for_timeout(1000) rack_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker # Создаем экземпляр ModalEditRack
rack_edit = EditRackMaker(browser, self.RACK_NAME) rack_edit = ModalEditRack(browser, RACK_NAME)
# Создаем тестовые данные для заполнения всех полей # Создаем тестовые данные для заполнения всех полей
rack_edit_data = EditRackData( rack_edit_data = RackEditData(
# Основные поля # Основные поля
name=self.RACK_NAME, name="Test-Rack-Functionality",
serial="SN123456789", serial="SN123456789",
inventory="INV987654321", inventory="INV987654321",
comment="Тестовый комментарий для стойки (обновленный)", comment="Тестовый комментарий для стойки (обновленный)",
@ -302,19 +261,7 @@ class TestRackEdit:
# Сохраняем изменения # Сохраняем изменения
rack_edit.click_done_button() rack_edit.click_done_button()
rack_edit.wait_for_timeout(3000)
# Проверяем уведомление об успешном обновлении
expected_alert_text = "Успешно обновлено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
logger.debug("Alert already closed by the time forcible close was attempted")
# Вход в режим редактирования # Вход в режим редактирования
rack_page.click_edit_button() rack_page.click_edit_button()
@ -334,78 +281,28 @@ class TestRackEdit:
rack_edit.click_close_button() rack_edit.click_close_button()
logger.debug("General info tab test completed successfully") #@pytest.mark.develop
def test_required_field_name_validation(self, browser: Page) -> None:
"""Тест проверки обязательного поля ИМЯ при создании стойки."""
logger.debug(f"Starting required field name validation test for rack: {self.RACK_NAME}")
rack_page = RackPage(browser)
# Переходим в режим редактирования
rack_page.click_edit_button()
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, self.RACK_NAME)
# ========== Тест 1: Обязательное поле имя пустое ==========
# Очищаем поле имя
rack_edit.clear_field("Имя")
# Создаем тестовые данные для заполнения поля
test_data_1 = EditRackData(
name=""
)
rack_edit.fill_rack_data(test_data_1)
rack_edit.click_done_button()
# Проверяем уведомление об ошибочном обновлении
expected_alert_text = "поле ИМЯ должно быть заполнено, и не должно превышать 35 знаков"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# ========== Тест 2: Обязательное поле имя не должно превышать 35 знаков ==========
# Создаем тестовые данные для заполнения поля
test_data_2 = EditRackData(
name="_123456789_123456789_123456789_12345"
)
rack_edit.fill_rack_data(test_data_2)
rack_edit.click_done_button()
# Проверяем уведомление об ошибочном обновлении
expected_alert_text = "поле ИМЯ должно быть заполнено, и не должно превышать 35 знаков"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
rack_edit.click_close_button()
logger.debug("Required field name validation test completed successfully")
def test_rack_image_tab(self, browser: Page) -> None: def test_rack_image_tab(self, browser: Page) -> None:
"""Тест вкладки 'Изображение' стойки.""" """Тест вкладки 'Изображение' стойки."""
logger.debug(f"Starting image tab test for rack: {self.RACK_NAME}")
rack_page = RackPage(browser) rack_page = RackPage(browser)
# Переходим в режим редактирования # Переходим в режим редактирования
rack_page.click_edit_button() rack_page.click_edit_button()
rack_page.wait_for_timeout(1000) rack_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker # Создаем экземпляр ModalEditRack
rack_edit = EditRackMaker(browser, self.RACK_NAME) rack_edit = ModalEditRack(browser, RACK_NAME)
# Переключаемся на вкладку "Изображение" # Переключаемся на вкладку "Изображение"
rack_edit.switch_to_tab(EditRackMaker.TAB_IMAGE) rack_edit.switch_to_tab(ModalEditRack.TAB_IMAGE)
# Проверяем вкладку # Проверяем вкладку
assert rack_edit.is_tab_active(EditRackMaker.TAB_IMAGE), "Image tab should be active" assert rack_edit.is_tab_active(ModalEditRack.TAB_IMAGE), \
"Image tab should be active"
# Загружаем изображение если есть # Загружаем изображение если есть
test_image_path = os.path.join(os.path.dirname(__file__), "test_edit_rack_image.jpg") test_image_path = os.path.join(os.path.dirname(__file__), "test_image.jpg")
if os.path.exists(test_image_path): if os.path.exists(test_image_path):
logger.debug(f"Found test image: {test_image_path}") logger.debug(f"Found test image: {test_image_path}")
# Находим input и загружаем файл # Находим input и загружаем файл
@ -423,43 +320,27 @@ class TestRackEdit:
# Сохраняем # Сохраняем
rack_edit.click_done_button() rack_edit.click_done_button()
# Проверяем уведомление об успешном обновлении @pytest.mark.develop
expected_alert_text = "Успешно обновлено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
logger.debug("Alert already closed by the time forcible close was attempted")
logger.debug("Image tab test completed successfully")
def test_rack_access_rules(self, browser: Page) -> None: def test_rack_access_rules(self, browser: Page) -> None:
"""Тест заполнения полей правил доступа. """Тест заполнения полей правил доступа.
В каждое поле добавляются ВСЕ пользователи из списка custom_users. В каждое поле добавляются ВСЕ пользователи из списка custom_users.
""" """
logger.debug(f"Starting access rules test for rack: {self.RACK_NAME}")
rack_page = RackPage(browser) rack_page = RackPage(browser)
# Переходим в режим редактирования # Переходим в режим редактирования
rack_page.click_edit_button() rack_page.click_edit_button()
rack_page.wait_for_timeout(1000) rack_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker # Создаем экземпляр ModalEditRack
rack_edit = EditRackMaker(browser, self.RACK_NAME) rack_edit = ModalEditRack(browser, RACK_NAME)
# Переключаемся на вкладку "Настройки" # Переключаемся на вкладку "Настройки"
rack_edit.switch_to_tab(EditRackMaker.TAB_SETTINGS) rack_edit.switch_to_tab(ModalEditRack.TAB_SETTINGS)
# Проверяем, что вкладка активна # Проверяем, что вкладка активна
assert rack_edit.is_tab_active(EditRackMaker.TAB_SETTINGS), \ assert rack_edit.is_tab_active(ModalEditRack.TAB_SETTINGS), \
"Settings tab should be active after switching" "Settings tab should be active after switching"
# Целевые поля для заполнения # Целевые поля для заполнения
@ -473,11 +354,11 @@ class TestRackEdit:
# Пользователи для добавления в каждое поле # Пользователи для добавления в каждое поле
custom_users = [ custom_users = [
"admin", "TestUserRulesAdmin",
"manager", "TestUserRulesOper",
"operator", "TestUserRulesManager",
"sec", "TestUserRulesSec",
"collector" "TestUserRulesCollector"
] ]
# Заполняем поля # Заполняем поля
@ -512,26 +393,14 @@ class TestRackEdit:
# Сохраняем изменения # Сохраняем изменения
rack_edit.click_done_button() rack_edit.click_done_button()
rack_edit.wait_for_timeout(3000)
# Проверяем уведомление об успешном обновлении
expected_alert_text = "Успешно обновлено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
logger.debug("Alert already closed by the time forcible close was attempted")
# Возвращаемся в режим редактирования и проверяем снова # Возвращаемся в режим редактирования и проверяем снова
rack_page.click_edit_button() rack_page.click_edit_button()
rack_page.wait_for_timeout(1000) rack_page.wait_for_timeout(1000)
rack_edit = EditRackMaker(browser, self.RACK_NAME) rack_edit = ModalEditRack(browser, RACK_NAME)
rack_edit.switch_to_tab(EditRackMaker.TAB_SETTINGS) rack_edit.switch_to_tab(ModalEditRack.TAB_SETTINGS)
verification_results_after_save = rack_edit.verify_access_rules( verification_results_after_save = rack_edit.verify_access_rules(
expected_users=custom_users, expected_users=custom_users,
@ -543,5 +412,3 @@ class TestRackEdit:
f"After save - correctly filled {verification_results_after_save['correctly_filled']} out of {expected_total}" f"After save - correctly filled {verification_results_after_save['correctly_filled']} out of {expected_total}"
rack_edit.click_close_button() rack_edit.click_close_button()
logger.debug("Access rules test completed successfully")

View File

@ -0,0 +1,519 @@
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
import pytest
import os
from playwright.sync_api import Page, expect
from locators.navigation_panel_locators import NavigationPanelLocators
from pages.location_page import LocationPage
from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData
from components_derived.frames.create_child_element_frame import CreateChildElementFrame
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.rack_page import RackPage
from tools.logger import get_logger
from components_derived.modal_edit_rack import ModalEditRack, RackEditData
# Константы
RACK_NAME = "Test-Rack-Functionality"
# Инициализация логгера для всего модуля
logger = get_logger("RACK_TESTS")
#logger.setLevel("INFO")
class TestRackTab:
"""Набор тестов для вкладки 'Стойка' в модуле Объекты.
Проверяет корректность отображения, функциональность элементов интерфейса
и переключение между вкладками стойки оборудования.
Тесты покрывают следующие функциональные области:
1. test_rack_tab_content - Базовая структура и содержимое вкладки стойки
2. test_rack_tab_switching - Функциональность переключения между вкладками стойки
3. test_rack_general_info_tab_fields - Заполнение полей вкладки 'Общая информация'
4. test_rack_image_tab - Работа с вкладкой 'Изображение'
5. test_rack_access_rules - Заполнение полей правил доступа
6. test_rack_all_tabs_navigation - Навигация по всем вкладкам модального окна редактирования
"""
# Инициализируем атрибуты
main_page: MainPage = None
location_page: LocationPage = None
def _check_rack_existance(self, browser: Page, rack_name: str) -> bool:
"""Проверяет существование стойки.
Args:
browser: Страница Playwright
rack_name: Имя стойки для проверки
Returns:
bool: True если стойка существует, False в противном случае
"""
# Обновляем навигационную панель
self.main_page.wait_for_timeout(500)
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
return True
return False
def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку.
Args:
browser: Страница Playwright
rack_name: Имя стойки для создания
"""
logger.debug(f"Creating rack: {rack_name}")
# Нажимаем кнопку "Создать" на тулбаре
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()
logger.info(f"Rack '{rack_name}' created successfully")
def _delete_rack_from_context_menu(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
# 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. Проверяем и нажимаем кнопку "Изменить"
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()
# 3. Создаем экземпляр ModalRackEditRack
rack_edit = ModalEditRack(browser, rack_name)
# Используем метод для удаления
rack_edit.click_remove_button()
logger.info(f"Rack '{rack_name}' deleted successfully")
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Создание стойки если она не существует
3. Переход к стойке
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Мы на главной странице
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone")
# Создаем экземпляр страницы локации
self.location_page = LocationPage(browser)
# Проверяем существование стойки
if not self._check_rack_existance(browser, RACK_NAME):
self._create_rack(browser, RACK_NAME)
self.main_page.wait_for_timeout(3000)
else:
logger.info(f"Rack '{RACK_NAME}' already exists")
# Переходим к стойке для тестирования
self.main_page.click_subpanel_item(RACK_NAME, parent="test-zone")
self.main_page.wait_for_timeout(3000)
logger.info("Test setup completed")
@pytest.fixture(scope="class", autouse=True)
def cleanup_rack(self, browser: Page):
"""Фикстура для очистки созданной стойки после ВСЕХ тестов класса.
Выполняется один раз после завершения всех тестов класса TestRackTab.
Удаляет созданную стойку.
Args:
browser: Экземпляр страницы Playwright
"""
# Тесты выполняются здесь
yield
# Переходим на главную страницу и в нужную зону
login_page = LoginPage(browser)
login_page.do_login()
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone")
self.main_page.wait_for_timeout(1000)
# Проверяем существование стойки
if self._check_rack_existance(browser, RACK_NAME):
# Переходим на страницу стойки
self.main_page.click_subpanel_item(RACK_NAME, parent="test-zone")
self.main_page.wait_for_timeout(2000)
# Удаляем стойку
self._delete_rack_from_context_menu(browser, RACK_NAME)
# Дополнительная проверка
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(1000)
#@pytest.mark.develop
def test_rack_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки 'Стойка'.
Проверяет:
1. Наличие и корректность заголовка панели с навигационной цепочкой
2. Отображение и структуру обеих сторон стойки (лицевой и обратной)
3. Наличие и функциональность кнопок панели инструментов
4. Корректность отображения юнитов и устройств на стойке
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
expected_toolbar_subtitles = [
"test-zone",
'chevron_right',
RACK_NAME
]
rt = RackPage(browser)
rt.should_be_panel_header(expected_toolbar_subtitles)
# Комплексная проверка отображения обеих сторон стойки с детальной информацией
rt.should_be_rack_sides_displayed()
# Проверка кнопки "Скрыть стойку"
rt.should_have_hide_rack_button()
# Проверка кнопки "Показать стойку"
rt.should_have_show_rack_button()
rt.wait_for_timeout(1000)
logger.info("test_rack_tab_content completed successfully")
#@pytest.mark.develop
def test_rack_tab_switching(self, browser: Page) -> None:
"""Тест переключения между вкладками стойки оборудования.
Тестирует переключение на все доступные вкладки: Общая информация,
Обслуживание, События, Сервисы.
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
rt = RackPage(browser)
# Проверяем переключение между всеми вкладками стойки
rt.check_tab_switching()
# Переход в режим редактирования
rt.should_be_toolbar_buttons()
rt.wait_for_timeout(1000)
#@pytest.mark.develop
def test_rack_general_info_tab_fields(self, browser: Page) -> None:
"""Тест заполнения полей вкладки 'Общая информация' стойки."""
rt = RackPage(browser)
# Переходим в режим редактирования
logger.debug("Switching to edit mode...")
rt.click_edit_button()
rt.wait_for_timeout(1000)
# Создаем экземпляр ModalEditRack
rack_edit = ModalEditRack(browser, RACK_NAME)
# Создаем тестовые данные для заполнения всех полей
rack_edit_data = RackEditData(
# Основные поля
name="Test-Rack-Functionality",
serial="SN123456789",
inventory="INV987654321",
comment="Тестовый комментарий для стойки (обновленный)",
allocated_power="5500",
# Combobox поля
cable_entry="сверху",
state="Введен в эксплуатацию",
owner="",
service_org="",
project="",
# Checkbox поля
ventilation_panel=True,
)
# Заполняем все поля формы
logger.info("Filling rack form using fill_rack_data method...")
results = rack_edit.fill_rack_data(rack_edit_data)
logger.info(f"Fill results: {results}")
# Сохраняем изменения
logger.info("Saving changes...")
rack_edit.click_done_button()
rack_edit.wait_for_timeout(3000)
# Вход в режим редактирования
rt.click_edit_button()
# Проверяем поля, пропуская недоступные
verification_results = rack_edit.verify_all_filled_fields(
rack_edit_data,
skip_fields=["Владелец", "Обслуживающая организация", "Проект/Титул"]
)
logger.info(f"Verification results: {verification_results}")
# Проверяем результаты
assert verification_results["incorrectly_filled"] == 0, \
f"Available fields incorrectly filled: {verification_results['field_errors']}"
assert verification_results["not_filled"] == 0, \
f"Available fields not filled: {verification_results['field_errors']}"
rack_edit.click_close_button()
#@pytest.mark.develop
def test_rack_image_tab(self, browser: Page) -> None:
"""Тест вкладки 'Изображение' стойки."""
rt = RackPage(browser)
# Переходим в режим редактирования
rt.click_edit_button()
rt.wait_for_timeout(1000)
# Создаем экземпляр ModalEditRack
rack_edit = ModalEditRack(browser, RACK_NAME)
# Переключаемся на вкладку "Изображение"
rack_edit.switch_to_tab(ModalEditRack.TAB_IMAGE)
# Проверяем вкладку
assert rack_edit.is_tab_active(ModalEditRack.TAB_IMAGE), "Image tab should be active"
# Загружаем изображение если есть
test_image_path = os.path.join(os.path.dirname(__file__), "test_image.jpg")
if os.path.exists(test_image_path):
logger.info(f"Found test image: {test_image_path}")
# Находим input и загружаем файл
file_input = browser.locator("input[type='file']")
if file_input.count() == 0:
file_input = browser.locator(".button-file-upload__input")
if file_input.count() > 0:
file_input.set_input_files(test_image_path)
rt.wait_for_timeout(2000)
logger.info("Test image uploaded")
else:
logger.warning(f"Test image not found at: {test_image_path}")
# Сохраняем
rack_edit.click_done_button()
#@pytest.mark.develop
def test_rack_access_rules(self, browser: Page) -> None:
"""Тест заполнения полей правил доступа.
В каждое поле добавляются ВСЕ пользователи из списка custom_users.
"""
rt = RackPage(browser)
# Переходим в режим редактирования
rt.click_edit_button()
rt.wait_for_timeout(1000)
# Создаем экземпляр ModalEditRack
rack_edit = ModalEditRack(browser, RACK_NAME)
# Переключаемся на вкладку "Настройки"
rack_edit.switch_to_tab(ModalEditRack.TAB_SETTINGS)
# Проверяем, что вкладка активна
assert rack_edit.is_tab_active(ModalEditRack.TAB_SETTINGS), \
"Settings tab should be active after switching"
# Целевые поля для заполнения
target_fields = [
"read_access_rules",
"write_access_rules",
"sms_access_rules",
"email_access_rules",
"push_access_rules"
]
# Пользователи для добавления в каждое поле
custom_users = [
"TestUserRulesAdmin",
"TestUserRulesOper",
"TestUserRulesManager",
"TestUserRulesSec",
"TestUserRulesCollector"
]
# Заполняем поля - в КАЖДОЕ поле добавляются ВСЕ пользователи из custom_users
fill_results = rack_edit.fill_access_rules(
users_to_add=custom_users,
target_fields=target_fields
)
# Проверяем, что все пользователи были добавлены
expected_total = len(target_fields) * len(custom_users)
assert fill_results["access_rules_filled"] == expected_total, \
f"Added {fill_results['access_rules_filled']} users, expected {expected_total}"
assert len(fill_results["errors"]) == 0, \
f"Errors during filling: {fill_results['errors']}"
# Проверяем заполнение - в КАЖДОМ поле должны быть ВСЕ пользователи
verification_results = rack_edit.verify_access_rules(
expected_users=custom_users,
target_fields=target_fields
)
logger.info(f"Verification results: {verification_results}")
# Проверяем результаты
assert verification_results["correctly_filled"] == expected_total, \
f"Correctly filled {verification_results['correctly_filled']} out of {expected_total}"
assert verification_results["incorrectly_filled"] == 0, \
f"Verification errors: {verification_results['field_errors']}"
# Дополнительная проверка - убеждаемся, что все поля были проверены
assert len(verification_results["fields_verified"]) == len(target_fields), \
f"Fields verified: {len(verification_results['fields_verified'])}, expected: {len(target_fields)}"
# Сохраняем изменения
rack_edit.click_done_button()
rack_edit.wait_for_timeout(3000)
# Возвращаемся в режим редактирования и проверяем снова
rt.click_edit_button()
rt.wait_for_timeout(1000)
rack_edit = ModalEditRack(browser, RACK_NAME)
rack_edit.switch_to_tab(ModalEditRack.TAB_SETTINGS)
verification_results_after_save = rack_edit.verify_access_rules(
expected_users=custom_users,
target_fields=target_fields
)
logger.debug(f"Verification results after save: {verification_results_after_save}")
assert verification_results_after_save["correctly_filled"] == expected_total, \
f"After save - correctly filled {verification_results_after_save['correctly_filled']} out of {expected_total}"
rack_edit.click_close_button()
#@pytest.mark.develop
def test_rack_all_tabs_navigation(self, browser: Page) -> None:
"""Тест навигации по всем вкладкам модального окна редактирования стойки."""
rt = RackPage(browser)
# Переходим в режим редактирования
rt.click_edit_button()
rt.wait_for_timeout(1000)
# Создаем экземпляр ModalEditRack
rack_edit = ModalEditRack(browser, RACK_NAME)
# Проверяем начальное состояние
initial_tab = rack_edit.get_active_tab()
assert initial_tab == ModalEditRack.TAB_GENERAL, \
f"Initial tab should be '{ModalEditRack.TAB_GENERAL}', got '{initial_tab}'"
logger.debug(f"Initial tab: {initial_tab}")
# Переключаемся на вкладку "Изображение"
rack_edit.switch_to_tab(ModalEditRack.TAB_IMAGE)
assert rack_edit.is_tab_active(ModalEditRack.TAB_IMAGE), \
"Should be on Image tab after switching"
logger.debug(f"Switched to: {ModalEditRack.TAB_IMAGE}")
# Переключаемся на вкладку "Настройки"
rack_edit.switch_to_tab(ModalEditRack.TAB_SETTINGS)
assert rack_edit.is_tab_active(ModalEditRack.TAB_SETTINGS), \
"Should be on Settings tab after switching"
logger.debug(f"Switched to: {ModalEditRack.TAB_SETTINGS}")
# Возвращаемся на вкладку "Общая информация"
rack_edit.switch_to_tab(ModalEditRack.TAB_GENERAL)
assert rack_edit.is_tab_active(ModalEditRack.TAB_GENERAL), \
"Should be back on General tab after switching"
logger.debug(f"Switched back to: {ModalEditRack.TAB_GENERAL}")
# Закрываем окно редактирования
rack_edit.click_close_button()

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -1,301 +0,0 @@
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
import pytest
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from frames.create_element_frame import CreateElementFrame
from forms.create_rack_form import CreateRackForm, CreateRackData
from makers.edit_rack_maker import EditRackMaker
from pages.location_page import LocationPage
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.rack_page import RackPage
from components.alert_component import AlertComponent
logger = get_logger("RACK_MANAGEMENT_TESTS")
logger.setLevel("INFO")
class TestRackManagement:
"""Набор тестов для вкладки 'Стойка' в модуле Объекты.
Проверяет корректность отображения, функциональность элементов интерфейса
и переключение между вкладками стойки оборудования.
"""
# Имя тестовой стойки
RACK_NAME = "Test-Rack-Functionality"
# Инициализируем атрибуты
main_page: MainPage = None
location_page: LocationPage = None
alert: AlertComponent = None
create_child_frame: CreateElementFrame = None
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход к локации test-zone
3. Инициализацию компонентов
4. Создание стойки если она не существует
5. Переход к стойке
Args:
browser: Экземпляр страницы Playwright для взаимодействия с UI
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Мы на главной странице
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone")
# Создаем экземпляр страницы локации
self.location_page = LocationPage(browser)
# Инициализируем компонент алертов (вынесено в атрибуты класса)
self.alert = AlertComponent(browser)
# Инициализируем фрейм создания дочернего элемента (вынесено в атрибуты класса)
self.create_child_frame = CreateElementFrame(browser)
# Проверяем существование стойки
if not self._check_rack_existance(browser, self.RACK_NAME):
logger.info(f"Rack '{self.RACK_NAME}' does not exist. Creating...")
rack_data = CreateRackData(
name=self.RACK_NAME,
usize="42",
depth="1000"
)
self._create_rack(browser, rack_data)
self.main_page.wait_for_timeout(3000)
else:
logger.info(f"Rack '{self.RACK_NAME}' already exists")
# Переходим к стойке для тестирования
self.main_page.click_subpanel_item(self.RACK_NAME, parent="test-zone")
self.main_page.wait_for_timeout(3000)
@pytest.fixture(scope="class", autouse=True)
def cleanup_rack(self, browser: Page):
"""Фикстура для очистки созданной стойки после ВСЕХ тестов класса.
Выполняется один раз после завершения всех тестов класса TestRackTab.
Удаляет созданную стойку.
Args:
browser: Экземпляр страницы Playwright
"""
# Тесты выполняются здесь
yield
logger.debug(f"Cleaning up rack: {self.RACK_NAME}")
# Переходим на главную страницу и в нужную зону
login_page = LoginPage(browser)
login_page.do_login()
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone")
self.main_page.wait_for_timeout(1000)
# Инициализируем компонент алертов для cleanup
self.alert = AlertComponent(browser)
# Проверяем существование стойки
if self._check_rack_existance(browser, self.RACK_NAME):
# Переходим на страницу стойки
self.main_page.click_subpanel_item(self.RACK_NAME, parent="test-zone")
self.main_page.wait_for_timeout(2000)
# Удаляем стойку
self._delete_rack(browser, self.RACK_NAME)
# Дополнительная проверка
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(1000)
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}'")
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
logger.debug(f"Rack with name '{rack_name}' found")
return True
logger.debug(f"Rack with name '{rack_name}' not found")
return False
def _create_rack(self, browser: Page, rack_data: CreateRackData) -> None:
"""Создает стойку.
Args:
browser: Страница Playwright
rack_data: Данные стойки для создания
"""
logger.debug(f"Creating rack with name '{rack_data.name}'")
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Нажимаем на плашку "Класс объекта учета"
self.create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
self.create_child_frame.select_object_class("Стойка")
# Создаем форму создания стойки
rack_form = CreateRackForm(browser)
# Заполняем данные стойки
fill_results = rack_form.fill_rack_data(rack_data)
logger.debug(f"Fill results: {fill_results}")
# Нажимаем кнопку создания
self.create_child_frame.click_add_button()
# Ждем появления alert с текстом
expected_alert_text = f"Успешно создано"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.info(f"Rack '{rack_data.name}' created successfully")
def _delete_rack(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
# Находим элемент стойки в навигационной панели
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)
# Проверяем и нажимаем кнопку "Изменить"
rack_page = RackPage(browser)
# Проверяем видимость и тултип кнопки
rack_page.should_be_toolbar_buttons()
# Кликаем на кнопку "Изменить"
rack_page.click_edit_button()
self.main_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, rack_name)
# Используем метод для удаления
rack_edit.click_remove_button()
# Проверяем уведомление об успешном удалении
expected_alert_text = "Успешно удалено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000)
# Закрываем alert с текстом кнопкой 'Закрыть'
try:
self.alert.close_alert_by_text(expected_alert_text)
logger.debug("Alert forcibly closed")
except AssertionError:
# Если уже закрылся - игнорируем
logger.debug("Alert already closed by the time forcible close was attempted")
logger.info(f"Rack '{rack_name}' deleted successfully")
def test_rack_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки 'Стойка'.
Проверяет:
1. Наличие и корректность заголовка панели с навигационной цепочкой
2. Отображение и структуру обеих сторон стойки (лицевой и обратной)
3. Наличие и функциональность кнопок панели инструментов
4. Корректность отображения юнитов и устройств на стойке
Args:
browser: Экземпляр страницы Playwright для взаимодействия с UI
"""
logger.debug(f"Starting test for rack tab content with rack: {self.RACK_NAME}")
expected_toolbar_subtitles = [
"test-zone",
'chevron_right',
self.RACK_NAME
]
rack_page = RackPage(browser)
rack_page.should_be_panel_header(expected_toolbar_subtitles)
# Комплексная проверка отображения обеих сторон стойки с детальной информацией
rack_page.should_be_rack_sides_displayed()
# Проверка кнопки "Скрыть стойку"
rack_page.should_have_hide_rack_button()
# Проверка кнопки "Показать стойку"
rack_page.should_have_show_rack_button()
# Проверяем переключение между всеми вкладками стойки
rack_page.check_tab_switching()
# Проверям наличие кнопки редактирования
rack_page.should_be_toolbar_buttons()
rack_page.wait_for_timeout(1000)
logger.debug("Test for rack tab content completed successfully")

View File

@ -4,7 +4,7 @@
контейнера для отображения событий вкладки Действия. контейнера для отображения событий вкладки Действия.
""" """
import pytest # import pytest
from playwright.sync_api import Page from playwright.sync_api import Page
from pages.main_page import MainPage from pages.main_page import MainPage
from pages.login_page import LoginPage from pages.login_page import LoginPage
@ -53,36 +53,28 @@ class TestActionsEventsContainer:
# Получение количества строк в таблице Реальное время # Получение количества строк в таблице Реальное время
rows_count = actions_events_container.get_events_table_rows_count() rows_count = actions_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
actions_events_container.check_events_table_row_highlighting(0) actions_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
actions_events_container.check_events_table_row_highlighting(rows_count - 1) actions_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
actions_events_container.check_events_table_row_highlighting(int(rows_count / 2)) actions_events_container.check_events_table_row_highlighting(int(rows_count / 2))
actions_events_container.click_archive_button() actions_events_container.click_archive_button()
# Получение количества строк в таблице Архив # Получение количества строк в таблице Архив
rows_count = actions_events_container.get_events_table_rows_count() rows_count = actions_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
actions_events_container.check_events_table_row_highlighting(0) actions_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
actions_events_container.check_events_table_row_highlighting(rows_count - 1) actions_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
actions_events_container.check_events_table_row_highlighting(int(rows_count / 2)) actions_events_container.check_events_table_row_highlighting(int(rows_count / 2))
# @pytest.mark.develop # @pytest.mark.develop
def test_events_table_scrolling(self, browser: Page): def test_events_table_scrolling(self, browser: Page):
"""Проверяет возможность скроллинга таблицы событий на примере таблицы Архив. """Проверяет возможность скроллинга таблицы событий.
Args: Args:
browser: Экземпляр страницы Playwright. browser: Экземпляр страницы Playwright.
""" """
rows_to_start_scrolling = 20
lp = LoginPage(browser) lp = LoginPage(browser)
lp.do_login() lp.do_login()
@ -90,21 +82,18 @@ class TestActionsEventsContainer:
mp.should_be_event_panel() mp.should_be_event_panel()
actions_events_container = mp.click_events_panel_actions_tab() actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.click_archive_button()
rows_count = actions_events_container.get_events_table_rows_count()
if rows_count > rows_to_start_scrolling:
events_panel_position = mp.get_events_panel_position() events_panel_position = mp.get_events_panel_position()
# Проверка, что панель с таблицей открыта # Проверка, что панель с таблицей открыта
assert events_panel_position != "bottom", "Panel with actions events should be opened" assert events_panel_position != "bottom", "Panel with system log events should be opened"
is_scrollable = actions_events_container.check_events_table_verticall_scrolling() is_scrollable = actions_events_container.check_events_table_verticall_scrolling()
# Убеждаемся, что панель открыта наполовину и проверяем скроллинг # Убеждаемся, что панель открыта наполовину и проверяем скроллинг
assert events_panel_position == "center",\ assert events_panel_position == "center",\
"Panel with actions events should be located on the main page center" "Panel with system log events should be located on the main page center"
assert is_scrollable, "Actions events table should be scrollable" assert is_scrollable, "System log events table should be scrollable"
# Скроллинг вниз # Скроллинг вниз
actions_events_container.scroll_events_table_down() actions_events_container.scroll_events_table_down()
@ -128,10 +117,10 @@ class TestActionsEventsContainer:
events_panel_position = mp.get_events_panel_position() events_panel_position = mp.get_events_panel_position()
assert events_panel_position == "top",\ assert events_panel_position == "top",\
"Panel with actions events should be located on the main page top" "Panel with system log events should be located on the main page top"
is_scrollable = actions_events_container.check_events_table_verticall_scrolling() is_scrollable = actions_events_container.check_events_table_verticall_scrolling()
assert is_scrollable, "Actions events table should be scrollable in the full window" assert is_scrollable, "System log events table should be scrollable in the full window"
# Скроллинг вниз # Скроллинг вниз
actions_events_container.scroll_events_table_down() actions_events_container.scroll_events_table_down()
@ -146,8 +135,6 @@ class TestActionsEventsContainer:
# Проверка видимости первой строки после прокрутки # Проверка видимости первой строки после прокрутки
actions_events_container.check_events_table_first_row_visibility() actions_events_container.check_events_table_first_row_visibility()
else:
print("Not enough data to check vertical scrolling")
# @pytest.mark.develop # @pytest.mark.develop
def test_real_time_task_view(self, browser: Page): def test_real_time_task_view(self, browser: Page):
@ -182,15 +169,6 @@ class TestActionsEventsContainer:
task_view_window.check_stages_table_headers(stages_table_content[0], expected_task_headers) task_view_window.check_stages_table_headers(stages_table_content[0], expected_task_headers)
stages_table_content.pop(0) stages_table_content.pop(0)
rows_count = len(stages_table_content)
if rows_count != 0:
# Проверка выделения строк
task_view_window.check_stages_table_row_highlighting(0)
if rows_count > 1:
task_view_window.check_stages_table_row_highlighting(rows_count - 1)
if rows_count > 3:
task_view_window.check_stages_table_row_highlighting(int(rows_count / 2))
payload = {"filter": {"page": 1}, payload = {"filter": {"page": 1},
"count": 40} "count": 40}
@ -261,146 +239,150 @@ class TestActionsEventsContainer:
# convert2timestamp=True) # convert2timestamp=True)
# assert is_descending_order, "Column data should be in descending order" # assert is_descending_order, "Column data should be in descending order"
# @pytest.mark.develop # def test_events_table_pagination(self, browser: Page):
def test_real_time_events_table_pagination(self, browser: Page): # """Проверяет возможность пагинации таблицы событий.
"""Проверяет возможность пагинации таблицы событий на примере вкладки 'Реальное время'.
Args: # Args:
browser: Экземпляр страницы Playwright. # browser: Экземпляр страницы Playwright.
""" # """
lp = LoginPage(browser) # lp = LoginPage(browser)
lp.do_login() # lp.do_login()
mp = MainPage(browser) # mp = MainPage(browser)
mp.should_be_event_panel() # mp.should_be_event_panel()
actions_events_container = mp.click_events_panel_actions_tab() # actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.click_real_time_button() # audit_events_container.should_be_pagination_buttons()
actions_events_container.should_be_pagination_buttons() # # Проверка начального состояния
# tested_pages = 1
# Проверка начального состояния # # to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
tested_pages = 1 # payload = {"table": "default",
pages = 1 # "filter": {
# "tags": [
# "testaudit",
# "type:action"
# ],
# "page": 1
# },
# "count": 40
# }
# response = mp.send_post_api_request("e-nms/logs_enode", payload)
# if response.status == 200:
# response_body = mp.get_response_body(response)
payload = {"filter": {"page": 1}, # if response_body:
"count": 40} # pages = response_body["data"]["pages"]
response = mp.send_post_api_request("e-nms/task_manager/showTaskWithFilter", payload) # if pages > 5:
if response.status == 200: # tested_pages = 5
response_body = mp.get_response_body(response) # else:
# tested_pages = pages
if response_body: # current_number = audit_events_container.get_current_data_set_number()
pages = response_body["data"]["pages"] # assert current_number == 1, "The first page should be number one"
if pages > 5: # if tested_pages == 1:
tested_pages = 5 # audit_events_container.should_be_all_disabled()
else: # else:
tested_pages = pages # audit_events_container.should_be_initial_state()
current_number = actions_events_container.get_current_data_set_number() # # Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед
assert current_number == 1, "The first page should be number one" # audit_events_container.click_last_page()
# browser.wait_for_timeout(2000)
if tested_pages == 1: # audit_events_container.should_be_final_state()
actions_events_container.should_be_all_disabled()
else:
actions_events_container.should_be_initial_state()
# Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед # last_number = audit_events_container.get_current_data_set_number()
actions_events_container.click_last_page()
browser.wait_for_timeout(2000)
actions_events_container.should_be_final_state() # audit_events_container.click_chevron_left()
# browser.wait_for_timeout(4000)
last_number = actions_events_container.get_current_data_set_number() # if last_number == 2:
# audit_events_container.should_be_initial_state()
# else:
# audit_events_container.should_be_all_enabled()
actions_events_container.click_chevron_left() # counter = last_number - 1
browser.wait_for_timeout(4000) # current_number = audit_events_container.get_current_data_set_number()
# assert current_number == counter, f"Expected page number {counter} is not equal actual {current_number}"
if last_number == 2: # audit_events_container.click_chevron_right()
actions_events_container.should_be_initial_state() # browser.wait_for_timeout(4000)
else:
actions_events_container.should_be_all_enabled()
counter = last_number - 1 # audit_events_container.should_be_final_state()
current_number = actions_events_container.get_current_data_set_number()
assert current_number == counter, f"Expected page number {counter} is not equal actual {current_number}"
actions_events_container.click_chevron_right() # current_number = audit_events_container.get_current_data_set_number()
browser.wait_for_timeout(4000) # assert current_number == last_number, \
# f"Expected page number {last_number} is not equal actual {current_number}"
actions_events_container.should_be_final_state() # # Переход на первую страницу, переход на следующую страницу, возврат на первую страницу
# audit_events_container.click_first_page()
# browser.wait_for_timeout(2000)
current_number = actions_events_container.get_current_data_set_number() # audit_events_container.should_be_initial_state()
assert current_number == last_number, \
f"Expected page number {last_number} is not equal actual {current_number}"
# Переход на первую страницу, переход на следующую страницу, возврат на первую страницу # current_number = audit_events_container.get_current_data_set_number()
actions_events_container.click_first_page() # assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
browser.wait_for_timeout(2000)
actions_events_container.should_be_initial_state() # audit_events_container.click_chevron_right()
# browser.wait_for_timeout(4000)
current_number = actions_events_container.get_current_data_set_number() # if tested_pages == 2:
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}" # audit_events_container.should_be_final_state()
# else:
# audit_events_container.should_be_all_enabled()
actions_events_container.click_chevron_right() # current_number = audit_events_container.get_current_data_set_number()
browser.wait_for_timeout(4000) # assert current_number == 2, f"Expected page number 2 is not equal actual {current_number}"
if tested_pages == 2: # audit_events_container.click_chevron_left()
actions_events_container.should_be_final_state() # browser.wait_for_timeout(4000)
else:
actions_events_container.should_be_all_enabled()
current_number = actions_events_container.get_current_data_set_number() # audit_events_container.should_be_initial_state()
assert current_number == 2, f"Expected page number 2 is not equal actual {current_number}"
actions_events_container.click_chevron_left() # current_number = audit_events_container.get_current_data_set_number()
browser.wait_for_timeout(4000) # assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
actions_events_container.should_be_initial_state() # if tested_pages > 2:
# # загрузка страниц от начала и до конца
# # to_do: проверка, что происходит обновление содержимого таблицы
# counter = 1
current_number = actions_events_container.get_current_data_set_number() # while counter < tested_pages:
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}" # audit_events_container.click_chevron_right()
# counter += 1
# browser.wait_for_timeout(2000)
if tested_pages > 2: # if counter == tested_pages:
# загрузка страниц от начала и до конца # audit_events_container.should_be_final_state()
# to_do: проверка, что происходит обновление содержимого таблицы # else:
counter = 1 # audit_events_container.should_be_all_enabled()
# current_number = audit_events_container.get_current_data_set_number()
# assert current_number == counter,\
# f"Expected page number {counter} is not equal actual {current_number}"
while counter < tested_pages: # # загрузка страниц от конца к началу
actions_events_container.click_chevron_right() # # to_do: проверка, что происходит обновление содержимого таблицы
counter += 1 # while counter > 2:
browser.wait_for_timeout(2000) # audit_events_container.click_chevron_left()
# browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages: # audit_events_container.should_be_all_enabled()
actions_events_container.should_be_final_state()
else:
actions_events_container.should_be_all_enabled()
current_number = actions_events_container.get_current_data_set_number()
assert current_number == counter,\
f"Expected page number {counter} is not equal actual {current_number}"
# загрузка страниц от конца к началу # counter -= 1
# to_do: проверка, что происходит обновление содержимого таблицы # current_number = audit_events_container.get_current_data_set_number()
while counter > 2: # assert current_number == counter,\
actions_events_container.click_chevron_left() # f"Expected page number {counter} is not equal actual {current_number}"
browser.wait_for_timeout(2000)
actions_events_container.should_be_all_enabled() # # Проверка возврата к начальному состоянию
# audit_events_container.click_chevron_left()
# browser.wait_for_timeout(2000)
counter -= 1 # audit_events_container.should_be_initial_state()
current_number = actions_events_container.get_current_data_set_number()
assert current_number == counter,\
f"Expected page number {counter} is not equal actual {current_number}"
# Проверка возврата к начальному состоянию # current_number = audit_events_container.get_current_data_set_number()
actions_events_container.click_chevron_left() # assert current_number == 1, "The first page should be number one"
browser.wait_for_timeout(2000)
actions_events_container.should_be_initial_state()
current_number = actions_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"

View File

@ -52,12 +52,9 @@ class TestAuditEventsContainer:
# Получение количества строк в таблице # Получение количества строк в таблице
rows_count = audit_events_container.get_events_table_rows_count() rows_count = audit_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
audit_events_container.check_events_table_row_highlighting(0) audit_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
audit_events_container.check_events_table_row_highlighting(rows_count - 1) audit_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
audit_events_container.check_events_table_row_highlighting(int(rows_count / 2)) audit_events_container.check_events_table_row_highlighting(int(rows_count / 2))
def test_events_table_scrolling(self, browser: Page): def test_events_table_scrolling(self, browser: Page):
@ -183,7 +180,6 @@ class TestAuditEventsContainer:
# Проверка начального состояния # Проверка начального состояния
tested_pages = 1 tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0} # to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "default", payload = {"table": "default",
@ -283,7 +279,7 @@ class TestAuditEventsContainer:
counter += 1 counter += 1
browser.wait_for_timeout(2000) browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages: if counter == tested_pages:
audit_events_container.should_be_final_state() audit_events_container.should_be_final_state()
else: else:
audit_events_container.should_be_all_enabled() audit_events_container.should_be_all_enabled()

View File

@ -1,381 +0,0 @@
"""Модуль тестов контейнера для отображения событий информационной безопасности.
Содержит тесты для проверки функциональности
контейнера для отображения событий информационной безопасности.
"""
import pytest
from playwright.sync_api import Page
from pages.users_tab import UsersTab
from pages.main_page import MainPage
from pages.login_page import LoginPage
# @pytest.mark.smoke
class TestAuditEventsContainerSecurity:
"""Класс тестов для проверки контейнера для отображения событий информационной безопасности.
Атрибуты:
browser: Фикстура для работы с браузером.
"""
user_data = {"name": "TestSecurityUser", "role": "Специалист информационной безопасности",
"password": "qwerty1234567"}
@pytest.fixture(scope="class", autouse=True)
def test_fixture(self, browser: Page) -> None:
"""Функция для подготовки и удаления тестового окружения.
"""
# Авторизация в системе
lp = LoginPage(browser)
lp.do_login()
# Инициализация главной страницы
mp = MainPage(browser)
# Создание тестового пользователя
mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки")
mp.click_subpanel_item("Пользователи")
ut = UsersTab(browser)
ut.open_add_user_window()
ut.add_new_user(self.user_data)
mp.do_logout()
yield
# Авторизация как администратор
lp = LoginPage(browser)
lp.do_login()
# Удаляем тестового пользователя
mp = MainPage(browser)
mp.click_main_navigation_panel_item("Настройки")
mp.click_subpanel_item("Пользователи")
ut = UsersTab(browser)
# Проверяем существует ли пользователь и удаляем его
user_index = ut.find_user_in_table(self.user_data["name"], self.user_data["role"])
if user_index != -1:
ut.open_edit_user_page_by_user(self.user_data["name"], self.user_data["role"])
ut.delete_user(self.user_data["name"])
mp.do_logout()
# @pytest.mark.develop
def test_events_tab_content(self, browser: Page) -> None:
"""Проверяет содержимое контейнера для отображения событий информационной безопасности.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login(username=self.user_data["name"], password=self.user_data["password"])
browser.wait_for_timeout(1000)
mp = MainPage(browser)
mp.should_be_event_panel()
security_events_container = mp.click_events_panel_audit_tab()
browser.wait_for_timeout(2000)
security_events_container.check_content_security()
# Выход из системы текущего пользователя
mp.do_logout()
def test_events_table_row_highlighting(self, browser: Page):
"""Проверяет выделение строк в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login(username=self.user_data["name"], password=self.user_data["password"])
browser.wait_for_timeout(1000)
mp = MainPage(browser)
mp.should_be_event_panel()
security_events_container = mp.click_events_panel_audit_tab()
# Получение количества строк в таблице
rows_count = security_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк
security_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
security_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
security_events_container.check_events_table_row_highlighting(int(rows_count / 2))
# Выход из системы текущего пользователя
mp.do_logout()
def test_events_table_scrolling(self, browser: Page):
"""Проверяет возможность скроллинга таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login(username=self.user_data["name"], password=self.user_data["password"])
browser.wait_for_timeout(1000)
mp = MainPage(browser)
mp.should_be_event_panel()
security_events_container = mp.click_events_panel_audit_tab()
events_panel_position = mp.get_events_panel_position()
# Проверка, что панель с таблицей открыта
assert events_panel_position != "bottom", "Panel with security events should be opened"
is_scrollable = security_events_container.check_events_table_verticall_scrolling()
# Убеждаемся, что панель открыта наполовину и проверяем скроллинг
assert events_panel_position == "center",\
"Panel with security events should be located on the main page center"
assert is_scrollable, "Security events table should be scrollable"
# Скроллинг вниз
security_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
security_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
security_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
security_events_container.check_events_table_first_row_visibility()
# Раскрываем панель полностью и проверяем скроллинг
assert mp.check_expand_more_button(), \
"Expand more button should be present"
mp.click_events_panel_expand_more_button()
mp.wait_for_timeout(500)
events_panel_position = mp.get_events_panel_position()
assert events_panel_position == "top",\
"Panel with security events should be located on the main page top"
is_scrollable = security_events_container.check_events_table_verticall_scrolling()
assert is_scrollable, "Security events table should be scrollable in the full window"
# Скроллинг вниз
security_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
security_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
security_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
security_events_container.check_events_table_first_row_visibility()
# Выход из системы текущего пользователя
mp.do_logout()
def test_events_table_column_sorting(self, browser: Page):
"""Проверяет сортировку колонки 'Время' в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login(username=self.user_data["name"], password=self.user_data["password"])
browser.wait_for_timeout(1000)
mp = MainPage(browser)
mp.should_be_event_panel()
security_events_container = mp.click_events_panel_audit_tab()
index = 0
state = security_events_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
security_events_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = security_events_container.get_arrow_button_state(index)
assert state == "up", "Arrow button should be up"
is_descending_order = security_events_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert not is_descending_order, "Column data should be in ascending order"
security_events_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = security_events_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
is_descending_order = security_events_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert is_descending_order, "Column data should be in descending order"
# Выход из системы текущего пользователя
mp.do_logout()
def test_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login(username=self.user_data["name"], password=self.user_data["password"])
browser.wait_for_timeout(1000)
mp = MainPage(browser)
mp.should_be_event_panel()
security_events_container = mp.click_events_panel_audit_tab()
security_events_container.should_be_pagination_buttons()
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "default",
"filter": {
"tags": [
"testaudit",
"secure"
],
"page": 0
},
"count": 40
}
response = mp.send_post_api_request("e-nms/logs_enode", payload)
if response.status == 200:
response_body = mp.get_response_body(response)
if response_body:
pages = response_body["data"]["pages"]
if pages > 5:
tested_pages = 5
else:
tested_pages = pages
current_number = security_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"
if tested_pages == 1:
security_events_container.should_be_all_disabled()
else:
security_events_container.should_be_initial_state()
# Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед
security_events_container.click_last_page()
browser.wait_for_timeout(2000)
security_events_container.should_be_final_state()
last_number = security_events_container.get_current_data_set_number()
security_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
if last_number == 2:
security_events_container.should_be_initial_state()
else:
security_events_container.should_be_all_enabled()
counter = last_number - 1
current_number = security_events_container.get_current_data_set_number()
assert current_number == counter, f"Expected page number {counter} is not equal actual {current_number}"
security_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
security_events_container.should_be_final_state()
current_number = security_events_container.get_current_data_set_number()
assert current_number == last_number, \
f"Expected page number {last_number} is not equal actual {current_number}"
# Переход на первую страницу, переход на следующую страницу, возврат на первую страницу
security_events_container.click_first_page()
browser.wait_for_timeout(2000)
security_events_container.should_be_initial_state()
current_number = security_events_container.get_current_data_set_number()
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
security_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
if tested_pages == 2:
security_events_container.should_be_final_state()
else:
security_events_container.should_be_all_enabled()
current_number = security_events_container.get_current_data_set_number()
assert current_number == 2, f"Expected page number 2 is not equal actual {current_number}"
security_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
security_events_container.should_be_initial_state()
current_number = security_events_container.get_current_data_set_number()
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
if tested_pages > 2:
# загрузка страниц от начала и до конца
# to_do: проверка, что происходит обновление содержимого таблицы
counter = 1
while counter < tested_pages:
security_events_container.click_chevron_right()
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages:
security_events_container.should_be_final_state()
else:
security_events_container.should_be_all_enabled()
current_number = security_events_container.get_current_data_set_number()
assert current_number == counter,\
f"Expected page number {counter} is not equal actual {current_number}"
# загрузка страниц от конца к началу
# to_do: проверка, что происходит обновление содержимого таблицы
while counter > 2:
security_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
security_events_container.should_be_all_enabled()
counter -= 1
current_number = security_events_container.get_current_data_set_number()
assert current_number == counter,\
f"Expected page number {counter} is not equal actual {current_number}"
# Проверка возврата к начальному состоянию
security_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
security_events_container.should_be_initial_state()
current_number = security_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"
# Выход из системы текущего пользователя
mp.do_logout()

View File

@ -39,7 +39,7 @@ class TestEventPanel:
# Проверяем соответствие тултипов информации на кнопках # Проверяем соответствие тултипов информации на кнопках
tooltip_event_counters = mp.get_event_counters_by_tooltips() tooltip_event_counters = mp.get_event_counters_by_tooltips()
button_event_counters = mp.get_event_counters_by_buttons() button_event_counters = mp.get_event_counters_by_tooltips()
for event, counter in tooltip_event_counters.items(): for event, counter in tooltip_event_counters.items():
button_counter = button_event_counters.get(event) button_counter = button_event_counters.get(event)
@ -49,7 +49,7 @@ class TestEventPanel:
if button_counter != counter: if button_counter != counter:
assert False, f"Expected tooltip value {counter} is not equal button value {button_counter} for event button {event}" assert False, f"Expected tooltip value {counter} is not equal button value {button_counter} for event button {event}"
# @pytest.mark.develop @pytest.mark.develop
def test_event_panel_expand_buttons(self, browser: Page) -> None: def test_event_panel_expand_buttons(self, browser: Page) -> None:
"""Проверяет состояние и количество кнопок расширения рабочей области панели событий. """Проверяет состояние и количество кнопок расширения рабочей области панели событий.

View File

@ -53,12 +53,9 @@ class TestEventsTabContainer:
# Получение количества строк в таблице # Получение количества строк в таблице
rows_count = events_tab_container.get_events_table_rows_count() rows_count = events_tab_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
events_tab_container.check_events_table_row_highlighting(0) events_tab_container.check_events_table_row_highlighting(0)
if rows_count > 1:
events_tab_container.check_events_table_row_highlighting(rows_count - 1) events_tab_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
events_tab_container.check_events_table_row_highlighting(int(rows_count / 2)) events_tab_container.check_events_table_row_highlighting(int(rows_count / 2))
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий") @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
@ -187,7 +184,6 @@ class TestEventsTabContainer:
# Проверка начального состояния # Проверка начального состояния
tested_pages = 1 tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0} # to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "syslogs", payload = {"table": "syslogs",
@ -281,7 +277,7 @@ class TestEventsTabContainer:
counter += 1 counter += 1
browser.wait_for_timeout(2000) browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages: if counter == tested_pages:
events_tab_container.should_be_final_state() events_tab_container.should_be_final_state()
else: else:
events_tab_container.should_be_all_enabled() events_tab_container.should_be_all_enabled()

View File

@ -34,6 +34,7 @@ class TestMaintenanceEventsContainer:
maintenance_events_container = mp.click_events_panel_maintenance_tab() maintenance_events_container = mp.click_events_panel_maintenance_tab()
maintenance_events_container.check_content() maintenance_events_container.check_content()
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_row_highlighting(self, browser: Page): def test_events_table_row_highlighting(self, browser: Page):
"""Проверяет выделение строк в таблице событий. """Проверяет выделение строк в таблице событий.
@ -51,12 +52,10 @@ class TestMaintenanceEventsContainer:
# Получение количества строк в таблице # Получение количества строк в таблице
rows_count = maintenance_events_container.get_events_table_rows_count() rows_count = maintenance_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
maintenance_events_container.check_events_table_row_highlighting(0) maintenance_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
maintenance_events_container.check_events_table_row_highlighting(rows_count - 1) maintenance_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
maintenance_events_container.check_events_table_row_highlighting(int(rows_count / 2)) maintenance_events_container.check_events_table_row_highlighting(int(rows_count / 2))
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий") @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
@ -75,9 +74,6 @@ class TestMaintenanceEventsContainer:
maintenance_events_container = mp.click_events_panel_maintenance_tab() maintenance_events_container = mp.click_events_panel_maintenance_tab()
# Получение количества строк в таблице
# rows_count = maintenance_events_container.get_events_table_rows_count()
events_panel_position = mp.get_events_panel_position() events_panel_position = mp.get_events_panel_position()
# Проверка, что панель с таблицей открыта # Проверка, что панель с таблицей открыта
@ -131,7 +127,7 @@ class TestMaintenanceEventsContainer:
# Проверка видимости первой строки после прокрутки # Проверка видимости первой строки после прокрутки
maintenance_events_container.check_events_table_first_row_visibility() maintenance_events_container.check_events_table_first_row_visibility()
# @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий") @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_column_sorting(self, browser: Page): def test_events_table_column_sorting(self, browser: Page):
"""Проверяет сортировку колонки 'Время' в таблице событий. """Проверяет сортировку колонки 'Время' в таблице событий.
@ -168,7 +164,7 @@ class TestMaintenanceEventsContainer:
convert2timestamp=True) convert2timestamp=True)
assert is_descending_order, "Column data should be in descending order" assert is_descending_order, "Column data should be in descending order"
# @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий") @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_pagination(self, browser: Page): def test_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий. """Проверяет возможность пагинации таблицы событий.
@ -188,7 +184,6 @@ class TestMaintenanceEventsContainer:
# Проверка начального состояния # Проверка начального состояния
tested_pages = 1 tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0} # to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"id": [ "/physical"], payload = {"id": [ "/physical"],
@ -201,11 +196,7 @@ class TestMaintenanceEventsContainer:
response_body = mp.get_response_body(response) response_body = mp.get_response_body(response)
if response_body: if response_body:
pages = int(len(response_body)/40) pages = response_body["data"]["pages"]
if (len(response_body) % 40) > 0:
pages = pages + 1
# print(f"pages = {pages}")
if pages > 5: if pages > 5:
tested_pages = 5 tested_pages = 5
@ -287,7 +278,7 @@ class TestMaintenanceEventsContainer:
counter += 1 counter += 1
browser.wait_for_timeout(2000) browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages: if counter == tested_pages:
maintenance_events_container.should_be_final_state() maintenance_events_container.should_be_final_state()
else: else:
maintenance_events_container.should_be_all_enabled() maintenance_events_container.should_be_all_enabled()

View File

@ -20,7 +20,7 @@ class TestSystemLogEventsContainer:
Атрибуты: Атрибуты:
browser: Фикстура для работы с браузером. browser: Фикстура для работы с браузером.
""" """
# @pytest.mark.develop @pytest.mark.develop
def test_system_log_events_content(self, browser: Page) -> None: def test_system_log_events_content(self, browser: Page) -> None:
"""Проверяет содержимое контейнера для отображения событий системного журнала. """Проверяет содержимое контейнера для отображения событий системного журнала.
@ -55,12 +55,9 @@ class TestSystemLogEventsContainer:
# Получение количества строк в таблице # Получение количества строк в таблице
rows_count = system_log_events_container.get_events_table_rows_count() rows_count = system_log_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
system_log_events_container.check_events_table_row_highlighting(0) system_log_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
system_log_events_container.check_events_table_row_highlighting(rows_count - 1) system_log_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
system_log_events_container.check_events_table_row_highlighting(int(rows_count / 2)) system_log_events_container.check_events_table_row_highlighting(int(rows_count / 2))
def test_events_table_scrolling(self, browser: Page): def test_events_table_scrolling(self, browser: Page):
@ -188,7 +185,6 @@ class TestSystemLogEventsContainer:
# Проверка начального состояния # Проверка начального состояния
tested_pages = 1 tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0} # to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "logs", payload = {"table": "logs",
@ -282,7 +278,7 @@ class TestSystemLogEventsContainer:
counter += 1 counter += 1
browser.wait_for_timeout(2000) browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages: if counter == tested_pages:
system_log_events_container.should_be_final_state() system_log_events_container.should_be_final_state()
else: else:
system_log_events_container.should_be_all_enabled() system_log_events_container.should_be_all_enabled()

View File

@ -1,225 +0,0 @@
"""Модуль тестов вкладки настройки E-mail уведомлений.
Содержит тесты для проверки корректности отображения
и функциональности элементов страницы настройки E-mail уведомлений.
"""
import pytest
from playwright.sync_api import Page, expect
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.email_notifications_settings_tab import EmailNotificationsSettingsTab
# @pytest.mark.smoke
class TestEmailNotificationsSettingsTab:
"""Набор тестов для вкладки настройки E-mail уведомлений.
Проверяет корректность отображения и функциональность элементов вкладки настройки E-mail уведомлений.
"""
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход на вкладку настройки E-mail уведомлений через панель навигации
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Инициализация главной страницы
main_page = MainPage(browser)
# Проверка и взаимодействие с элементами навигации
main_page.should_be_navigation_panel()
main_page.click_main_navigation_panel_item("Настройки")
main_page.click_subpanel_item("Уведомления")
main_page.click_subpanel_item("E-mail")
# @pytest.mark.develop
def test_email_notifications_settings_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки E-mail уведомлений.
Проверяет:
Наличие и корректность элементов интерфейса
"""
current_settings = {}
default_settings = {}
# Инициализация вкладки
email_notification_settings_tab = EmailNotificationsSettingsTab(browser)
# запрос текущих установок
cur_settings_response = email_notification_settings_tab.send_get_api_request("e-cmdb/api/email_notifier")
if cur_settings_response.status == 200:
response_body = email_notification_settings_tab.get_response_body(cur_settings_response)
if response_body:
current_settings = response_body[0].copy()
# запрос дефолтных значений
default_settings_response = email_notification_settings_tab. \
send_get_api_request("e-cmdb/api/email_notifier/meta")
if default_settings_response.status == 200:
response_body = email_notification_settings_tab.get_response_body(default_settings_response)
if response_body:
default_settings = response_body["fields"].copy()
# Проверка элементов интерфейса
email_notification_settings_tab.check_content()
# Получение и проверка отображаемых входных значений формы настроек 'Общие'
common_settings = email_notification_settings_tab.get_common_settings_values()
value = common_settings["Сервер"]
expected = current_settings["smtp_host"]
if expected is None:
expected = self._get_default_value("smtp_host", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'Сервер'"
value = common_settings["Порт"]
expected = current_settings["smtp_port"]
if expected is None:
expected = self._get_default_value("smtp_port", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'Порт'"
value = common_settings["Отправитель"]
expected = current_settings["from_email"]
if expected is None:
expected = self._get_default_value("from_email", default_settings)
assert value == expected, f"Actual value {value} is not equal expected {expected} for field 'Отправитель'"
# Получение и проверка отображаемых входных значений формы настроек 'TLS'
value = email_notification_settings_tab.get_tls_tunnel_setting_value()
expected = current_settings["secure"]
if expected is None:
expected = self._get_default_value("secure", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Использовать TLS-туннель'"
# Получение и проверка отображаемых входных значений формы настроек 'Аутентификация'
auth_settings = email_notification_settings_tab.get_auth_settings_values()
value = auth_settings["Метод авторизации"]
expected = current_settings["auth_method"]
if expected is None:
expected = self._get_default_value("auth_method", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Метод авторизации'"
value = auth_settings["Имя пользователя"]
expected = current_settings["login"]
if expected is None:
expected = self._get_default_value("login", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Имя пользователя'"
value = auth_settings["Пароль"]
expected = current_settings["password"]
if expected is None:
expected = self._get_default_value("password", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Пароль'"
value = auth_settings["Домен"]
expected = current_settings["domain"]
if expected is None:
expected = self._get_default_value("domain", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Домен'"
value = auth_settings["Рабочая станция"]
expected = current_settings["workstation"]
if expected is None:
expected = self._get_default_value("workstation", default_settings)
assert value == expected, \
f"Actual value {value} is not equal expected {expected} for field 'Рабочая станция'"
def test_send_test_email_window_content(self, browser: Page) -> None:
"""Тест модального окна для посылки тестового E-mail.
Проверяет:
Наличие и корректность элементов интерфейса
"""
# Инициализация вкладки
email_notification_settings_tab = EmailNotificationsSettingsTab(browser)
send_test_email_window = email_notification_settings_tab.click_test_button()
send_test_email_window.check_content()
send_test_email_window.close()
send_test_email_window = email_notification_settings_tab.click_test_button()
send_test_email_window.close_by_toolbar_button()
# @pytest.mark.develop
def test_send_test_email_successful(self, browser: Page) -> None:
"""Тест модального окна для посылки тестового E-mail.
Проверяет:
Возможность посылки тестового E-mail по существующему адресу.
"""
# Адрес куда отправлять e-mail
sent_address = "audiomine.platform@gmail.com"
# Инициализация вкладки
email_notification_settings_tab = EmailNotificationsSettingsTab(browser)
# Заполнить поля настройки
# TO-DO after feature release
send_test_email_window = email_notification_settings_tab.click_test_button()
send_test_email_window.input_email(sent_address)
with browser.expect_response("**/e-nms/email/testEmail") as response_info:
send_test_email_window.click_test_button()
send_test_email_window.should_be_success_alert()
response = response_info.value
assert response.ok, "Unsuccessful test e-mail request"
send_test_email_window.close()
# @pytest.mark.develop
def test_send_test_email_address_validation(self, browser: Page) -> None:
"""Тест модального окна для посылки тестового E-mail.
Проверяет:
Валидацию вводимого адреса E-mail.
"""
# Адрес куда отправлять e-mail - фейковый
incorrect_addresses = ["rrrrr", "@mail.ru", "rrrmail.ru", "rr@mail", "rrrr@@mail.ru", "rr@mailru", "rr@my_mail.ru"]
# Инициализация вкладки
email_notification_settings_tab = EmailNotificationsSettingsTab(browser)
send_test_email_window = email_notification_settings_tab.click_test_button()
# Пустое поле ввода адреса
send_test_email_window.click_test_button()
send_test_email_window.should_be_error_alert('\nПоле должно быть заполнено\n')
for address in incorrect_addresses:
send_test_email_window.input_email(address)
send_test_email_window.click_test_button()
send_test_email_window.should_be_error_alert('\nНекорректный e-mail\n')
# нет проверки валидности домена
# fake_address = "test@grandpasvillage.com"
# send_test_email_window.input_email(fake_address)
# send_test_email_window.click_test_button()
# send_test_email_window.should_be_error_alert('\Ошибка входа в систему\n')
send_test_email_window.close()
def _get_default_value(self, setting_name: str, default_settings: dict) -> str| None:
for setting in default_settings:
if setting["name"] == setting_name:
return setting["default"]
return None

View File

@ -62,7 +62,7 @@ class TestPushNotificationsSettingsTab:
assert msg_value == expected_msg_value, \ assert msg_value == expected_msg_value, \
f"Actual message field value {msg_value} is not equal expected message field value {expected_msg_value}" f"Actual message field value {msg_value} is not equal expected message field value {expected_msg_value}"
@pytest.mark.develop # @pytest.mark.develop
def test_send_push_notification(self, browser: Page) -> None: def test_send_push_notification(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки Push уведомлений. """Тест содержимого вкладки настройки Push уведомлений.
@ -122,38 +122,3 @@ class TestPushNotificationsSettingsTab:
push_notification_settings_tab.clear_users_setting_value() push_notification_settings_tab.clear_users_setting_value()
selected_users = push_notification_settings_tab.get_users_setting_value() selected_users = push_notification_settings_tab.get_users_setting_value()
assert len(selected_users) == 0, "There should be no selected users" assert len(selected_users) == 0, "There should be no selected users"
# @pytest.mark.develop
def test_send_push_notification_for_empty_fields(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки Push уведомлений.
Проверяет:
запрет отправки Push уведомления при незаполненных полях формы (кнопка 'Отправить' должна быть заблокирована)
"""
# Инициализация вкладки
push_notification_settings_tab = PushNotificationsSettingsTab(browser)
# оставить пустым поле 'Сообщение', заполнить поле 'Пользователи'
push_notification_settings_tab.input_message("")
msg_value = push_notification_settings_tab.get_message_setting_value()
assert msg_value == "", "Actual message field is not empty"
push_notification_settings_tab.select_users(["manager"])
selected_users = push_notification_settings_tab.get_users_setting_value()
assert selected_users == "manager", \
f"Actual users field value {selected_users} is not equal expected users field value 'manager'"
push_notification_settings_tab.should_be_disabled_button()
# заполнить поле 'Сообщение', очистить поле 'Пользователи'
push_notification_settings_tab.input_message("TEST")
msg_value = push_notification_settings_tab.get_message_setting_value()
assert msg_value == "TEST", \
f"Actual message field value {msg_value} is not equal expected message field value 'TEST'"
push_notification_settings_tab.deselect_users(["manager"])
selected_users = push_notification_settings_tab.get_users_setting_value()
assert selected_users == "", "Actual users field is not empty"
push_notification_settings_tab.should_be_disabled_button()

View File

@ -1,124 +0,0 @@
"""Модуль тестов вкладки настройки СМС уведомлений.
Содержит тесты для проверки корректности отображения
и функциональности элементов страницы настройки СМС уведомлений.
"""
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.sms_notifications_settings_tab import SMSNotificationsSettingsTab
# @pytest.mark.smoke
class TestSMSNotificationsSettingsTab:
"""Набор тестов для вкладки настройки СМС уведомлений.
Проверяет корректность отображения и функциональность элементов вкладки настройки СМС уведомлений.
"""
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход на вкладку настройки СМС уведомлений через панель навигации
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Инициализация главной страницы
main_page = MainPage(browser)
# Проверка и взаимодействие с элементами навигации
main_page.should_be_navigation_panel()
main_page.click_main_navigation_panel_item("Настройки")
main_page.click_subpanel_item("Уведомления")
main_page.click_subpanel_item("СМС")
# @pytest.mark.develop
def test_sms_notifications_settings_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки СМС уведомлений.
Проверяет:
Наличие и корректность элементов интерфейса
"""
# Инициализация вкладки
sms_notification_settings_tab = SMSNotificationsSettingsTab(browser)
# Проверка элементов интерфейса
sms_notification_settings_tab.check_content()
# @pytest.mark.develop
def test_send_sms_window_content(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки СМС уведомлений.
Проверяет:
Заполнение наличие и заполнение поля ввода номера
"""
expected_sms_phone = "7(111) 111-11-11"
# Инициализация вкладки
sms_notification_settings_tab = SMSNotificationsSettingsTab(browser)
sms_notification_settings_tab.should_be_test_button()
send_sms_window = sms_notification_settings_tab.click_test_button()
send_sms_window.check_content()
send_sms_window.input_sms_phone(expected_sms_phone)
actual_sms_phone = send_sms_window.get_sms_phone()
assert expected_sms_phone == actual_sms_phone, \
f"Expected sms phone field value {expected_sms_phone} is not equal actual {actual_sms_phone}"
send_sms_window.close_by_toolbar_button()
# @pytest.mark.develop
# TO-DO: rewrite tescase after feature release
def test_send_sms_notification(self, browser: Page) -> None:
"""Тест содержимого вкладки настройки СМС уведомлений.
Проверяет:
Заполнение полей и отправку СМС уведомления
"""
field_values = ["8.8.8.8", "testadmin", "test12345678"]
# sms_phone = "7(910)123-34-40"
# Инициализация вкладки
sms_notification_settings_tab = SMSNotificationsSettingsTab(browser)
sms_notification_settings_tab.click_edit_button()
sms_notification_settings_tab.input_ip(field_values[0])
ip = sms_notification_settings_tab.get_ip_setting_value()
assert ip == field_values[0], f"Actual IP value {ip} is not equal expected value {field_values[0]}"
sms_notification_settings_tab.input_user(field_values[1])
user = sms_notification_settings_tab.get_user_setting_value()
assert user == field_values[1], f"Actual user name value {user} is not equal expected value {field_values[1]}"
sms_notification_settings_tab.input_password(field_values[2])
password = sms_notification_settings_tab.get_password_setting_value()
dots = password.count(".")
assert dots > 0, "Password should be hidden"
assert len(field_values[2]) == dots, f"Should be {dots} dots for hidden password"
sms_notification_settings_tab.click_password_hidden_icon()
password = sms_notification_settings_tab.get_password_setting_value()
assert password == field_values[2], \
f"Actual password value {password} is not equal expected value {field_values[2]}"
# temporarily, should be rewritten
sms_notification_settings_tab.click_cancel_button()
sms_notification_settings_tab.should_be_test_button()
# send_sms_window = sms_notification_settings_tab.click_test_button()
# send_sms_window.input_sms_phone(sms_phone)
# send_sms_window.should_be_success_alert()

View File

@ -220,11 +220,12 @@ class TestCurrentSessionsTab:
Проверяет: Проверяет:
1. Создание нового пользователя 1. Создание нового пользователя
2. Вход нового пользователя в систему 2. Вход нового пользователя в систему
3. Вход в систему пользователя admin 3. Проверка наличия сеанса нового пользователя
4. Проверка наличия сеанса нового пользователя 4. Выход нового пользователя из системы (logout)
5. Удаление сеанса нового пользователя 5. Вход в систему пользователя admin
6. Проверка отсутствия сеанса нового пользователя 6. Удаление сеанса нового пользователя
7. Удаление пользователя выполняется автоматически фикстурой cleanup_users 7. Проверка отсутствия сеанса нового пользователя
8. Удаление пользователя выполняется автоматически фикстурой cleanup_users
""" """
user_data = {"name": "TestUserForManualDeletion", "role": "Администратор", "password": "qwerty1234567"} user_data = {"name": "TestUserForManualDeletion", "role": "Администратор", "password": "qwerty1234567"}
@ -444,6 +445,9 @@ class TestCurrentSessionsTab:
# Проверка наличия сеанса в таблице # Проверка наличия сеанса в таблице
sessions_tab.should_be_session_in_table(new_user_token) sessions_tab.should_be_session_in_table(new_user_token)
# Выход из системы нового пользователя
new_mp.do_logout()
# Авторизация администратором # Авторизация администратором
admin_lp = LoginPage(browser) admin_lp = LoginPage(browser)
admin_lp.do_login() admin_lp.do_login()
@ -547,8 +551,6 @@ class TestCurrentSessionsTab:
print("Ожидание 15 минут для автоматического удаления сеанса...") print("Ожидание 15 минут для автоматического удаления сеанса...")
browser.wait_for_timeout(901000) # 15 минут 1 секунда в миллисекундах browser.wait_for_timeout(901000) # 15 минут 1 секунда в миллисекундах
# TO-DO: Должна быть проверка перехода на страницу логина для текущего пользователя, fix 890
# Авторизация администратором # Авторизация администратором
admin_lp = LoginPage(browser) admin_lp = LoginPage(browser)
admin_lp.do_login() admin_lp.do_login()

View File

@ -91,14 +91,12 @@ class TestSessionSettingsTab:
session_settings_tab.edit_settings(new_settings) session_settings_tab.edit_settings(new_settings)
updated_settings = session_settings_tab.get_settings_values()
for key, value in new_settings.items():
updated_value = updated_settings.get(key)
assert updated_value == value, f"{key} updated value {updated_value} is not equal expected value {value}"
# temporarily # temporarily
session_settings_tab.click_cancel_button() # updated_settings = session_settings_tab.get_settings_values()
# for key, value in new_settings.items():
# updated_value = updated_settings.get(key)
# assert updated_value == value, f"{key} updated value {updated_value} is not equal expected value {value}"
# @pytest.mark.develop # @pytest.mark.develop
def test_edit_session_setting_by_arrow(self, browser: Page) -> None: def test_edit_session_setting_by_arrow(self, browser: Page) -> None:

View File

@ -1,526 +0,0 @@
"""Модуль тестов вкладки 'Резервное копирование'.
Содержит тесты для проверки корректности отображения
и функциональности элементов вкладки настройки времени жизни сеансов.
"""
from datetime import datetime, timezone
import os
from pathlib import Path
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.backup_settings_tab import BackupSettingsTab
# @pytest.mark.smoke
class TestBackupSettingsTab:
"""Набор тестов для вкладки 'Обслуживание и диагностика/Резервное копирование'.
Проверяет корректность отображения и функциональность элементов вкладки 'Резервное копирование'.
"""
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход на вкладку 'Резервное копирование' через панель навигации
"""
browser.add_init_script("window.__PLAYWRIGHT__ = true;")
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Инициализация главной страницы
main_page = MainPage(browser)
# Проверка и взаимодействие с элементами навигации
main_page.should_be_navigation_panel()
main_page.click_main_navigation_panel_item("Настройки")
main_page.click_subpanel_item("Обслуживание и диагностика")
main_page.click_subpanel_item("Резервное копирование")
@pytest.mark.develop
def test_backup_settings_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки 'Резервное копирование'.
Проверяет:
1. Наличие и корректность элементов интерфейса
2. Соответствие содержимого полей формы данным из БД
"""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# Проверка элементов интерфейса
backup_settings_tab.check_content()
# запрос текущих установок настройки 'Инвентаризация/Параметры планировщика'
expected_inventory_settings = {}
cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb")
if cur_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(cur_settings_response)
if response_body:
expected_inventory_settings = response_body[0].copy()
if len(expected_inventory_settings) == 0:
# запрос дефолтных значений настройки 'Инвентаризация/Параметры планировщика'
default_settings = {}
default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb/meta")
if default_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(default_settings_response)
if response_body:
default_settings = response_body["fields"].copy()
expected_inventory_settings["auto_backup"] = self._get_default_value("auto_backup",
default_settings)
expected_inventory_settings["backup_limitation"] = self._get_default_value("backup_limitation",
default_settings)
# Проверка соответствия для значений настройки 'Инвентаризация/Параметры планировщика'
inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
inventory_auto_backup = inventory_scheduler_settings.get("auto_backup")
inventory_backup_limitation = inventory_scheduler_settings.get("backup_limitation")
if inventory_auto_backup:
expected = expected_inventory_settings["auto_backup"]
assert inventory_auto_backup == expected,\
f"Actual value {inventory_auto_backup} \
is not equal expected {expected} for field 'Время создания резервной копии'"
else:
assert False, "No value setting for field 'Время создания резервной копии'"
if inventory_backup_limitation:
expected = expected_inventory_settings["backup_limitation"]
assert inventory_backup_limitation == expected,\
f"Actual value {inventory_backup_limitation} \
is not equal expected {expected} for field 'Количество резервных копий'"
else:
assert False, "No value setting for field 'Количество резервных копий'"
# запрос текущих установок настройки 'Потоковые данные'
expected_sd_settings = {}
cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata")
if cur_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(cur_settings_response)
if response_body:
expected_sd_settings = response_body[0].copy()
if len(expected_sd_settings) == 0:
# запрос дефолтных значений настройки 'Потоковые данные'
default_sd_settings = {}
default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata/meta")
if default_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(default_settings_response)
if response_body:
default_sd_settings = response_body["fields"].copy()
expected_sd_settings["auto_backup"] = self._get_default_value("auto_backup", default_sd_settings)
expected_sd_settings["data_limitation_default"] = self._get_default_value(
"data_limitation_default", default_sd_settings)
expected_sd_settings["interval_limitation_default"] = self._get_default_value(
"interval_limitation_default", default_sd_settings)
expected_sd_settings["data_limitation_logs"] = self._get_default_value("data_limitation_logs",
default_sd_settings)
expected_sd_settings["interval_limitation_logs"] = self._get_default_value(
"interval_limitation_logs", default_sd_settings)
expected_sd_settings["data_limitation_metrics"] = self._get_default_value("data_limitation_metrics",
default_sd_settings)
expected_sd_settings["interval_limitation_metrics"] = self._get_default_value(
"interval_limitation_metrics",
default_sd_settings)
expected_sd_settings["data_limitation_syslog"] = self._get_default_value("data_limitation_syslog",
default_sd_settings)
expected_sd_settings["interval_limitation_syslog"] = self._get_default_value(
"interval_limitation_syslog", default_sd_settings)
expected_sd_settings["data_limitation_tasks"] = self._get_default_value("data_limitation_tasks",
default_sd_settings)
expected_sd_settings["interval_limitation_tasks"] = self._get_default_value(
"interval_limitation_tasks", default_sd_settings)
# Проверка соответствия для значений настроек 'Потоковые данные и Параметры планировщика'
dates = {"day":"ДЕНЬ", "hour":"ЧАС", "month":"МЕСЯЦ", "year":"ГОД"}
streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
sd_auto_backup = streaming_data_scheduler_settings.get("auto_backup")
streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
if sd_auto_backup:
expected = expected_sd_settings["auto_backup"]
assert sd_auto_backup == expected,\
f"Actual value {sd_auto_backup} \
is not equal expected {expected} for field 'Время создания резервной копии'"
else:
assert False, "No value setting for field 'Время создания резервной копии' streaming data"
settings_list = streaming_data_settings.keys()
for setting in settings_list:
expected = expected_sd_settings[setting]
if dates.get(expected):
expected_setting = dates[expected]
else:
expected_setting = str(expected)
actual = streaming_data_settings[setting]
assert actual == expected_setting,\
f"Actual value {actual} is not equal expected {expected_setting} for field '{setting}'"
# @pytest.mark.develop
def test_backup_settings_tab_check_backup_copies_amount(self, browser: Page) -> None:
"""Тест проверки количества резервных копий."""
# TO-DO: Тест проверки правильности времени их создания
# To-DO: Ограничение на количество копий для потоковых данных?
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# получение текущих установок настройки 'Инвентаризация/Параметры планировщика'
inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
#inventory_auto_backup = inventory_scheduler_settings["auto_backup"]
inventory_backup_limitation = int(inventory_scheduler_settings["backup_limitation"])
# получение списка резервных копий настройки 'Инвентаризация'
dumps = backup_settings_tab.get_inventory_dumps_list()
print(dumps)
assert inventory_backup_limitation >= len(dumps), \
f"Required to store {inventory_backup_limitation} but {len(dumps)} stores"
# @pytest.mark.develop
def test_backup_settings_tab_create_copy(self, browser: Page) -> None:
"""Тест проверки создания резервных копий."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# проверка создания резервной копии для блока 'Инвентаризация'
current_date = datetime.now(timezone.utc)
current_ts = current_date.timestamp()
backup_settings_tab.create_inventory_copy()
backup_settings_tab.wait_for_timeout(3000)
dumps_cmdb = backup_settings_tab.get_inventory_dumps_list()
max_ts, _ = self._get_last_dump(dumps_cmdb, "inventory")
assert max_ts - current_ts < 1000, "New inventory backup copy not found"
# проверка создания резервной копии для блока 'Потоковые данные'
current_date = datetime.now(timezone.utc)
current_ts = current_date.timestamp()
backup_settings_tab.create_streaming_data_copy()
backup_settings_tab.wait_for_timeout(3000)
dumps_streaming_data = backup_settings_tab.get_streaming_data_dumps_list()
max_ts, _ = self._get_last_dump(dumps_streaming_data, "streaming_data")
assert max_ts - current_ts < 1000, "New streaming_data backup copy not found"
# @pytest.mark.develop
def test_backup_settings_tab_check_backup_buttons(self, browser: Page) -> None:
"""Тест проверки поведения кнопок управления резервными копиями."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
dumps_cmdb = backup_settings_tab.get_inventory_dumps_list()
max_ts, last_dump = self._get_last_dump(dumps_cmdb, "inventory")
backup_settings_tab.select_inventory_dump(last_dump)
backup_settings_tab.should_be_inventory_download_button()
is_disabled = backup_settings_tab.check_inventory_restore_copy_button_disabling()
assert not is_disabled, "Inventory button to restore copy should be enabled"
backup_settings_tab.clear_inventory_dump_selection()
backup_settings_tab.should_be_inventory_upload_button()
is_disabled = backup_settings_tab.check_inventory_restore_copy_button_disabling()
assert is_disabled, "Inventory button to restore copy should be disabled"
dumps_streaming_data = backup_settings_tab.get_streaming_data_dumps_list()
max_ts, last_dump = self._get_last_dump(dumps_streaming_data, "streaming_data")
backup_settings_tab.select_streaming_data_dump(last_dump)
backup_settings_tab.should_be_streaming_data_download_button()
is_disabled = backup_settings_tab.check_streaming_data_restore_copy_button_disabling()
assert not is_disabled, "Streaming data button to restore copy should be enabled"
backup_settings_tab.clear_streaming_data_dump_selection()
backup_settings_tab.should_be_streaming_data_upload_button()
is_disabled = backup_settings_tab.check_streaming_data_restore_copy_button_disabling()
assert is_disabled, "Streaming data button to restore copy should be disabled"
# @pytest.mark.develop
def test_backup_settings_tab_check_inventory_download_copy(self, browser: Page) -> None:
"""Тест проверки возможности загрузки резервной копии 'Инвентаризация'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
path_to_download = Path.home() / "Downloads"
dumps_cmdb = backup_settings_tab.get_inventory_dumps_list()
max_ts, last_dump = self._get_last_dump(dumps_cmdb, "inventory")
backup_settings_tab.download_inventory_copy(last_dump, path_to_download)
downloaded = str(path_to_download) + "/" + last_dump
assert os.path.exists(downloaded), f"The file '{downloaded}' not found"
assert os.path.getsize(downloaded) > 0, f"The file '{downloaded}' is empty"
os.remove(downloaded)
#@pytest.mark.develop
def test_backup_settings_tab_check_streaming_data_download_copy(self, browser: Page) -> None:
"""Тест проверки возможности загрузки резервной копии 'Потоковые данные'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
path_to_download = Path.home() / 'Documents'
dumps_cmdb = backup_settings_tab.get_streaming_data_dumps_list()
max_ts, last_dump = self._get_last_dump(dumps_cmdb, "streaming_data")
backup_settings_tab.download_streaming_data_copy(last_dump, path_to_download)
downloaded = str(path_to_download) + "/" + last_dump
assert os.path.exists(downloaded), f"The file '{downloaded}' not found"
assert os.path.getsize(downloaded) > 0, f"The file '{downloaded}' is empty"
os.remove(downloaded)
# @pytest.mark.develop
def test_backup_settings_tab_set_inventory_scheduler_settings(self, browser: Page) -> None:
"""Тест проверки возможности изменения значения настроек 'Инвентаризация/Параметры планировщика'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# считываем и запоминаем текущие знчения
orig_inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
orig_auto_backup = orig_inventory_scheduler_settings.get("auto_backup")
assert orig_auto_backup, "Сouldn't read the value of 'auto backup' from inventory scheduler settings"
orig_backup_limitation = orig_inventory_scheduler_settings.get("backup_limitation")
assert orig_backup_limitation, "Сouldn't read the value of 'backup limitation' from inventory scheduler settings"
# устанавливаем новые значения
backup_settings_tab.click_edit_button()
backup_settings_tab.input_inventory_backup_creation_time("0 0 22 * * 7")
backup_settings_tab.input_inventory_backups_number("6")
backup_settings_tab.decrease_inventory_backups_number()
backup_settings_tab.increase_inventory_backups_number()
backup_settings_tab.click_save_button()
# проверка ожидаемых значений
inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
inventory_auto_backup = inventory_scheduler_settings["auto_backup"]
inventory_backup_limitation = inventory_scheduler_settings["backup_limitation"]
assert inventory_auto_backup == "0 0 22 * * 7", \
f"Actual value '{inventory_auto_backup}' \
is not equal expected '0 0 22 * * 7' for field 'Время создания резервной копии'"
assert inventory_backup_limitation == "6", \
f"Actual value '{inventory_backup_limitation}' \
is not equal expected '6' for field 'Количество резервных копий'"
# восстановление ранее сохраненных значений
backup_settings_tab.click_edit_button()
backup_settings_tab.input_inventory_backup_creation_time(orig_auto_backup)
backup_settings_tab.input_inventory_backups_number(orig_backup_limitation)
backup_settings_tab.click_save_button()
# @pytest.mark.develop
def test_backup_settings_tab_set_streaming_data_settings(self, browser: Page) -> None:
"""Тест проверки возможности изменения значения настроек 'Потоковые данные'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# считываем и запоминаем текущие знчения
orig_streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
for name in orig_streaming_data_settings:
val = orig_streaming_data_settings.get(name)
assert val, f"Сouldn't read the value of '{name}' from inventory scheduler settings"
orig_streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
orig_auto_backup = orig_streaming_data_scheduler_settings.get("auto_backup")
assert orig_auto_backup, "Сouldn't read the value of 'auto backup' from streaming data scheduler settings"
# устанавливаем новые значения
backup_settings_tab.click_edit_button()
backup_settings_tab.input_audit_time_period("4", "месяц")
backup_settings_tab.increase_audit_time_period()
backup_settings_tab.decrease_audit_time_period()
backup_settings_tab.input_logs_time_period("4", "месяц")
backup_settings_tab.increase_logs_time_period()
backup_settings_tab.decrease_logs_time_period()
backup_settings_tab.input_metrics_time_period("4", "месяц")
backup_settings_tab.increase_metrics_time_period()
backup_settings_tab.decrease_metrics_time_period()
backup_settings_tab.input_syslog_time_period("4", "месяц")
backup_settings_tab.increase_syslog_time_period()
backup_settings_tab.decrease_syslog_time_period()
backup_settings_tab.input_tasks_time_period("4", "месяц")
backup_settings_tab.increase_tasks_time_period()
backup_settings_tab.decrease_tasks_time_period()
backup_settings_tab.input_streaming_data_backup_creation_time("0 0 22 * * 7")
backup_settings_tab.click_save_button()
# проверка ожидаемых значений
streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
data_limitation_default = streaming_data_settings["data_limitation_default"]
assert data_limitation_default == "4", \
f"Actual value '{data_limitation_default}' is not equal expected '4' for category 'Аудит'"
interval_limitation_default = streaming_data_settings["interval_limitation_default"]
assert interval_limitation_default == "МЕСЯЦ", \
f"Actual value '{interval_limitation_default}' is not equal expected 'месяц' for category 'Аудит'"
data_limitation_logs = streaming_data_settings["data_limitation_logs"]
assert data_limitation_logs == "4", \
f"Actual value '{data_limitation_logs}' is not equal expected '4' for category 'Логи'"
interval_limitation_logs = streaming_data_settings["interval_limitation_logs"]
assert interval_limitation_logs == "МЕСЯЦ", \
f"Actual value '{interval_limitation_logs}' is not equal expected 'месяц' for category 'Логи'"
data_limitation_metrics = streaming_data_settings["data_limitation_metrics"]
assert data_limitation_metrics == "4", \
f"Actual value '{data_limitation_metrics}' is not equal expected '4' for category 'Метрики'"
interval_limitation_metrics = streaming_data_settings["interval_limitation_metrics"]
assert interval_limitation_metrics == "МЕСЯЦ", \
f"Actual value '{interval_limitation_metrics}' is not equal expected 'месяц' for category 'Метрики'"
data_limitation_syslog = streaming_data_settings["data_limitation_syslog"]
assert data_limitation_syslog == "4", \
f"Actual value '{data_limitation_syslog}' is not equal expected '4' for category 'Системный лог'"
interval_limitation_syslog = streaming_data_settings["interval_limitation_syslog"]
assert interval_limitation_syslog == "МЕСЯЦ", \
f"Actual value '{interval_limitation_syslog}' is not equal expected 'месяц' for category 'Системный лог'"
data_limitation_tasks = streaming_data_settings["data_limitation_tasks"]
assert data_limitation_tasks == "4", \
f"Actual value '{data_limitation_tasks}' is not equal expected '4' for category 'Действия'"
interval_limitation_tasks = streaming_data_settings["interval_limitation_tasks"]
assert interval_limitation_tasks == "МЕСЯЦ", \
f"Actual value '{interval_limitation_tasks}' is not equal expected 'месяц' for category 'Действия'"
streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
streaming_data_auto_backup = streaming_data_scheduler_settings["auto_backup"]
assert streaming_data_auto_backup == "0 0 22 * * 7", \
f"Actual value '{streaming_data_auto_backup}' \
is not equal expected '0 0 22 * * 7' for field 'Потоковые данные Время создания резервной копии'"
# восстановление ранее сохраненных значений
backup_settings_tab.click_edit_button()
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.input_audit_time_period(orig_streaming_data_settings["data_limitation_default"],
orig_streaming_data_settings["interval_limitation_default"])
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.input_logs_time_period(orig_streaming_data_settings["data_limitation_logs"],
orig_streaming_data_settings["interval_limitation_logs"])
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.input_metrics_time_period(orig_streaming_data_settings["data_limitation_metrics"],
orig_streaming_data_settings["interval_limitation_metrics"])
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.input_syslog_time_period(orig_streaming_data_settings["data_limitation_syslog"],
orig_streaming_data_settings["interval_limitation_syslog"])
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.input_tasks_time_period(orig_streaming_data_settings["data_limitation_tasks"],
orig_streaming_data_settings["interval_limitation_tasks"])
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.input_streaming_data_backup_creation_time(orig_auto_backup)
backup_settings_tab.wait_for_timeout(1000)
backup_settings_tab.click_save_button()
# @pytest.mark.develop
@pytest.mark.skip(reason="Временно пока работает неправильно")
def test_backup_settings_tab_check_auto_copy_creation(self, browser: Page) -> None:
"""Тест проверки создания резервных копий в автоматическом режиме на примере блока 'Инвентаризация'."""
# делать backup один раз в минуту
requested_creation_time = "* */1 * * * *"
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
current_setings = backup_settings_tab.get_inventory_scheduler_settings_values()
current_creation_time = current_setings.get("auto_backup")
assert current_creation_time, "No creation time value for 'Inventory' block"
current_backup_limitation = current_setings.get("backup_limitation")
assert current_backup_limitation, "No backup limitation value for 'Inventory' block"
time_to_wait = int(current_backup_limitation) + 1
backup_settings_tab.click_edit_button()
backup_settings_tab.input_inventory_backup_creation_time(requested_creation_time)
backup_settings_tab.click_save_button()
current_date = datetime.now(timezone.utc)
current_ts = current_date.timestamp()
backup_settings_tab.wait_for_timeout(time_to_wait*60000)
backup_settings_tab.click_edit_button()
backup_settings_tab.input_inventory_backup_creation_time(current_creation_time)
backup_settings_tab.click_save_button()
dumps_cmdb = backup_settings_tab.get_inventory_dumps_list()
dumps_amount = len(dumps_cmdb)
assert dumps_amount == int(current_backup_limitation), \
f"Should be {current_backup_limitation} dumps but got {dumps_amount}"
for dump in dumps_cmdb:
dump_date = dump.replace("dump_cmdb_", "").replace(".dump", "")
date_object = datetime.strptime(dump_date, "%Y-%m-%d_%H_%M_%S")
ts = date_object.timestamp()
assert current_ts < ts, f"Old backup copy {dump} in backups list"
# Вспомогательные функции
def _get_default_value(self, setting_name: str, default_settings: dict) -> str| None:
""" Выбор дефолтного значения из списка по его имени """
for setting in default_settings:
if setting["name"] == setting_name:
return setting["default"]
return None
def _get_last_dump(self, dumps_list: list[str],settings_type : str) -> list:
""" Выбор последнего по времени дампа из списка.
Возвращает timestamp и имя дампа.
"""
dumps = {}
ret_values = []
for dump in dumps_list:
if settings_type == "inventory":
dump_date = dump.replace("dump_cmdb_", "").replace(".dump", "")
date_object = datetime.strptime(dump_date, "%Y-%m-%d_%H_%M_%S")
elif settings_type == "streaming_data":
dump_date = dump.replace("monitoring_backup_", "").replace(".zip", "")
date_object = datetime.strptime(dump_date, "%Y-%m-%d_%H-%M-%S")
else:
assert False, "Unsupported backup setting type"
ts = date_object.timestamp()
dumps[ts] = dump
max_ts = sorted(dumps.keys())[-1]
ret_values.append(max_ts)
ret_values.append(dumps[max_ts])
return ret_values

Some files were not shown because too many files have changed in this diff Show More