Compare commits
4 Commits
main
...
ra3/edit_e
| Author | SHA1 | Date |
|---|---|---|
|
|
852b42f226 | |
|
|
c56bf70cfa | |
|
|
6576e4463e | |
|
|
4d3731d9f7 |
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
# ...
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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:
|
||||||
"""Прокручивает содержимое окна вниз."""
|
"""Прокручивает содержимое окна вниз."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет наличие и видимость кнопок тулбара."""
|
"""Проверяет наличие и видимость кнопок тулбара."""
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет наличие и видимость кнопок тулбара."""
|
"""Проверяет наличие и видимость кнопок тулбара."""
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет наличие и видимость кнопок тулбара."""
|
"""Проверяет наличие и видимость кнопок тулбара."""
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет наличие и видимость кнопок тулбара."""
|
"""Проверяет наличие и видимость кнопок тулбара."""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
|
@ -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":
|
||||||
|
|
|
||||||
|
|
@ -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 при успешном добавлении пользователя
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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):
|
||||||
"""Проверяет наличие и корректность элементов окна.
|
"""Проверяет наличие и корректность элементов окна.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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')
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
@ -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/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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']"
|
|
||||||
|
|
@ -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']"
|
||||||
|
|
|
||||||
|
|
@ -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']"
|
|
||||||
|
|
||||||
|
|
@ -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')]"
|
||||||
|
|
|
||||||
|
|
@ -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)"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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')]"
|
||||||
|
|
|
||||||
|
|
@ -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']"
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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']"
|
|
||||||
|
|
|
||||||
|
|
@ -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')]"
|
||||||
|
|
|
||||||
|
|
@ -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')]"
|
||||||
|
|
|
||||||
|
|
@ -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']"
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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()
|
|
||||||
|
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
@ -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')
|
|
||||||
|
|
@ -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')
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет наличие кнопки 'Обновить'.
|
"""Проверяет наличие кнопки 'Обновить'.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет соответствие содержимого полей формы данным из БД.
|
"""Проверяет соответствие содержимого полей формы данным из БД.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет наличие тулбара.
|
"""Проверяет наличие тулбара.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет прокрутку таблицы статусов сервисов.
|
"""Проверяет прокрутку таблицы статусов сервисов.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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, \
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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 знаков
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
|
@ -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")
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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:
|
||||||
"""Проверяет состояние и количество кнопок расширения рабочей области панели событий.
|
"""Проверяет состояние и количество кнопок расширения рабочей области панели событий.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class TestSessionSettingsTab:
|
||||||
else:
|
else:
|
||||||
print(f"Error request session setings data from API: {response.status_text}")
|
print(f"Error request session setings data from API: {response.status_text}")
|
||||||
|
|
||||||
# @pytest.mark.develop
|
#@pytest.mark.develop
|
||||||
def test_edit_session_settings(self, browser: Page) -> None:
|
def test_edit_session_settings(self, browser: Page) -> None:
|
||||||
"""Тест проверки возможности редактирования выбранных полей формы настройки времени жизни сеансов.
|
"""Тест проверки возможности редактирования выбранных полей формы настройки времени жизни сеансов.
|
||||||
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue