Compare commits

..

6 Commits

Author SHA1 Message Date
Radislav bed778d84d Изменено проверка pylint в check_tab_switching 2025-12-16 15:01:13 +03:00
Radislav 00cf1ce14f Изменено в соответствии с code-review 2025-12-16 13:56:20 +03:00
Radislav e87b428f09 Обновление локаторов элементов стойки v2 2025-12-15 16:38:59 +03:00
Radislav 4523ff6db3 Обновление локаторов элементов стойки 2025-12-15 13:25:59 +03:00
Radislav d71fe80166 Обновление локаторов элементов стойки
- Обновил локатор кнопки редактирования на использование data-testid
- Исправил локаторы кнопок скрытия/показа стойки
- Исправил метод определения активной стороны стойки
- Добавил вкладку 'Состав' в тесты переключения вкладок
- Обновил структуру вкладок в соответствии с текущим UI
2025-12-15 11:27:01 +03:00
Radislav 348530fe37 Добавлены тесты для вкладки элемента 'Стойка'
>>
>> - Создан tests/e2e/elements/test_element_rack.py
>> - Создан pages/rack_page.py
>> - Изменен модуль locators/rack_locators.py, добавлены с локаторами элементов стойки
2025-12-15 08:46:11 +03:00
113 changed files with 3712 additions and 12462 deletions

View File

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

View File

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

View File

@ -8,7 +8,6 @@ from tools.logger import get_logger
logger = get_logger("BASE_COMPONENT") logger = get_logger("BASE_COMPONENT")
logger.setLevel("INFO")
class BaseComponent: class BaseComponent:
"""Базовый компонент для работы с элементами страницы. """Базовый компонент для работы с элементами страницы.
@ -29,56 +28,6 @@ class BaseComponent:
self.page = page self.page = page
# Действия: # Действия:
def get_input_fields_locators(self, container_locator: Locator) -> dict:
"""Находит пары "метка-поле ввода" в контейнере с layout структурой.
Метод ищет элементы в структуре div.layout > div.flex, где:
- Первый div.flex содержит метку (текст в input элементе)
- Второй div.flex содержит соответствующее поле ввода
Поддерживает различные структуры:
- xs4 (метка) -> xs8 (поле ввода)
- xs4 (метка) -> xs1 (поле ввода)
- Любые другие парные flex контейнеры
Args:
container_locator: Контейнер, в котором искать поля ввода.
Returns:
Словарь, где ключ - текст метки, значение - Locator контейнера с полем ввода.
"""
fields_locators = {}
layouts = container_locator.locator("div.layout > div.flex").locator("..")
for i in range(layouts.count()):
layout = layouts.nth(i)
flex_containers = layout.locator("div.flex")
# Обрабатываем пары контейнеров
for j in range(0, flex_containers.count() - 1, 2):
label_container = flex_containers.nth(j)
input_container = flex_containers.nth(j + 1)
# Извлекаем текст метки
inputs = label_container.locator("input")
if inputs.count() > 0:
label_text = inputs.first.input_value().strip()
if label_text:
# Проверяем поле ввода
has_input = input_container.locator(
"input, textarea, select"
).count() > 0
not_found = fields_locators.get(label_text) is None
if has_input and not_found:
fields_locators[label_text] = input_container
return fields_locators
def get_locator(self, locator: str | Locator) -> Locator: def get_locator(self, locator: str | Locator) -> Locator:
"""Получение объекта Locator из строки или существующего Locator. """Получение объекта Locator из строки или существующего Locator.

View File

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

View File

@ -17,48 +17,31 @@ logger = get_logger("CONFIRM_WINDOW")
class ConfirmComponent(BaseComponent): class ConfirmComponent(BaseComponent):
"""Компонент окна подтверждения действий.""" """Компонент окна подтверждения действий."""
def __init__(self, page: Page, cancel_button_text: str = "", allow_button_text: str = "", def __init__(self, page: Page, cancel_button_text: str, allow_button_text: str):
cancel_button_locator: str = None, allow_button_locator: str = None):
"""Инициализация компонента. """Инициализация компонента.
Args: Args:
page: Экземпляр страницы Playwright. page: Экземпляр страницы Playwright.
cancel_button_text: Текст кнопки отмены (по умолчанию пустая строка). cancel_button_text: Текст кнопки отмены.
allow_button_text: Текст кнопки подтверждения (по умолчанию пустая строка). allow_button_text: Текст кнопки подтверждения.
cancel_button_locator: Локатор кнопки отмены (опционально).
allow_button_locator: Локатор кнопки подтверждения (опционально).
""" """
super().__init__(page) super().__init__(page)
self.title = Text(page, ConfirmLocators.TITLE, "confirm title") self.title = Text(page, ConfirmLocators.TITLE, "confirm title")
self.text = Text(page, ConfirmLocators.TEXT, "confirm text") self.text = Text(page, ConfirmLocators.TEXT, "confirm text")
self.close_button = Button(page, ConfirmLocators.BUTTON_CLOSE, "confirm close button")
# Инициализация кнопок с приоритетом локаторам self.close_button = Button(page, ConfirmLocators.BUTTON_CLOSE, "confirm close button")
if cancel_button_locator:
self.cancel_button = Button(page, cancel_button_locator, "confirm cancel button")
elif cancel_button_text:
self.cancel_button = Button( self.cancel_button = Button(
page, page,
page.get_by_role("button", name=cancel_button_text).first, page.get_by_role("button", name=cancel_button_text).first,
"confirm cancel button" "confirm cancel button"
) )
else:
self.cancel_button = None
logger.warning("Cancel button not initialized - neither text nor locator specified")
if allow_button_locator:
self.allow_button = Button(page, allow_button_locator, "confirm allow button")
elif allow_button_text:
self.allow_button = Button( self.allow_button = Button(
page, page,
page.get_by_role("button", name=allow_button_text).first, page.get_by_role("button", name=allow_button_text).first,
"confirm allow button" "confirm allow button"
) )
else:
self.allow_button = None
logger.warning("Allow button not initialized - neither text nor locator specified")
# Действия: # Действия:
def click_allow_button(self) -> None: def click_allow_button(self) -> None:

View File

@ -86,7 +86,7 @@ class DatePickerComponent(BaseComponent):
days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS) days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS)
days_table_locator.wait_for(timeout=300) days_table_locator.wait_for(timeout=300)
day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day, exact=True) day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day)
visible = day_button_locator.is_visible() visible = day_button_locator.is_visible()
if visible: if visible:
day_button_locator.click() day_button_locator.click()
@ -140,27 +140,27 @@ class DatePickerComponent(BaseComponent):
def check_content(self) -> None: def check_content(self) -> None:
"""Проверка состава компонент средства выбора даты.""" """Проверка состава компонент средства выбора даты."""
month_dict = {"01":"январь", month_dict = {"1":"январь",
"02":"февраль", "2":"февраль",
"03":"март", "3":"март",
"04":"апрель", "4":"апрель",
"05":"май", "5":"май",
"06":"июнь", "6":"июнь",
"07":"июль", "7":"июль",
"08":"август", "8":"август",
"09":"сентябрь", "9":"сентябрь",
"10":"октябрь", "10":"октябрь",
"11":"ноябрь", "11":"ноябрь",
"12":"декабрь"} "12":"декабрь"}
days_per_month = {"01":31, days_per_month = {"1":31,
"02":28, "2":28,
"03":31, "3":31,
"04":30, "4":30,
"05":31, "5":31,
"06":30, "6":30,
"07":31, "7":31,
"08":31, "8":31,
"09":30, "9":30,
"10":31, "10":31,
"11":30, "11":30,
"12":31} "12":31}
@ -189,7 +189,7 @@ class DatePickerComponent(BaseComponent):
assert actual_month_year == expected_month_year, \ assert actual_month_year == expected_month_year, \
f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body" f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body"
expected_day = str(expected_date.strftime("%d")).lstrip('0') expected_day = str(expected_date.strftime("%d"))
actual_day = self.get_day() actual_day = self.get_day()
assert actual_day == expected_day, \ assert actual_day == expected_day, \
f"Expected day {expected_day} is not equal actual day {actual_day} on date picker body" f"Expected day {expected_day} is not equal actual day {actual_day} on date picker body"

View File

@ -82,12 +82,7 @@ class DropdownList(BaseComponent):
loc = self.get_locator(locator) loc = self.get_locator(locator)
texts = loc.all_inner_texts() texts = loc.all_inner_texts()
if len(texts) == 1 and texts[0].find("\n") != -1: return texts[0].splitlines()
names = list(texts[0].splitlines())
else:
names = list(texts)
return names
def get_selected_combobox_value(self, combobox_locator: str | Locator, def get_selected_combobox_value(self, combobox_locator: str | Locator,
value_locator: str | Locator = None) -> str: value_locator: str | Locator = None) -> str:

View File

@ -6,10 +6,6 @@ from locators.event_panel_locators import EventPanelLocators
from elements.tooltip_button_element import TooltipButton from elements.tooltip_button_element import TooltipButton
from elements.tab_button_element import TabButton from elements.tab_button_element import TabButton
from elements.button_element import Button from elements.button_element import Button
from components_derived.container_actions_events import ActionsEventsContainer
from components_derived.container_audit_events import AuditEventsContainer
from components_derived.container_events import EventsTabContainer
from components_derived.container_maintenance_events import MaintenanceEventsContainer
from components_derived.container_system_log_events import SystemLogEventsContainer from components_derived.container_system_log_events import SystemLogEventsContainer
from components_derived.user_card import UserCard from components_derived.user_card import UserCard
from components.base_component import BaseComponent from components.base_component import BaseComponent
@ -29,82 +25,43 @@ class EventPanelComponent(BaseComponent):
super().__init__(page) super().__init__(page)
self.expand_less_button = Button(page, self.states_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_STATES), "states_tab")
page. locator(EventPanelLocators.BUTTON_EXPAND_LESS), self.actions_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_ACTIONS), "actions_tab")
"expand_less_button") self.events_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_EVENTS), "events_tab")
self.expand_more_button = Button(page,
page. locator(EventPanelLocators.BUTTON_EXPAND_MORE),
"expand_more_button")
self.states_tab = TabButton(page, page.locator(EventPanelLocators.TAB_STATES), "states_tab")
self.actions_tab = TabButton(page, page.locator(EventPanelLocators.TAB_ACTIONS), "actions_tab")
self.events_tab = TabButton(page, page.locator(EventPanelLocators.TAB_EVENTS), "events_tab")
self.maintenance_tab = TabButton(page, self.maintenance_tab = TabButton(page,
page.locator(EventPanelLocators.TAB_MAINTENANCE), "maintenance_tab") self.page.locator(EventPanelLocators.TAB_MAINTENANCE), "maintenance_tab")
self.system_log_tab = TabButton(page, page.locator(EventPanelLocators.TAB_SYSTEM_LOG), "system_log_tab") self.system_log_tab = TabButton(page, self.page.locator(EventPanelLocators.TAB_SYSTEM_LOG), "system_log_tab")
self.audit_tab = TabButton(page, page.locator(EventPanelLocators.TAB_AUDIT), "audit_tab")
self.unknown_reason_button = TooltipButton(page, self.unknown_reason_button = TooltipButton(page,
page.locator(EventPanelLocators.BUTTONS_EVENT).nth(0), self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(0),
"unknown_reason_button") "unknown_reason_button")
self.warning_button = TooltipButton(page, self.warning_button = TooltipButton(page,
page.locator(EventPanelLocators.BUTTONS_EVENT).nth(1), self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(1),
"warning_button") "warning_button")
self.damage_button = TooltipButton(page, self.damage_button = TooltipButton(page,
page.locator(EventPanelLocators.BUTTONS_EVENT).nth(2), self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(2),
"damage_button") "damage_button")
self.failure_button = TooltipButton(page, self.failure_button = TooltipButton(page,
page.locator(EventPanelLocators.BUTTONS_EVENT).nth(3), self.page.locator(EventPanelLocators.BUTTONS_EVENT).nth(3),
"failure_button") "failure_button")
self.user_button = Button(page, page.locator(EventPanelLocators.BUTTON_USER), "user_button") user_button_locator = self.page.locator(EventPanelLocators.BUTTONS_SERVICE).get_by_role("button")
self.user_button = Button(page, user_button_locator, "user_button")
# Действия: # Действия:
def click_expand_less_button(self) -> None: def click_expand_less_button(self) -> None:
"""Выполняет нажатие кнопки галочка вверх.""" """Выполняет нажатие кнопки галочка вверх."""
self.expand_less_button.click() button_locator = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\
get_by_role("button").filter(has_text='expand_less')
button_locator.click()
def click_expand_more_button(self) -> None: def click_expand_more_button(self) -> None:
"""Выполняет нажатие кнопки галочка вниз.""" """Выполняет нажатие кнопки галочка вниз."""
self.expand_more_button.click() button_locator = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\
get_by_role("button").filter(has_text='expand_more')
def click_actions_tab(self) -> ActionsEventsContainer: button_locator.click(force=True)
"""Выполняет нажатие tab-кнопки Действия."""
self.actions_tab.check_visibility("Actions tab button is missing on event panel")
self.actions_tab.click()
actions_tab = ActionsEventsContainer(self.page, EventPanelLocators.CONTAINER_ACTIONS_TAB)
return actions_tab
def click_audit_tab(self) -> AuditEventsContainer:
"""Выполняет нажатие tab-кнопки Аудит."""
self.audit_tab.check_visibility("Audit tab button is missing on event panel")
self.audit_tab.click()
audit_tab = AuditEventsContainer(self.page, EventPanelLocators.CONTAINER_AUDIT_EVENTS)
return audit_tab
def click_events_tab(self) -> EventsTabContainer:
"""Выполняет нажатие tab-кнопки События."""
self.events_tab.check_visibility("Events tab button is missing on event panel")
self.events_tab.click()
events_tab = EventsTabContainer(self.page, EventPanelLocators.CONTAINER_EVENTS_TAB)
return events_tab
def click_maintenance_tab(self) -> MaintenanceEventsContainer:
"""Выполняет нажатие tab-кнопки Обслуживание."""
self.maintenance_tab.check_visibility("Maintenance tab button is missing on event panel")
self.maintenance_tab.click()
maintenance_tab = MaintenanceEventsContainer(self.page, EventPanelLocators.CONTAINER_MAINTENANCE_EVENTS)
return maintenance_tab
def click_system_log_tab(self) -> SystemLogEventsContainer: def click_system_log_tab(self) -> SystemLogEventsContainer:
"""Выполняет нажатие tab-кнопки Системный журнал.""" """Выполняет нажатие tab-кнопки Системный журнал."""
@ -158,12 +115,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"
@ -171,19 +128,21 @@ class EventPanelComponent(BaseComponent):
# Проверки: # Проверки:
def check_expand_less_button(self) -> bool: def check_expand_less_button(self) -> bool:
"""Проверяет наличие кнопки галочка вниз.""" """Проверяет наличие кнопки галочка вверх."""
try: try:
_ = self.page.locator(EventPanelLocators.BUTTON_EXPAND_LESS) _ = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\
get_by_role("button").filter(has_text='expand_less')
except TimeoutError: except TimeoutError:
return False return False
return True return True
def check_expand_more_button(self) -> bool: def check_expand_more_button(self) -> bool:
"""Проверяет наличие кнопки галочка вверх""" """Проверяет наличие кнопки галочка вниз"""
try: try:
_ = self.page.locator(EventPanelLocators.BUTTON_EXPAND_MORE) _ = self.page.locator(EventPanelLocators.TAB_EXPAND_BUTTONS).\
get_by_role("button").filter(has_text='expand_more')
except TimeoutError: except TimeoutError:
return False return False
return True return True
@ -193,8 +152,13 @@ class EventPanelComponent(BaseComponent):
self.user_button.check_visibility("User button is missing on event panel") self.user_button.check_visibility("User button is missing on event panel")
def should_be_search_button(self) -> None:
"""Проверяет наличие кнопки поиска."""
self.search_button.check_visibility("Search button is missing on event panel")
def should_be_tab_buttons(self) -> None: def should_be_tab_buttons(self) -> None:
"""Проверяет наличие блока tab-кнопок Состояния, Действия, События, Обслуживание, Системный журнал, Аудит.""" """Проверяет наличие блока tab-кнопок Состояния, Действия, События, Обслуживание, Системный журнал."""
self.states_tab.check_have_text('Состояния', "Tab button with text Состояния is missing on event panel") self.states_tab.check_have_text('Состояния', "Tab button with text Состояния is missing on event panel")
self.actions_tab.check_have_text('Действия',"Tab button with text Действия is missing on event panel") self.actions_tab.check_have_text('Действия',"Tab button with text Действия is missing on event panel")
@ -203,8 +167,6 @@ class EventPanelComponent(BaseComponent):
"Tab button with text Обслуживание is missing on event panel") "Tab button with text Обслуживание is missing on event panel")
self.system_log_tab.check_have_text('Системный журнал', self.system_log_tab.check_have_text('Системный журнал',
"Tab button with text Системный журнал is missing on event panel") "Tab button with text Системный журнал is missing on event panel")
self.audit_tab.check_have_text('Аудит',
"Tab button with text Аудит is missing on event panel")
def should_be_event_buttons(self) -> None: def should_be_event_buttons(self) -> None:
"""Проверяет наличие блока кнопок-счетчиков событий.""" """Проверяет наличие блока кнопок-счетчиков событий."""

View File

@ -1,13 +1,13 @@
"""Модуль компонента контейнера с перечнем событий. Содержит класс для работы с контейнерами, """Модуль компонента контейнера с перечнем событий. Содержит класс для работы с контейнерами,
их элементами и проверками.""" их элементами и проверками."""
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 locators.event_panel_locators import EventPanelLocators from locators.toolbar_locators import ToolbarLocators
from elements.tooltip_button_element import TooltipButton from elements.tooltip_button_element import TooltipButton
from elements.tab_button_element import TabButton from elements.tab_button_element import TabButton
from elements.button_element import Button from elements.button_element import Button
from components_derived.events_filter_panel import EventsFilterPanel from components_derived.sidebar_filter_component import SidebarFilterComponent
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 components.base_component import BaseComponent from components.base_component import BaseComponent
@ -32,11 +32,18 @@ class EventsContainerComponent(BaseComponent):
# тулбар # тулбар
self.toolbar = ToolbarComponent(page, "") self.toolbar = ToolbarComponent(page, "")
self.toolbar_locator = EventPanelLocators.TOOLBAR filter_button_locator = self.container_locator.locator(ToolbarLocators.TITLE).\
get_by_role("button")
self.toolbar.add_button(filter_button_locator, "filter_button")
export_buttons = self.container_locator.locator(ToolbarLocators.ITEMS).\
get_by_role("button").all()
self.toolbar.add_tooltip_button(export_buttons[1], "export_to_csv_button")
self.toolbar.add_tooltip_button(export_buttons[0], "export_to_pdf_button")
# Таблица событий # Таблица событий
self.events_table = TableComponent(page) self.events_table = TableComponent(page)
self.table_locator = EventPanelLocators.TABLE self.table_locator = "//div[@class='scrolltable']/div/table"
# Кнопки пагинации в нижней части контейнера # Кнопки пагинации в нижней части контейнера
self.chevron_left = Button(page, self.chevron_left = Button(page,
@ -57,21 +64,16 @@ class EventsContainerComponent(BaseComponent):
locator("xpath=..").get_by_role("button").nth(2) locator("xpath=..").get_by_role("button").nth(2)
self.data_set_number = Button(page, loc, "data_set_number") self.data_set_number = Button(page, loc, "data_set_number")
self.events_filter = EventsFilterPanel(self.page) self.sidebar_filter_locator = self.container_locator.locator("//aside//div[@class='scrollarea__container']")
self.sidebar_filter = SidebarFilterComponent(self.page,
self.sidebar_filter_locator)
# Действия: # Действия:
def add_tab_to_toolbar(self, locator: str | Locator, name: str) -> None: def add_tab_to_toolbar(self, locator: str | Locator, name: str) -> None:
"""Добавление кнопки типа v-tabs к тулбару""" """Добавление кнопки типа v-tabs к тулбару"""
tab_locator = self.get_locator(locator) tabs_locator = self.get_locator(locator)
self.toolbar.add_tab_button(tab_locator, name) self.toolbar.add_tab_button(self.container_locator.locator(tabs_locator), name)
def click_tab_button(self, name: str) -> None:
"""Нажатие tab кнопки на тулбаре"""
tab_button = self.toolbar.get_button_by_name(name)
assert tab_button, f"Try to click unexisted button {name}"
tab_button.click()
def click_chevron_left(self) -> None: def click_chevron_left(self) -> None:
"""Нажатие кнопки получения предыдущего набора данных""" """Нажатие кнопки получения предыдущего набора данных"""
@ -84,7 +86,7 @@ class EventsContainerComponent(BaseComponent):
self.chevron_right.click() self.chevron_right.click()
def click_first_page(self) -> None: def click_first_page(self) -> None:
"""Нажатие кнопки перехода на первую страницу""" """Нажатие кнопки перехода на первую сраницу"""
self.first_page.click() self.first_page.click()
@ -93,13 +95,13 @@ class EventsContainerComponent(BaseComponent):
self.last_page.click() self.last_page.click()
def click_filter_button(self) -> EventsFilterPanel: def click_filter_button(self) -> SidebarFilterComponent:
"""Нажатие кнопки фильтр""" """Нажатие кнопки перехода на первую сраницу"""
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" self.sidebar_filter.check_visibility(self.sidebar_filter_locator,
"Filter sidebar is missing")
return self.events_filter return self.sidebar_filter
def click_event_table_header_arrow(self, index: int) -> None: def click_event_table_header_arrow(self, index: int) -> None:
""" Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы
@ -110,15 +112,6 @@ class EventsContainerComponent(BaseComponent):
loc = self.container_locator.locator(self.table_locator) loc = self.container_locator.locator(self.table_locator)
self.events_table.click_arrow_button(loc, index) self.events_table.click_arrow_button(loc, index)
def get_events_table_row_locator(self, index: int) -> Locator:
""" Возвращает локатор строки таблицы по ее индексу
Args:
index: Индекс строки в таблице.
"""
loc = self.container_locator.locator(self.table_locator)
return self.events_table.get_row_locator(loc, index)
def get_current_data_set_number(self) -> int: def get_current_data_set_number(self) -> int:
"""Получение номера текущего набора данных""" """Получение номера текущего набора данных"""
@ -164,10 +157,10 @@ class EventsContainerComponent(BaseComponent):
loc = self.container_locator.locator(self.table_locator) loc = self.container_locator.locator(self.table_locator)
return self.events_table.get_rows_count(loc) return self.events_table.get_rows_count(loc)
def get_events_filter(self) -> EventsFilterPanel: def get_sidebar_filter(self) -> SidebarFilterComponent:
"""Возвращает панель фильтрации.""" """Возвращает боковую панель фильтрации."""
return self.events_filter return self.sidebar_filter
def get_toolbar_filter_button(self) -> Button: def get_toolbar_filter_button(self) -> Button:
"""Возвращает кнопку фильтрации.""" """Возвращает кнопку фильтрации."""
@ -207,16 +200,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:
@ -296,21 +279,6 @@ class EventsContainerComponent(BaseComponent):
return self.last_page.is_disabled() return self.last_page.is_disabled()
def is_tab_active(self, name: str) -> bool:
"""Проверка является ли tab-button активным"""
tab_button = self.toolbar.get_button_by_name(name)
assert tab_button, f"Try to find unexisted button {name}"
tab_button_locator = tab_button.get_locator()
is_active = True
attr = tab_button_locator.locator("../..").get_attribute("class")
if attr.find("active") == -1:
is_active = False
return is_active
def should_be_all_disabled(self) -> None: def should_be_all_disabled(self) -> None:
"""Проверка видимости кнопок пагинации: все кнопки disabled""" """Проверка видимости кнопок пагинации: все кнопки disabled"""
@ -387,9 +355,19 @@ class EventsContainerComponent(BaseComponent):
def should_be_toolbar(self) -> None: def should_be_toolbar(self) -> None:
"""Проверка наличия тулбара""" """Проверка наличия тулбара"""
loc = self.container_locator.locator(self.toolbar_locator).first loc = self.container_locator.locator("//nav[contains(@class, 'v-toolbar')]").nth(0)
self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar is missing") self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar is missing")
def should_be_base_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость базовых кнопок тулбара."""
self.toolbar.check_button_visibility("filter_button")
self.toolbar.check_button_visibility("export_to_pdf_button")
self.toolbar.check_button_tooltip("export_to_pdf_button", "Скачать в формате PDF")
self.toolbar.check_button_visibility("export_to_csv_button")
self.toolbar.check_button_tooltip("export_to_csv_button", "Скачать в формате CSV")
def should_be_pagination_buttons(self) -> None: def should_be_pagination_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок пагинации.""" """Проверяет наличие и видимость кнопок пагинации."""

View File

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

View File

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

View File

@ -327,29 +327,43 @@ 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: коээфициенты масштабирования (причина: несовпадение масштабов контента страницы и фрейма)
""" """
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)
# Прокручиваем и ждем
row.scroll_into_view_if_needed()
self.page.wait_for_timeout(1000)
# Получение "ограничительной рамки" строки
bounding_box = row.evaluate("el => el.getBoundingClientRect()")
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")
row.scroll_into_view_if_needed() # Вычисление координат целевой строки таблицы и перевод на нее курсора мыши
self.page.wait_for_timeout(1000) bounding_box = row.evaluate("el => el.getBoundingClientRect()")
bounding_box = row.bounding_box() # center_x = (bounding_box["x"] + bounding_box["width"] / 2 + offset_x) * scale_x
assert bounding_box, "Requested row is not visible" # center_y = (bounding_box["y"] + bounding_box["height"] / 2 + offset_y) * scale_y
center_x = bounding_box["x"] + bounding_box["width"] / 4 center_x = (bounding_box["x"] + bounding_box["width"] / 2) * scale_x + offset_x
center_y = bounding_box["y"] + bounding_box["height"] / 2 center_y = (bounding_box["y"] + bounding_box["height"] / 2) * scale_y + offset_y
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(1000)

View File

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

View File

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

View File

@ -0,0 +1,228 @@
"""Модуль создания объекта 'Стойка'."""
from dataclasses import dataclass
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from components.base_component import BaseComponent
logger = get_logger("RACK_MAKER")
@dataclass
class RackData:
"""Класс для хранения данных стойки."""
name: str
height: str = "42"
depth: str = "1000"
serial: str = ""
inventory: str = ""
comment: str = ""
cable_entry: str = ""
state: str = ""
owner: str = ""
service_org: str = ""
project: str = ""
class RackObjectMaker(BaseComponent):
"""Компонент для создания и настройки стойки."""
def __init__(self, page: Page) -> None:
"""
Инициализирует компонент создания стойки.
Args:
page: Экземпляр страницы Playwright
"""
super().__init__(page)
# Действия:
def fill_rack_data(self, rack_data: RackData) -> None:
"""
Заполняет данные для создания стойки.
Args:
rack_data: Данные стойки
"""
logger.info(f"Filling rack data: {rack_data.name}")
self._fill_text_fields(rack_data)
self._fill_combobox_fields(rack_data)
logger.info("Rack data filled successfully")
def _fill_text_fields(self, rack_data: RackData) -> None:
"""Заполняет текстовые поля."""
def clear_and_fill(locator, value: str, field_name: str):
"""Очищает поле и заполняет его значением."""
field = self.page.locator(locator).first
# Очищаем поле
field.click()
field.press("Control+A")
field.press("Backspace")
# Заполняем значение
field.fill(value)
logger.info(f"Filled '{field_name}': {value}")
# Обязательные поля.
if rack_data.name:
clear_and_fill(RackLocators.RACK_NAME_FIELD, rack_data.name, "Name")
# Опциональные поля.
if rack_data.serial:
clear_and_fill(RackLocators.RACK_SERIAL_FIELD, rack_data.serial, "Serial number")
if rack_data.inventory:
clear_and_fill(RackLocators.RACK_INVENTORY_FIELD, rack_data.inventory, "Inventory number")
if rack_data.comment:
clear_and_fill(RackLocators.RACK_COMMENT_FIELD, rack_data.comment, "Comment")
def _fill_combobox_fields(self, rack_data: RackData) -> None:
"""Заполняет combobox поля."""
# Обязательные поля.
if rack_data.height:
self._fill_combobox_field("Height in units", rack_data.height,
RackLocators.RACK_HEIGHT_FIELD)
logger.info(f"Selected height: {rack_data.height} units")
if rack_data.depth:
self._fill_combobox_field("Depth (mm)", rack_data.depth,
RackLocators.RACK_DEPTH_FIELD)
logger.info(f"Selected depth: {rack_data.depth} mm")
# Опциональные поля.
if rack_data.cable_entry:
self._fill_combobox_field("Cable entry", rack_data.cable_entry,
RackLocators.RACK_CABLE_ENTRY_FIELD)
logger.info(f"Selected cable entry: {rack_data.cable_entry}")
if rack_data.state:
self._fill_combobox_field("State", rack_data.state,
RackLocators.RACK_STATE_FIELD)
logger.info(f"Selected state: {rack_data.state}")
if rack_data.owner:
self._fill_combobox_field("Owner", rack_data.owner,
RackLocators.RACK_OWNER_FIELD)
logger.info(f"Selected owner: {rack_data.owner}")
if rack_data.service_org:
self._fill_combobox_field("Service organization", rack_data.service_org,
RackLocators.RACK_SERVICE_ORG_FIELD)
logger.info(f"Selected service organization: {rack_data.service_org}")
if rack_data.project:
self._fill_combobox_field("Project/Title", rack_data.project,
RackLocators.RACK_PROJECT_FIELD)
logger.info(f"Selected project/title: {rack_data.project}")
def _fill_combobox_field(self, field_name: str, value: str, field_locator: str) -> None:
"""
Заполняет combobox поле.
Args:
field_name: Название поля
value: Значение для установки
field_locator: Локатор поля
"""
logger.info(f"Filling field '{field_name}' with value '{value}'...")
# Используем first() для избежания strict mode violation
field_container = self.page.locator(field_locator).first
# Прокручиваем до поля
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(500)
# Проверяем видимость поля
self.check_visibility(field_container, f"Field '{field_name}' not found")
# Кликаем и вводим значение
field_container.click(force=True)
self.wait_for_timeout(1000)
# Вводим значение
self.page.keyboard.type(value)
self.wait_for_timeout(500)
self.page.keyboard.press("Enter")
logger.info(f"Field '{field_name}' filled successfully")
def _get_field_locator(self, field_name: str) -> str:
"""
Возвращает локатор поля по его названию.
Args:
field_name: Название поля
Returns:
str: Локатор поля
"""
field_map = {
"Имя": RackLocators.RACK_NAME_FIELD,
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
}
if field_name not in field_map:
raise ValueError(f"Field '{field_name}' is not supported")
return field_map[field_name]
# Проверки:
def check_rack_fields_presence(self) -> None:
"""
Проверяет наличие полей специфичных для стойки.
Raises:
AssertionError: Если какое-либо поле не найдено
"""
logger.info("Checking rack fields presence...")
# Основные обязательные поля
required_fields = [
(RackLocators.RACK_NAME_FIELD, "Name"),
(RackLocators.RACK_HEIGHT_FIELD, "Height in units"),
(RackLocators.RACK_DEPTH_FIELD, "Depth (mm)")
]
# Дополнительные поля
optional_fields = [
(RackLocators.RACK_SERIAL_FIELD, "Serial number"),
(RackLocators.RACK_INVENTORY_FIELD, "Inventory number"),
(RackLocators.RACK_COMMENT_FIELD, "Comment"),
(RackLocators.RACK_CABLE_ENTRY_FIELD, "Cable entry"),
(RackLocators.RACK_STATE_FIELD, "State"),
(RackLocators.RACK_OWNER_FIELD, "Owner"),
(RackLocators.RACK_SERVICE_ORG_FIELD, "Service organization"),
(RackLocators.RACK_PROJECT_FIELD, "Project/Title")
]
# Проверяем обязательные поля
for field_locator, field_name in required_fields:
field = self.page.locator(field_locator).first
self.check_visibility(field, f"Required field '{field_name}' not found")
logger.info(f"Required field '{field_name}' found")
# Проверяем дополнительные поля
for field_locator, field_name in optional_fields:
field = self.page.locator(field_locator).first
if field.count() > 0 and field.is_visible():
logger.info(f"Optional field '{field_name}' found")
else:
logger.info(f"Optional field '{field_name}' not found or not visible")
logger.info("All main rack fields are present")

View File

@ -1,138 +0,0 @@
"""Модуль контейнера для отображения событий вкладки Действия.
Содержит класс для работы с контейнером для отображения событий
вкладки Действия через Playwright.
"""
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.event_panel_locators import EventPanelLocators
from components.events_container_component import EventsContainerComponent
from components_derived.modal_view_task import ViewTaskModalWindow
logger = get_logger("ACTIONS_EVENTS_CONTAINER")
class ActionsEventsContainer(EventsContainerComponent):
"""Компонент контейнера для отображения событий вкладки Действия.
Предоставляет методы для взаимодействия с элементами
контейнера для отображения событий вкладки Действия.
"""
def __init__(self, page: Page, locator: str | Locator):
"""Инициализирует компонент контейнера для отображения событий вкладки Действия.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page, locator)
toolbar_locator = self.get_locator(locator).locator(EventPanelLocators.TOOLBAR). \
filter(has_text="Фильтр Реальное время Архив")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.FILTER_TOOLBAR_BUTTON), "filter_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.REAL_TIME_TOOLBAR_BUTTON),
"real_time_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.ARCHIVE_TOOLBAR_BUTTON), "archive_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.PDF_TOOLBAR_BUTTON), "export_to_pdf_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.add_filtering_parameter("filter_status", "Статус")
events_filter.add_filtering_parameter("filter_task_name", "НАИМЕНОВАНИЕ ЗАДАЧИ")
events_filter.add_filtering_parameter("filter_object", "Объект")
events_filter.add_filtering_parameter("filter_user", "Пользователь")
# Действия:
def click_archive_button(self) -> None:
"""Нажимает кнопку Архив на тулбаре."""
self.toolbar.check_button_visibility("archive_button")
self.click_tab_button("archive_button")
def click_real_time_button(self) -> None:
"""Нажимает кнопку Реальное время на тулбаре."""
self.toolbar.check_button_visibility("real_time_button")
self.click_tab_button("real_time_button")
def click_events_table_row(self, index) -> ViewTaskModalWindow:
"""Выбор и нажатие на строку таблицы по ее индексу."""
loc = self.get_events_table_row_locator(index)
loc.scroll_into_view_if_needed()
loc.click()
return ViewTaskModalWindow(self.page)
# Проверки:
def check_content(self) -> None:
"""Проверяет содержимое контейнера для отображения событий вкладки Действия."""
expected_real_time_headers = ['ВРЕМЯ НАЧАЛА', 'ВРЕМЯ ЗАВЕРШЕНИЯ','СТАТУС',
'НАИМЕНОВАНИЕ ЗАДАЧИ', 'ОБЪЕКТ', 'ПОЛЬЗОВАТЕЛЬ', 'ОПИСАНИЕ']
expected_archive_headers = ['ВРЕМЯ НАЧАЛА', 'ВРЕМЯ ЗАВЕРШЕНИЯ','СТАТУС',
'НАИМЕНОВАНИЕ ЗАДАЧИ', 'ОБЪЕКТ', 'ПОЛЬЗОВАТЕЛЬ', 'ПРОЦЕССИНГ']
self.should_be_toolbar()
self.should_be_toolbar_buttons()
if not self.is_tab_active("real_time_button"):
self.click_tab_button("real_time_button")
self.wait_for_timeout(1000)
self.check_events_table_content(expected_real_time_headers)
if not self.is_tab_active("archive_button"):
self.click_tab_button("archive_button")
self.wait_for_timeout(1000)
self.check_events_table_content(expected_archive_headers)
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("Пользователь")
events_filter.click_close_button()
def check_events_table_content(self, expected_headers: list[str]) -> None:
"""Проверка содержимого таблицы"""
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")
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()
def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара."""
self.toolbar.check_button_visibility("filter_button")
self.toolbar.check_button_visibility("real_time_button")
self.toolbar.check_button_visibility("archive_button")
self.toolbar.check_button_visibility("export_to_pdf_button")
self.toolbar.check_button_visibility("export_to_csv_button")

View File

@ -1,128 +0,0 @@
"""Модуль контейнера для отображения событий аудита.
Содержит класс для работы с контейнером для отображения событий
аудита через Playwright.
"""
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.event_panel_locators import EventPanelLocators
from components.events_container_component import EventsContainerComponent
logger = get_logger("AUDIT_EVENTS_CONTAINER")
class AuditEventsContainer(EventsContainerComponent):
"""Компонент контейнера для отображения событий аудита.
Предоставляет методы для взаимодействия с элементами
контейнера для отображения событий аудита.
"""
def __init__(self, page: Page, locator: str | Locator):
"""Инициализирует компонент контейнера для отображения событий аудита.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page, locator)
toolbar_locator = self.get_locator(locator).locator(EventPanelLocators.TOOLBAR)
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.FILTER_TOOLBAR_BUTTON), "filter_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.EVENTS_TOOLBAR_BUTTON), "view_events_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.PDF_TOOLBAR_BUTTON), "export_to_pdf_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.add_filtering_parameter("filter_type", "Тип")
events_filter.add_filtering_parameter("filter_role", "Роль")
events_filter.add_filtering_parameter("filter_name", "Имя")
events_filter.add_filtering_parameter("filter_ip", "ip")
# Действия:
# Проверки:
def check_content(self) -> None:
"""Проверяет содержимое контейнера для отображения событий аудита."""
expected_headers = [ 'ВРЕМЯ', 'ОПИСАНИЕ', 'ИДЕНТИФИКАТОР']
self.should_be_toolbar()
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 check_content_security(self) -> None:
"""Проверяет содержимое контейнера для отображения событий безопасности."""
expected_headers = [ 'ВРЕМЯ', 'ОПИСАНИЕ', 'ИДЕНТИФИКАТОР', 'ТИП']
self.should_be_toolbar()
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:
"""Проверяет наличие и видимость кнопок тулбара."""
self.toolbar.check_button_visibility("filter_button")
self.toolbar.check_button_visibility("view_events_button")
self.toolbar.check_button_visibility("export_to_pdf_button")
self.toolbar.check_button_visibility("export_to_csv_button")

View File

@ -1,88 +0,0 @@
"""Модуль контейнера для отображения событий вкладки События панели событий.
Содержит класс для работы с контейнером для отображения событий
вкладки События панели событий через Playwright.
"""
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.event_panel_locators import EventPanelLocators
from components.events_container_component import EventsContainerComponent
logger = get_logger("EVENTS_TAB_CONTAINER")
class EventsTabContainer(EventsContainerComponent):
"""Компонент контейнера для отображения событий вкладки События панели событий.
Предоставляет методы для взаимодействия с элементами
контейнера для отображения событий вкладки События панели событий.
"""
def __init__(self, page: Page, locator: str | Locator):
"""Инициализирует компонент контейнера для отображения событий вкладки События панели событий.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page, locator)
toolbar_locator = self.get_locator(locator).locator(EventPanelLocators.TOOLBAR)
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.FILTER_TOOLBAR_BUTTON), "filter_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.EVENTS_TOOLBAR_BUTTON), "view_events_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.PDF_TOOLBAR_BUTTON), "export_to_pdf_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.add_filtering_parameter("filter_type", "Тип")
events_filter.add_filtering_parameter("filter_strictness", "Критичность")
events_filter.add_filtering_parameter("filter_object", "Объект")
# Действия:
# Проверки:
def check_content(self) -> None:
"""Проверяет содержимое контейнера для отображения событий системного журнала."""
expected_headers = [ 'ВРЕМЯ', 'ТИП', 'КРИТИЧНОСТЬ', 'ОБЪЕКТ', 'ОПИСАНИЕ']
self.should_be_toolbar()
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.click_close_button()
def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара."""
self.toolbar.check_button_visibility("filter_button")
self.toolbar.check_button_visibility("view_events_button")
self.toolbar.check_button_visibility("export_to_pdf_button")
self.toolbar.check_button_visibility("export_to_csv_button")

View File

@ -1,103 +0,0 @@
"""Модуль контейнера для отображения событий обслуживания.
Содержит класс для работы с контейнером для отображения событий
обслуживания через Playwright.
"""
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.event_panel_locators import EventPanelLocators
from components.events_container_component import EventsContainerComponent
logger = get_logger("MAINTENANCE_EVENTS_CONTAINER")
class MaintenanceEventsContainer(EventsContainerComponent):
"""Компонент контейнера для отображения событий обслуживания.
Предоставляет методы для взаимодействия с элементами
контейнера для отображения событий системного журнала.
"""
def __init__(self, page: Page, locator: str | Locator):
"""Инициализирует компонент контейнера для отображения событий обслуживания.
Args:
page: Экземпляр страницы Playwright.
"""
super().__init__(page, locator)
toolbar_locator = self.get_locator(locator).locator(EventPanelLocators.TOOLBAR)
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.FILTER_TOOLBAR_BUTTON), "filter_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.MAINTENANCE_TOOLBAR_BUTTON),
"maintenance_button")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.PDF_TOOLBAR_BUTTON), "export_to_pdf_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.add_filtering_parameter("filter_event_name", "Наименование события")
events_filter.add_filtering_parameter("filter_type", "Тип")
events_filter.add_filtering_parameter("filter_status", "Состояние")
events_filter.add_filtering_parameter("filter_object", "Объект")
events_filter.add_filtering_parameter("filter_author", "Автор")
events_filter.add_filtering_parameter("filter_location", "Расположение")
# Действия:
# Проверки:
def check_content(self) -> None:
"""Проверяет содержимое контейнера для отображения событий обслуживания."""
expected_headers = ['ДАТА', 'НАИМЕНОВАНИЕ СОБЫТИЯ' ,'ТИП',
'СОСТОЯНИЕ', 'ОБЪЕКТ', 'АВТОР', 'РАСПОЛОЖЕНИЕ']
self.should_be_toolbar()
self.should_be_toolbar_buttons()
if not self.is_tab_active("maintenance_button"):
self.click_tab_button("maintenance_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'"
rows_count = len(events_table)
if rows_count == 1:
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()
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("Объект")
events_filter.should_be_filtering_parameter("Автор")
events_filter.should_be_filtering_parameter("Расположение")
events_filter.click_close_button()
def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и видимость кнопок тулбара."""
self.toolbar.check_button_visibility("filter_button")
self.toolbar.check_button_visibility("maintenance_button")
self.toolbar.check_button_visibility("export_to_pdf_button")
self.toolbar.check_button_visibility("export_to_csv_button")

View File

@ -6,7 +6,7 @@
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.event_panel_locators import EventPanelLocators from locators.toolbar_locators import ToolbarLocators
from components.events_container_component import EventsContainerComponent from components.events_container_component import EventsContainerComponent
logger = get_logger("SYSTEM_LOG_EVENTS_CONTAINER") logger = get_logger("SYSTEM_LOG_EVENTS_CONTAINER")
@ -28,31 +28,26 @@ class SystemLogEventsContainer(EventsContainerComponent):
super().__init__(page, locator) super().__init__(page, locator)
toolbar_locator = self.get_locator(locator).locator(EventPanelLocators.TOOLBAR) self.add_tab_to_toolbar(ToolbarLocators.TABS, "events")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.FILTER_TOOLBAR_BUTTON), "filter_button") sidebar_filter = self.get_sidebar_filter()
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.EVENTS_TOOLBAR_BUTTON), "view_events_button") sidebar_filter.add_filtering_parameter("filter_type", "Тип")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.PDF_TOOLBAR_BUTTON), "export_to_pdf_button") sidebar_filter.add_filtering_parameter("filter_strictness", "Критичность")
self.add_tab_to_toolbar(toolbar_locator.locator(EventPanelLocators.CSV_TOOLBAR_BUTTON), "export_to_csv_button") sidebar_filter.add_filtering_parameter("filter_host", "Объект")
events_filter = self.get_events_filter()
events_filter.add_filtering_parameter("filter_type", "Тип")
events_filter.add_filtering_parameter("filter_strictness", "Критичность")
events_filter.add_filtering_parameter("filter_object", "Объект")
# Действия: # Действия:
# Проверки: # Проверки:
def check_content(self) -> None: def check_content(self) -> None:
"""Проверяет содержимое контейнера для отображения событий системного журнала.""" """Проверяет содержимое контейнера для отображения событий системного журнала."""
expected_headers = ['ВРЕМЯ', 'ТИП','КРИТИЧНОСТЬ', 'ОБЪЕКТ', 'ОПИСАНИЕ'] expected_headers = ['ТИП', 'ВРЕМЯ', 'КРИТИЧНОСТЬ', 'ОБЪЕКТ', 'ОПИСАНИЕ']
self.should_be_toolbar() self.should_be_toolbar()
self.should_be_toolbar_buttons() self.should_be_base_toolbar_buttons()
if not self.is_tab_active("view_events_button"): events_tab = self.get_toolbar_tab_button("events")
self.click_tab_button("view_events_button") events_tab_text = events_tab.get_text(0)
self.wait_for_timeout(1000) assert events_tab_text.find("События") != -1, "Tab button with text События is missing on toolbar"
self.should_be_events_table() self.should_be_events_table()
events_table = self.get_events_table_content() events_table = self.get_events_table_content()
@ -61,37 +56,23 @@ 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") assert False, "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() sidebar_filter = self.click_filter_button()
events_filter.check_content() sidebar_filter.check_content()
events_filter.should_be_filtering_parameter("Тип") filter_type_bar = sidebar_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 = sidebar_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"
def should_be_toolbar_buttons(self) -> None: filter_host_bar = sidebar_filter.get_filtering_parameter("filter_host")
"""Проверяет наличие и видимость кнопок тулбара.""" filter_host_title = filter_host_bar.get_selection_bar_title()
assert filter_host_title == "Объект", "Filtering parameter bar 'Объект' is missing"
self.toolbar.check_button_visibility("filter_button")
self.toolbar.check_button_visibility("view_events_button")
self.toolbar.check_button_visibility("export_to_pdf_button")
self.toolbar.check_button_visibility("export_to_csv_button")

View File

@ -5,8 +5,9 @@
from playwright.sync_api import Page, Locator, expect from playwright.sync_api import Page, Locator, expect
from tools.logger import get_logger from tools.logger import get_logger
# from elements.text_element import Text
from elements.text_input_element import TextInput from elements.text_input_element import TextInput
from elements.tooltip_button_element import TooltipButton from elements.button_element import Button
from components.date_picker_component import DatePickerComponent from components.date_picker_component import DatePickerComponent
from components.base_component import BaseComponent from components.base_component import BaseComponent
@ -31,7 +32,7 @@ class DateInput(BaseComponent):
self.date_input_locator = self.get_locator(locator) self.date_input_locator = self.get_locator(locator)
self.switch_mode_button = TooltipButton(page, self.switch_mode_button = Button(page,
self.date_input_locator.get_by_role("button"), self.date_input_locator.get_by_role("button"),
"switch_mode_button") "switch_mode_button")
@ -87,15 +88,11 @@ class DateInput(BaseComponent):
except ValueError: except ValueError:
assert False, f"Incorrect year value {year} for selection" assert False, f"Incorrect year value {year} for selection"
# Temporarily due to error in UI if self.is_text_input_mode():
if not self.is_text_input_mode():
# if self.is_text_input_mode():
# print("by keyboard")
self.date_input_field.check_editable_input("Text field for date input should be editable") self.date_input_field.check_editable_input("Text field for date input should be editable")
self.date_input_field.clear() self.date_input_field.clear()
self.date_input_field.input_value(date) self.date_input_field.input_value(date)
else: else:
# print("by date picker")
self.date_picker.select_year_and_month(year, month) self.date_picker.select_year_and_month(year, month)
self.date_picker.select_day(day) self.date_picker.select_day(day)
@ -126,9 +123,8 @@ class DateInput(BaseComponent):
self.check_switch_mode_button_visibility() self.check_switch_mode_button_visibility()
# Temporarily: due to error in UI label_locator = self.date_input_locator.get_by_label(label)
# label_locator = self.date_input_locator.get_by_label(label) expect(label_locator).to_be_visible()
# expect(label_locator).to_be_visible()
self.date_input_field.check_visibility("Text field for date input is missing") self.date_input_field.check_visibility("Text field for date input is missing")
self.date_input_field.check_empty_input("Text field for date input should be empty") self.date_input_field.check_empty_input("Text field for date input should be empty")
@ -139,21 +135,18 @@ class DateInput(BaseComponent):
self.page.wait_for_timeout(300) self.page.wait_for_timeout(300)
self.date_picker.check_content() self.date_picker.check_content()
self.check_switch_mode_button_tooltip()
self.click_switch_mode_button() self.click_switch_mode_button()
self.check_switch_mode_button_tooltip()
self.click_switch_mode_button() self.click_switch_mode_button()
self.page.wait_for_timeout(1000) self.page.wait_for_timeout(300)
self.input_date("11.11.2011") self.input_date("11.11.2011")
label_locator = self.date_input_locator.get_by_label("Время") label_locator = self.date_input_locator.get_by_label("Время")
expect(label_locator).to_be_visible() expect(label_locator).to_be_visible()
self.time_input_field.check_visibility("Text field for time input is missing") self.time_input_field.check_visibility("Text field for time input is missing")
current_time_value = self.get_time_field_value() current_time_value = self.get_time_field_value()
assert current_time_value == "", \ assert current_time_value == "00:00", \
"Should be empty time input field" "Should be empty time input field"
def check_switch_mode_button_visibility(self) -> None: def check_switch_mode_button_visibility(self) -> None:
@ -161,27 +154,12 @@ class DateInput(BaseComponent):
self.switch_mode_button.check_visibility("Switch Mode Button is missing") self.switch_mode_button.check_visibility("Switch Mode Button is missing")
def check_switch_mode_button_tooltip(self) -> None:
""" Проверка tooltip кнопки переключения режимов ввода."""
text_mode = self.is_text_input_mode()
tooltip_text = self.switch_mode_button.get_tooltip_text()
if text_mode:
assert tooltip_text == "Ручной ввод", \
"Should be 'Ручной ввод' tooltip for switch mode button"
else:
assert tooltip_text == "Выбрать в календаре", \
"Should be 'Выбрать в календаре' tooltip for switch mode button"
def is_text_input_mode(self) -> bool: def is_text_input_mode(self) -> bool:
""" Проверка текстового режима ввода.""" """ Проверка текстового режима ввода."""
result = False result = False
inner_text = self.switch_mode_button.get_text(0).strip() inner_text = self.switch_mode_button.get_text(0).strip()
print(inner_text)
if inner_text == "keyboard": if inner_text == "keyboard":
result = True result = True
return result return result

View File

@ -0,0 +1,263 @@
"""Модуль фрейма создания дочернего элемента."""
import re
from playwright.sync_api import expect, Page
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from components.alert_component import AlertComponent
from components.base_component import BaseComponent
from components.toolbar_component import ToolbarComponent
from components_derived.selection_bar_component import SelectionBarComponent
logger = get_logger("CREATE_CHILD_ELEMENT_FRAME")
class CreateChildElementFrame(BaseComponent):
"""Фрейм создания дочернего элемента."""
def __init__(self, page: Page) -> None:
"""
Инициализирует фрейм создания дочернего элемента.
Args:
page: Экземпляр страницы Playwright
"""
super().__init__(page)
# Инициализация компонентов
self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в")
self.selection_bar = SelectionBarComponent(page, "Класс объекта учета")
self.alert = AlertComponent(page)
# Кнопка "Добавить" - первая кнопка в тулбаре фрейма создания
add_button_locator = self.page.get_by_role("navigation").filter(
has_text="Создать дочерний элемент в"
).get_by_role("button").nth(0)
# Кнопка "Отменить" - используем рабочий локатор из старой версии
cancel_button_locator = self.page.get_by_role("navigation").filter(
has_text=re.compile('Создать дочерний элемент в')
).get_by_role("button").nth(1)
# Инициализация кнопок
self.toolbar.add_tooltip_button(add_button_locator, "add")
self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")
# Действия:
def clear_combobox_field(self, field_name: str) -> None:
"""
Очищает combobox поле по его названию.
Args:
field_name: Название поля для очистки
"""
logger.info(f"Clearing combobox field '{field_name}'...")
# Получаем локатор поля по его названию
field_locator = self._get_field_locator(field_name)
# Используем метод из SelectionBarComponent
self.selection_bar.clear_combobox_field(field_name, field_locator)
def click_add_button(self) -> None:
"""Кликает на кнопку 'Добавить'."""
logger.info("Clicking on 'Add' button...")
self.toolbar.click_button("add")
def click_cancel_button(self) -> None:
"""Кликает на кнопку 'Отменить'."""
logger.info("Clicking on 'Cancel' button...")
self.toolbar.click_button("cancel")
def get_object_class_options(self) -> list[str]:
"""
Получает список доступных опций из combobox.
Returns:
list[str]: Список доступных классов объектов
"""
logger.info("Getting combobox 'Accounting object class' options...")
available_options = self.selection_bar.get_available_options()
logger.info(f"Available object class options: {available_options}")
return available_options
def get_selected_object_class(self) -> str:
"""
Получает выбранный класс объекта учета.
Returns:
str: Выбранный класс объекта или пустая строка если ничего не выбрано
"""
return self.selection_bar.get_selection_bar_title()
def open_object_class_combobox(self) -> None:
"""Открывает выпадающий список combobox 'Класс объекта учета'."""
logger.info("Opening combobox 'Accounting object class'...")
# Ждем стабильности combobox
expect(self.selection_bar.selection_bar_locator).to_be_visible()
# Проверяем, не открыт ли уже выпадающий список
is_menu_active = self.selection_bar.selection_bar_locator.get_attribute(
"class"
)
if is_menu_active and "v-select--is-menu-active" in is_menu_active:
logger.info("Dropdown list is already open")
return
# Используем force click для обхода перекрывающих элементов
logger.info("Using force click for combobox")
self.selection_bar.selection_bar_locator.click(force=True)
# Ждем появления выпадающего списка
self.wait_for_timeout(1500)
def select_object_class(self, class_name: str) -> None:
"""Выбирает класс объекта из выпадающего списка."""
logger.info(f"Selecting object class: '{class_name}'...")
# Открываем combobox
self.open_object_class_combobox()
# Выбираем значение из списка
self.selection_bar.select_value(class_name)
# Даем время на применение выбора
self.wait_for_timeout(3000)
# Логируем текущее состояние без строгой проверки
selected_value = self.get_selected_object_class()
logger.info(f"Current combobox value: '{selected_value}'")
# Временно пропускаем строгую проверку
logger.info(f"Assuming class '{class_name}' is selected")
logger.info(f"Object class '{class_name}' successfully selected")
# Проверки:
def check_field_error_highlighted(self, field_name: str) -> None:
"""
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
Args:
field_name: Название поля для проверки
"""
field_locator = self._get_field_locator(field_name)
self.selection_bar.check_field_error_highlighted(field_name, field_locator)
def check_field_error_not_highlighted(self, field_name: str) -> None:
"""
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args:
field_name: Название поля для проверки
"""
field_locator = self._get_field_locator(field_name)
self.selection_bar.check_field_error_not_highlighted(field_name, field_locator)
def check_object_class_selected(self, expected_class: str) -> None:
"""
Проверяет что выбран указанный класс объекта.
Args:
expected_class: Ожидаемый выбранный класс объекта
"""
logger.info(f"Checking selected object class: '{expected_class}'...")
self.wait_for_timeout(1000)
actual_class = self.get_selected_object_class()
is_match = (expected_class.lower() in actual_class.lower() or
actual_class.lower() in expected_class.lower())
assert is_match, (
f"Selected class does not match expected. "
f"Expected: '{expected_class}', Got: '{actual_class}'"
)
logger.info(
f"Object class '{expected_class}' successfully selected "
f"(actual: '{actual_class}')"
)
def check_toolbar_title(self, expected_title: str) -> None:
"""
Проверяет заголовок тулбара.
Args:
expected_title: Ожидаемый заголовок тулбара
"""
logger.info(f"Checking toolbar title: '{expected_title}'...")
# Используем метод тулбара с фильтрацией по тексту
actual_text = self.toolbar.get_toolbar_title_text(
filter_text="Создать дочерний элемент в"
)
assert expected_title in actual_text, (
f"Title does not match. Expected: '{expected_title}', "
f"Got: '{actual_text}'"
)
logger.info(f"Toolbar title is correct: '{actual_text}'")
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
"""
self.wait_for_timeout(2000)
self.toolbar.check_button_visibility("add")
self.toolbar.check_button_tooltip("add", "Добавить")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.click_button("cancel")
self.wait_for_timeout(2000)
def _get_field_locator(self, field_name: str) -> str:
"""
Возвращает локатор поля по его названию.
Args:
field_name: Название поля
Returns:
str: Локатор поля
"""
field_map = {
"Имя": RackLocators.RACK_NAME_FIELD,
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD,
"Серийный номер": RackLocators.RACK_SERIAL_FIELD,
"Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD,
"Комментарий": RackLocators.RACK_COMMENT_FIELD,
"Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD,
"Состояние": RackLocators.RACK_STATE_FIELD,
"Владелец": RackLocators.RACK_OWNER_FIELD,
"Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD,
"Проект/Титул": RackLocators.RACK_PROJECT_FIELD
}
if field_name not in field_map:
raise ValueError(f"Locator for field '{field_name}' not found")
return field_map[field_name]

View File

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

View File

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

View File

@ -0,0 +1,471 @@
"""Модуль modal_add_user содержит класс для работы с модальным окном добавления пользователя.
Класс AddUserModalWindow наследует базовый функционал ModalWindowComponent
и реализует специфичные методы для работы с формами добавления пользователей.
"""
import re
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 elements.text_element import Text
from elements.checkbox_element import Checkbox
from data.roles_dict import roles_dict
from components.modal_window_component import ModalWindowComponent
from components.dropdown_list_component import DropdownList
from components.confirm_component import ConfirmComponent
logger = get_logger("ADD_USER_FROM_ACTIVE_DIRECTORY_MODAL_WINDOW")
class AddADUserModalWindow(ModalWindowComponent):
"""Модальное окно добавления нового пользователя.
Наследует ModalWindowComponent и добавляет элементы формы:
- Поля ввода (имя, пароль, email и др.)
- Чекбоксы (Active Directory, Push-уведомления)
- Выпадающие списки групп, пользователей AD, ролей
- Кнопки действий
"""
def __init__(self, page: Page):
"""Инициализирует элементы формы добавления пользователя."""
super().__init__(page)
# Локаторы элементов формы
input_form_locator = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
text_field_locator = f"xpath={ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}"
label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA
# Настройка заголовка и кнопки закрытия тулбара
self.window_title = "Добавить нового пользователя"
locator_button_toolbar_close = self.page.get_by_role("navigation").filter(
has_text=re.compile(self.window_title)
).get_by_role("button")
self.add_toolbar_title(self.window_title)
self.add_toolbar_button(locator_button_toolbar_close, "close")
# Добавление элементов формы
checkbox_1 = Checkbox(
page,
input_form_locator.get_by_role("checkbox").nth(0),
"active_directory"
)
self.add_content_item("active_directory_checkbox", checkbox_1)
label_1 = Text(
page,
self.page.locator(label_locator).nth(0),
"active_directory_checkbox_label"
)
self.add_content_item("active_directory_checkbox_label", label_1)
# Начальный набор полей формы
# Поле Группа
group_loc = input_form_locator.get_by_role("combobox").nth(0)
group_input = TextInput(page, group_loc, "group_input")
self.add_content_item("group_input", group_input)
self.add_content_item(
"group_list",
DropdownList(page)
)
locator_button_search = self.page.get_by_role("button", name="Поиск")
self.add_button(locator_button_search, "search")
# Поле Имя
# loc = input_form_locator.locator("xpath=div[2]").locator(text_field_locator)
loc = input_form_locator.locator("xpath=div[3]").locator(text_field_locator)
name_input = TextInput(page, loc, "name_input")
self.add_content_item("name_input", name_input)
# Чекбокс "Блокировка" - индекс 1
checkbox_2 = Checkbox(
page,
input_form_locator.get_by_role("checkbox").nth(1),
"blocking"
)
self.add_content_item("blocking_checkbox", checkbox_2)
# Метка "Блокировка" - индекс 1
label_2 = Text(
page,
self.page.locator(label_locator).nth(1),
"blocking_checkbox_label"
)
self.add_content_item("blocking_checkbox_label", label_2)
# Поле Роль
role_loc = input_form_locator.get_by_role("combobox").nth(1)
role_input = TextInput(page, role_loc, "role_input")
self.add_content_item("role_input", role_input)
self.add_content_item(
"roles_list",
DropdownList(page)
)
# Поле Комментарий
loc = input_form_locator.locator("xpath=div[7]").locator(text_field_locator)
commentary_input = TextInput(page, loc, "commentary_input")
self.add_content_item("commentary_input", commentary_input)
# Поле E-mail
loc = input_form_locator.locator("xpath=div[8]").locator(text_field_locator)
email_input = TextInput(page, loc, "email_input")
self.add_content_item("email_input", email_input)
# Поле Номер для СМС
loc = input_form_locator.locator("xpath=div[9]").locator(text_field_locator)
phone_input = TextInput(page, loc, "phone_input")
self.add_content_item("phone_input", phone_input)
# Чекбокс "Подписка на Push-уведомления" - индекс 2
checkbox_3 = Checkbox(
page,
input_form_locator.get_by_role("checkbox").nth(2),
"push_notification"
)
self.add_content_item("push_notification_checkbox", checkbox_3)
# Метка "Подписка на Push-уведомления" - индекс 2
label_3 = Text(
page,
self.page.locator(label_locator).nth(2),
"push_notification_checkbox_label"
)
self.add_content_item("push_notification_checkbox_label", label_3)
# Добавление кнопок действий
locator_button_add = self.page.get_by_role("button", name="Добавить")
self.add_button(locator_button_add, "add")
locator_button_close = self.page.get_by_role("button", name="Закрыть")
self.add_button(locator_button_close, "close")
self.new_user_confirm = ConfirmComponent(page, " Отмена ", " Добавить ")
# Действия:
def check_active_directory_checkbox(self):
"""Включает чек-бокс Active Directory. """
self.get_content_item("active_directory_checkbox").check(force=True)
def uncheck_active_directory_checkbox(self):
"""Выключает чек-бокс Active Directory. """
self.get_content_item("active_directory_checkbox").uncheck(force=True)
def check_blocking_checkbox(self):
"""Включает чек-бокс Блокировка."""
self.get_content_item("blocking_checkbox").check(force=True)
def uncheck_blocking_checkbox(self):
"""Выключает чек-бокс Блокировка."""
self.get_content_item("blocking_checkbox").uncheck(force=True)
def check_push_notification_checkbox(self):
"""Включает чек-бокс Push-уведомления."""
self.get_content_item("push_notification_checkbox").check(force=True)
def uncheck_push_notification_checkbox(self):
"""Выключает чек-бокс Push-уведомления."""
self.get_content_item("push_notification_checkbox").uncheck(force=True)
def update_input_form_fields(self, expand):
"""Персчитывает локаторы полей формы ввода при добавлении/удалении дополнительного поля. """
input_form_locator = self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
# text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA
text_field_locator = f"xpath={ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}"
if expand:
new_loc = input_form_locator.locator("xpath=div[4]").locator(text_field_locator)
self.get_content_item("name_input").update_locator(new_loc)
new_loc = input_form_locator.locator("xpath=div[8]").locator(text_field_locator)
self.get_content_item("commentary_input").update_locator(new_loc)
new_loc = input_form_locator.locator("xpath=div[9]").locator(text_field_locator)
self.get_content_item("email_input").update_locator(new_loc)
new_loc = input_form_locator.locator("xpath=div[10]").locator(text_field_locator)
self.get_content_item("phone_input").update_locator(new_loc)
role_loc = input_form_locator.get_by_role("combobox").nth(2)
self.get_content_item("role_input").update_locator(role_loc)
else:
new_loc = input_form_locator.locator("xpath=div[3]").locator(text_field_locator)
self.get_content_item("name_input").update_locator(new_loc)
new_loc = input_form_locator.locator("xpath=div[7]").locator(text_field_locator)
self.get_content_item("commentary_input").update_locator(new_loc)
new_loc = input_form_locator.locator("xpath=div[8]").locator(text_field_locator)
self.get_content_item("email_input").update_locator(new_loc)
new_loc = input_form_locator.locator("xpath=div[9]").locator(text_field_locator)
self.get_content_item("phone_input").update_locator(new_loc)
role_loc = input_form_locator.get_by_role("combobox").nth(1)
self.get_content_item("role_input").update_locator(role_loc)
def new_user(self, user_data):
"""Заполняет форму и добавляет нового пользователя.
Args:
user_data (dict): Данные пользователя (имя, роль, пароль и др.)
"""
menu_locator = self.page.locator(ModalWindowLocators.MENU_INPUT_FORM_USER_DATA)
input_form_locator = self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
# Поле "Группа" - выбор из списка
group_name = user_data.get("group")
if group_name is None:
assert False, "Value of 'group' is missing"
# Поле "Пользователи AD" - выбор из списка
name_AD = user_data.get("name_AD")
if name_AD is None:
assert False, "Value of 'name_AD' is missing"
# Поле "Имя" - если определено (не None) вводим вручную
name = user_data.get("name")
# Поле "Роль" - выбор из списка
role = user_data.get("role")
if role is None:
assert False, "Value of 'role' is missing"
# Поиск и выбор заданной группы из списка существующих
group_field = self.get_content_item("group_input")
group_field.click()
group_list = self.get_content_item("group_list")
group_list.scroll_until_end(menu_locator)
group_names = group_list.get_item_names(menu_locator)
if group_name not in group_names:
assert False, f"Required group name {group_name} is missing"
group_list.check_item_with_text(group_name)
group_list.click_item_with_text(group_name)
# Нажатие кнопки "Поиск"
search_button = self.get_button_by_name("search")
search_button.click()
count = input_form_locator.get_by_role("combobox").count()
if count == 2:
assert False, f"Selected group {group_name} is empty. Use another group."
# Если в группе есть пользователи, открывается новое поле, персчет локаторов
self.update_input_form_fields(expand=True)
# Поиск и выбор заданного пользователя AD из списка существующих
user_AD_loc = input_form_locator.get_by_role("combobox").nth(1)
user_AD_input = TextInput(self.page, user_AD_loc, "user_AD_input")
self.add_content_item("user_AD_input", user_AD_input)
self.add_content_item(
"user_AD_list",
DropdownList(self.page)
)
user_AD_input.click()
user_AD_list = self.get_content_item("user_AD_list")
user_AD_list.scroll_until_end(menu_locator)
user_AD_names = group_list.get_item_names(menu_locator)
if name_AD not in user_AD_names:
assert False, f"Required user name {name_AD} is missing"
user_AD_list.check_item_with_text(name_AD)
user_AD_list.click_item_with_text(name_AD)
# Заполнение поля "Имя" (ручной ввод) если задано
if name:
name_field = self.get_content_item("name_input")
name_field.input_value(name)
# Поиск и выбор заданной роли из списка существующих
role_field = self.get_content_item("role_input")
role_field.click()
roles_list = self.get_content_item("roles_list")
roles_list.check_item_with_text(user_data["role"])
roles_list.click_item_with_text(user_data["role"])
if user_data.get("commentary"):
input_field = self.get_content_item("commentary_input")
input_field.input_value(user_data["commentary"])
if user_data.get("email"):
input_field = self.get_content_item("email_input")
input_field.input_value(user_data["email"])
if user_data.get("phone_number"):
input_field = self.get_content_item("phone_input")
input_field.input_value(user_data["phone_number"])
if user_data.get("blocking_checked"):
checkbox = self.get_content_item("blocking_checkbox")
if user_data["blocking_checked"]:
checkbox.check()
else:
checkbox.uncheck()
if user_data.get("push_notification_checked"):
checkbox = self.get_content_item("push_notification_checkbox")
if user_data["push_notification_checked"]:
checkbox.check()
else:
checkbox.uncheck()
# Отправка формы
add_button = self.get_button_by_name("add")
add_button.click()
# Подтверждение действия
title = "Добавить нового пользователя"
self.new_user_confirm.check_title(
title,
f"Confirmation dialog window with title '{title}' is missing"
)
self.new_user_confirm.click_allow_button()
def close_window(self):
"""Закрывает модальное окно через кнопку 'Закрыть'."""
close_button = self.get_button_by_name("close")
close_button.click()
def close_window_by_toolbar_button(self):
"""Закрывает модальное окно через кнопку в тулбаре."""
self.click_toolbar_close_button()
# Проверки:
def check_content(self):
"""Проверяет наличие и корректность всех элементов формы."""
input_form_locator = self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
menu_locator = self.page.locator(ModalWindowLocators.MENU_INPUT_FORM_USER_DATA)
self.check_by_window_title()
is_checked = self.get_content_item("active_directory_checkbox").is_checked()
if not is_checked:
assert False, \
"The checkbox 'Active Directory'should be checked for the add user from Active Directory window"
self.check_toolbar_button_visibility("close")
self.check_toolbar_button_tooltip("close", "Закрыть")
no_op_names = ["roles_list", "group_list"]
for name in self.content_items.keys():
item = self.get_content_item(name)
if name == "active_directory_checkbox_label":
item.check_have_text(
"Active Directory",
"Label 'Active Directory' is missing"
)
elif name == "blocking_checkbox_label":
item.check_have_text(
"Блокировка",
"Label 'Блокировка' is missing"
)
elif name == "push_notification_checkbox_label":
item.check_have_text(
"Подписка на Push-уведомления",
"Label 'Подписка на Push-уведомления' is missing"
)
elif name == "group_input":
item.click()
group_list = self.get_content_item("group_list")
group_list.check_visibility(menu_locator,
"Groups list is missing")
is_scrollable_vertically = group_list.check_vertical_scrolling(menu_locator)
assert is_scrollable_vertically, "Groups list should be scrollable_vertically"
self.page.keyboard.press("Escape")
elif name == "role_input":
item.click()
roles_list = self.get_content_item("roles_list")
roles_list.check_visibility(menu_locator,
"Roles list is missing")
is_scrollable_vertically = roles_list.check_vertical_scrolling(menu_locator)
assert not is_scrollable_vertically, \
"Roles list should not be scrollable_vertically"
for role in roles_dict.values():
# временно, пока есть несоответствие со списком ролей в вкладке Сессии
if role == "Пользователь":
continue
roles_list.check_item_with_text(role)
self.page.keyboard.press("Escape")
elif name in no_op_names:
continue
else:
print(f"check item: {name}")
item.check_visibility(
f"Modal window content item with name '{name}' is missing"
)
# Дополнительная проверка состояния чекбоксов
blocking_checkbox = self.get_content_item("blocking_checkbox")
is_blocking_checked = blocking_checkbox.is_checked()
assert not is_blocking_checked, (
"Checkbox 'Блокировка' should not be checked by default"
)
push_checkbox = self.get_content_item("push_notification_checkbox")
is_push_checked = push_checkbox.is_checked()
assert not is_push_checked, (
"Checkbox 'Подписка на Push-уведомления' should not be checked by default"
)
self.check_button_visibility("search")
self.check_button_visibility("add")
self.check_button_visibility("close")
search_button = self.get_button_by_name("search")
search_button.click()
# Проверка что поле "Пользователи AD" появилось после поиска
user_AD_loc = input_form_locator.get_by_role("combobox").nth(1)
user_AD_input = TextInput(self.page, user_AD_loc, "user_AD_input")
self.add_content_item("user_AD_input", user_AD_input)
self.add_content_item(
"user_AD_list",
DropdownList(self.page)
)
user_AD_input.click()
user_AD_list = self.get_content_item("user_AD_list")
user_AD_list.check_visibility(menu_locator,
"Users AD list is missing")
is_scrollable_vertically = user_AD_list.check_vertical_scrolling(menu_locator)
assert is_scrollable_vertically, "Users AD list should be scrollable_vertically"
self.page.keyboard.press("Escape")
self.update_input_form_fields(expand=True)
self.get_content_item("name_input").check_visibility(
"Modal window content item with name 'name_input' is missing")
self.get_content_item("role_input").check_visibility(
"Modal window content item with name 'role_input' is missing")
self.get_content_item("commentary_input").check_visibility(
"Modal window content item with name 'commentary_input' is missing")
self.get_content_item("email_input").check_visibility(
"Modal window content item with name 'email_input' is missing")
self.get_content_item("phone_input").check_visibility(
"Modal window content item with name 'phone_input' is missing")

View File

@ -12,19 +12,21 @@ from locators.modal_window_locators import ModalWindowLocators
from elements.text_input_element import TextInput from elements.text_input_element import TextInput
from elements.text_element import Text from elements.text_element import Text
from elements.checkbox_element import Checkbox from elements.checkbox_element import Checkbox
from data.roles_dict import roles_dict
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 components_derived.selection_bar_component import SelectionBarComponent
logger = get_logger("ADD_USER_MODAL_WINDOW")
class AddUserModalWindow(ModalWindowComponent): logger = get_logger("ADD_LOCAL_USER_MODAL_WINDOW")
class AddLocalUserModalWindow(ModalWindowComponent):
"""Модальное окно добавления нового пользователя. """Модальное окно добавления нового пользователя.
Наследует ModalWindowComponent и добавляет элементы формы: Наследует ModalWindowComponent и добавляет элементы формы:
- Поля ввода (имя, пароль, email и др.) - Поля ввода (имя, пароль, email и др.)
- Чекбоксы (Блокировка, Push-уведомления) - Чекбоксы (Active Directory, Блокировка, Push-уведомления)
- Выпадающий список ролей - Выпадающий список ролей
- Кнопки действий - Кнопки действий
""" """
@ -35,6 +37,7 @@ class AddUserModalWindow(ModalWindowComponent):
super().__init__(page) super().__init__(page)
# Локаторы элементов формы # Локаторы элементов формы
text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA
input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA
# Настройка заголовка и кнопки закрытия тулбара # Настройка заголовка и кнопки закрытия тулбара
@ -48,20 +51,12 @@ class AddUserModalWindow(ModalWindowComponent):
self.add_toolbar_title(self.window_title) self.add_toolbar_title(self.window_title)
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))
# print(elements_locators)
# Поле Тип авторизации
auth_type_loc = elements_locators.get("Тип авторизации")
if auth_type_loc:
auth_type_selector = SelectionBarComponent(page, auth_type_loc.get_by_role("combobox").first)
self.add_content_item("auth_type_selector", auth_type_selector)
# Поле Имя # Поле Имя
loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) loc = f"{input_form_locator}/div[1]/{text_field_locator}"
name_input = TextInput(page, loc, "name_input") name_input = TextInput(page, self.page.locator(loc), "name_input")
self.add_content_item("name_input", name_input) self.add_content_item("name_input", name_input)
# Метка "Блокировка" # Метка "Блокировка"
label_blocking_locator = self.page.locator(input_form_locator). \ label_blocking_locator = self.page.locator(input_form_locator). \
locator("//label").get_by_text("Блокировка") locator("//label").get_by_text("Блокировка")
@ -76,35 +71,35 @@ class AddUserModalWindow(ModalWindowComponent):
# Чекбокс "Блокировка" # Чекбокс "Блокировка"
checkbox_blocking = Checkbox( checkbox_blocking = Checkbox(
page, page,
page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED), label_blocking_locator.locator("../..").get_by_role("checkbox"),
"blocking" "blocking"
) )
self.add_content_item("blocking_checkbox", checkbox_blocking) self.add_content_item("blocking_checkbox", checkbox_blocking)
# Поле Роль # Поле Роль
role_loc = elements_locators.get("Роль").get_by_role("combobox").first role_loc = self.page.locator(input_form_locator).get_by_role("combobox").nth(0)
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))
# Поле Пароль # Поле Пароль
loc = elements_locators.get("Пароль").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_PASSWORD) loc = f"{input_form_locator}/div[4]/{text_field_locator}"
password_input = TextInput(page, loc, "password_input") password_input = TextInput(page, self.page.locator(loc), "password_input")
self.add_content_item("password_input", password_input) self.add_content_item("password_input", password_input)
# Поле Комментарий # Поле Комментарий
loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT) loc = f"{input_form_locator}/div[5]/{text_field_locator}"
commentary_input = TextInput(page, loc, "commentary_input") commentary_input = TextInput(page, self.page.locator(loc), "commentary_input")
self.add_content_item("commentary_input", commentary_input) self.add_content_item("commentary_input", commentary_input)
# Поле E-mail # Поле E-mail
loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL) loc = f"{input_form_locator}/div[6]/{text_field_locator}"
email_input = TextInput(page, loc, "email_input") email_input = TextInput(page, self.page.locator(loc), "email_input")
self.add_content_item("email_input", email_input) self.add_content_item("email_input", email_input)
# Поле Номер для СМС # Поле Номер для СМС
loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS) loc = f"{input_form_locator}/div[7]/{text_field_locator}"
phone_input = TextInput(page, loc, "phone_input") phone_input = TextInput(page, self.page.locator(loc), "phone_input")
self.add_content_item("phone_input", phone_input) self.add_content_item("phone_input", phone_input)
# Метка "Подписка на Push-уведомления" # Метка "Подписка на Push-уведомления"
@ -117,10 +112,10 @@ class AddUserModalWindow(ModalWindowComponent):
) )
self.add_content_item("push_notification_checkbox_label", label_push) self.add_content_item("push_notification_checkbox_label", label_push)
# Чекбокс "Подписка на Push-уведомления" # Чекбокс "Подписка на Push-уведомления" - индекс 2
checkbox_push = Checkbox( checkbox_push = Checkbox(
page, page,
page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE), label_push_locator.locator("../..").get_by_role("checkbox"),
"push_notification" "push_notification"
) )
self.add_content_item("push_notification_checkbox", checkbox_push) self.add_content_item("push_notification_checkbox", checkbox_push)
@ -156,24 +151,6 @@ class AddUserModalWindow(ModalWindowComponent):
self.get_content_item("push_notification_checkbox").uncheck(force=True) self.get_content_item("push_notification_checkbox").uncheck(force=True)
def get_auth_type(self) -> str | None:
"""Возвращает текущее значение поля 'Тип авторизации'"""
auth_type = None
auth_type_selector = self.get_content_item("auth_type_selector")
if auth_type_selector:
values = auth_type_selector.get_selected_values()
auth_type = values[0]
return auth_type
def select_auth_type(self, auth_type: str) -> None:
"""Выбирает заданное значение поля 'Тип авторизации' из списка"""
auth_type_selector = self.get_content_item("auth_type_selector")
if auth_type_selector:
auth_type_selector.open_values_list()
auth_type_selector.select_value(auth_type)
def new_user(self, user_data): def new_user(self, user_data):
"""Заполняет форму и добавляет нового пользователя. """Заполняет форму и добавляет нового пользователя.
@ -181,86 +158,12 @@ class AddUserModalWindow(ModalWindowComponent):
user_data (dict): Данные пользователя (имя, роль, пароль и др.) user_data (dict): Данные пользователя (имя, роль, пароль и др.)
""" """
auth_type = user_data.get("auth_type")
if auth_type is None:
auth_type = 'local'
current_auth_type = self.get_auth_type()
if current_auth_type != auth_type:
self.select_auth_type(auth_type)
if auth_type == "LDAP":
menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM)
elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Добавилось поле Группа
group_loc = elements_locators.get("Группа").get_by_role("combobox")
group_input = TextInput(self.page, group_loc, "group_input")
self.add_content_item("group_input", group_input)
self.add_content_item("group_list", DropdownList(self.page))
# Добавилась кнопка Поиск
locator_button_search = self.page.get_by_role("button", name="Поиск")
self.add_button(locator_button_search, "search")
# Поиск и выбор заданной группы из списка существующих
group_field = self.get_content_item("group_input")
group_field.click()
group_name = user_data["group"]
group_list = self.get_content_item("group_list")
group_list.scroll_until_end(menu_locator)
group_names = group_list.get_item_names(menu_locator)
if group_name not in group_names:
assert False, f"Required group name {group_name} is missing"
group_list.check_item_with_text(group_name)
group_list.click_item_with_text(group_name)
# Нажатие кнопки "Поиск"
search_button = self.get_button_by_name("search")
search_button.click()
# Если в группе есть пользователи, открывается новое поле, заново вычисляем локаторы
elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
users_ad_loc = elements_locators.get("Пользователи AD")
# users_ad_loc = elements_locators.get("Пользователи LDAP")
assert users_ad_loc, f"Selected group {group_name} is empty. Use another group."
# Поиск и выбор заданного пользователя AD из списка существующих
user_ldap_loc = users_ad_loc.get_by_role("combobox")
user_ldap_input = TextInput(self.page, user_ldap_loc, "user__input")
self.add_content_item("user_ldap_input", user_ldap_input)
self.add_content_item(
"user_ldap_list",
DropdownList(self.page)
)
user_ldap_input.click()
user_ldap_list = self.get_content_item("user_ldap_list")
user_ldap_list.scroll_until_end(menu_locator)
user_ldap_names = group_list.get_item_names(menu_locator)
name_ldap = user_data.get("name_ldap")
if name_ldap not in user_ldap_names:
assert False, f"Required user name {name_ldap} is missing"
user_ldap_list.check_item_with_text(name_ldap)
user_ldap_list.click_item_with_text(name_ldap)
# Заново вычисляем локаторы полей ввода
self.locators_recalculation(is_active_directory=True)
# Заполнение поля "Имя" (ручной ввод) если задано
name = user_data.get("name")
if name:
input_field = self.get_content_item("name_input")
input_field.input_value(name)
fields = user_data.keys() fields = user_data.keys()
if "name" in fields:
input_field = self.get_content_item("name_input")
input_field.input_value(user_data["name"])
if "role" in fields: if "role" in fields:
role_field = self.get_content_item("role_input") role_field = self.get_content_item("role_input")
role_field.click() role_field.click()
@ -322,43 +225,11 @@ class AddUserModalWindow(ModalWindowComponent):
self.click_toolbar_close_button() self.click_toolbar_close_button()
def locators_recalculation(self, is_active_directory=False) -> None:
"""Пересчет локаторов полей ввода"""
elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
new_loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
self.get_content_item("name_input").update_locator(new_loc)
if not is_active_directory:
new_loc = elements_locators.get("Пароль").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_PASSWORD)
self.get_content_item("password_input").update_locator(new_loc)
new_loc = elements_locators.get("Роль").get_by_role("combobox").first
self.get_content_item("role_input").update_locator(new_loc)
new_loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT)
self.get_content_item("commentary_input").update_locator(new_loc)
new_loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL)
self.get_content_item("email_input").update_locator(new_loc)
new_loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS)
self.get_content_item("phone_input").update_locator(new_loc)
# Проверки: # Проверки:
def check_content(self): def check_content(self):
"""Проверяет наличие и корректность всех элементов формы создания локального пользователя. """Проверяет наличие и корректность всех элементов формы."""
Форма для создания keycloack пользователя имеет тот же набор полей.
"""
expected_auth_types = ['local', 'LDAP', 'keycloak'] menu_locator = self.page.locator(ModalWindowLocators.MENU_INPUT_FORM_USER_DATA)
expected_roles = ['$collector', 'Администратор',
'Специалист информационной безопасности',
'Контактное лицо', 'Оператор']
menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM)
self.check_by_window_title() self.check_by_window_title()
@ -381,15 +252,6 @@ class AddUserModalWindow(ModalWindowComponent):
"Подписка на Push-уведомления", "Подписка на Push-уведомления",
"Label 'Подписка на Push-уведомления' is missing" "Label 'Подписка на Push-уведомления' is missing"
) )
elif name == "auth_type_selector":
current_auth_type = self.get_auth_type()
if current_auth_type is None:
continue
assert current_auth_type == 'local', "Default Auth Type value should be 'local'"
actual_auth_types = item.get_available_options()
assert actual_auth_types == 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":
item.click() item.click()
roles_list = self.get_content_item("roles_list") roles_list = self.get_content_item("roles_list")
@ -400,7 +262,10 @@ class AddUserModalWindow(ModalWindowComponent):
"Roles list should not be scrollable_vertically" "Roles list should not be scrollable_vertically"
) )
for role in expected_roles: for role in roles_dict.values():
# временно, пока есть несоответствие со списком ролей в вкладке Сессии
if role == "Пользователь":
continue
roles_list.check_item_with_text(role) roles_list.check_item_with_text(role)
elif name in input_fields: elif name in input_fields:
item.check_editable_input( item.check_editable_input(
@ -409,8 +274,8 @@ class AddUserModalWindow(ModalWindowComponent):
elif name == "roles_list": elif name == "roles_list":
continue continue
else: else:
# print(f"check item: {name}") print(f"check item: {name}")
# print(item) print(item)
item.check_visibility( item.check_visibility(
f"Modal window content item with name '{name}' is missing" f"Modal window content item with name '{name}' is missing"
) )
@ -428,35 +293,5 @@ class AddUserModalWindow(ModalWindowComponent):
"Checkbox 'Подписка на Push-уведомления' should not be checked by default" "Checkbox 'Подписка на Push-уведомления' should not be checked by default"
) )
# Выбор типа авторизации LDAP и проверка появления поля Группа и кнопки Поиск
auth_type_selector = self.get_content_item("auth_type_selector")
if auth_type_selector:
self.select_auth_type("LDAP")
elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Добавилось поле Группа
group_loc = elements_locators.get("Группа").get_by_role("combobox")
group_input = TextInput(self.page, group_loc, "group_input")
self.add_content_item("group_input", group_input)
self.add_content_item("group_list", DropdownList(self.page))
group_field = self.get_content_item("group_input")
group_field.click()
group_list = self.get_content_item("group_list")
group_list.check_visibility(menu_locator,
"Groups list is missing")
is_scrollable_vertically = group_list.check_vertical_scrolling(menu_locator)
assert is_scrollable_vertically, "Groups list should be scrollable_vertically"
self.page.keyboard.press("Escape")
# Добавилась кнопка Поиск
locator_button_search = self.page.get_by_role("button", name="Поиск")
self.add_button(locator_button_search, "search")
self.check_button_visibility("search")
self.check_button_visibility("add") self.check_button_visibility("add")
self.check_button_visibility("close") self.check_button_visibility("close")

View File

@ -38,7 +38,7 @@ class ChangePasswordModalWindow(ModalWindowComponent):
self.add_toolbar_title(f"Изменить пароль для пользователя {user_name}?") self.add_toolbar_title(f"Изменить пароль для пользователя {user_name}?")
# Поля ввода пароля # Поля ввода пароля
loc = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_CURRENT_PASSWORD) loc = modal_window_locator.get_by_label("Введите текущий пароль *")
old_password_input = TextInput(page, loc, "old_password_input") old_password_input = TextInput(page, loc, "old_password_input")
self.add_content_item("old_password_input", old_password_input) self.add_content_item("old_password_input", old_password_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 = modal_window_locator.get_by_label("Введите новый пароль *")
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 = modal_window_locator.get_by_label("Введите повторно новый пароль *")
confirm_password_input = TextInput(page, loc, "confirm_password_input") confirm_password_input = TextInput(page, loc, "confirm_password_input")
self.add_content_item("confirm_password_input", confirm_password_input) self.add_content_item("confirm_password_input", confirm_password_input)
@ -71,10 +71,10 @@ class ChangePasswordModalWindow(ModalWindowComponent):
self.add_content_item("input_form_error_message", input_form_error_message) self.add_content_item("input_form_error_message", input_form_error_message)
# Добавление кнопок действий # Добавление кнопок действий
locator_button_save = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_BUTTON_SAVE) locator_button_save = self.page.get_by_role("button", name="Сохранить")
self.add_button(locator_button_save, "save") self.add_button(locator_button_save, "save")
locator_button_cancel = page.locator(ModalWindowLocators.CHANDE_PASSWORD_WINDOW_BUTTON_CANCEL) locator_button_cancel = self.page.get_by_role("button", name="Отменить")
self.add_button(locator_button_cancel, "cancel") self.add_button(locator_button_cancel, "cancel")
# Alert при успешном добавлении пользователя # Alert при успешном добавлении пользователя

View File

@ -34,7 +34,10 @@ class EditUserModalWindow(ModalWindowComponent):
super().__init__(page) super().__init__(page)
# Локаторы элементов формы # Локаторы элементов формы
# text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA
text_field_locator = f"xpath={ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}"
input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA
label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA
# Настройка заголовка и кнопки закрытия # Настройка заголовка и кнопки закрытия
self.window_title = user_name self.window_title = user_name
@ -48,71 +51,83 @@ class EditUserModalWindow(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(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Поле Имя # Поле Имя
loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) loc = (
self.page.locator(input_form_locator)
.locator("xpath=div[1]")
.locator(text_field_locator)
)
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").nth(0)
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))
# Поле Комментарий # Поле Комментарий
loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT) loc = (
self.page.locator(input_form_locator)
.locator("xpath=div[4]")
.locator(text_field_locator)
)
commentary_input = TextInput(page, loc, "commentary_input") commentary_input = TextInput(page, loc, "commentary_input")
self.add_content_item("commentary_input", commentary_input) self.add_content_item("commentary_input", commentary_input)
# Поле E-mail # Поле E-mail
loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL) loc = (
self.page.locator(input_form_locator)
.locator("xpath=div[5]")
.locator(text_field_locator)
)
email_input = TextInput(page, loc, "email_input") email_input = TextInput(page, loc, "email_input")
self.add_content_item("email_input", email_input) self.add_content_item("email_input", email_input)
# Поле Номер для СМС # Поле Номер для СМС
loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS) loc = (
self.page.locator(input_form_locator)
.locator("xpath=div[6]")
.locator(text_field_locator)
)
phone_input = TextInput(page, loc, "phone_input") phone_input = TextInput(page, loc, "phone_input")
self.add_content_item("phone_input", phone_input) self.add_content_item("phone_input", phone_input)
# Добавление чекбоксов и их меток # Добавление чекбоксов и их меток
# Метка "Блокировка"
label_blocking_locator = self.page.locator(input_form_locator). \
locator("//label").get_by_text("Блокировка")
label_blocking = Text(
page,
label_blocking_locator,
"blocking_checkbox_label"
)
self.add_content_item("blocking_checkbox_label", label_blocking) # Чекбокс "Блокировка" - теперь индекс 0 (т.к. нет Active Directory)
checkbox_1 = Checkbox(
# Чекбокс "Блокировка"
checkbox_blocking = Checkbox(
page, page,
page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED), self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
.get_by_role("checkbox").nth(0),
"blocking" "blocking"
) )
self.add_content_item("blocking_checkbox", checkbox_blocking) self.add_content_item("blocking_checkbox", checkbox_1)
# Метка "Подписка на Push-уведомления" # Метка "Блокировка" - индекс 0
label_push_locator = self.page.locator(input_form_locator). \ label_1 = Text(
locator("//label").get_by_text("Подписка на Push-уведомления")
label_push = Text(
page, page,
label_push_locator, self.page.locator(label_locator).nth(0),
"push_notification_checkbox_label" "blocking_checkbox_label"
) )
self.add_content_item("push_notification_checkbox_label", label_push) self.add_content_item("blocking_checkbox_label", label_1)
# Чекбокс "Подписка на Push-уведомления" # Чекбокс "Подписка на Push-уведомления" - индекс 1
checkbox_push = Checkbox( checkbox_2 = Checkbox(
page, page,
page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE), self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)
.get_by_role("checkbox").nth(1),
"push_notification" "push_notification"
) )
self.add_content_item("push_notification_checkbox", checkbox_push) self.add_content_item("push_notification_checkbox", checkbox_2)
# Метка "Подписка на Push-уведомления" - индекс 1
label_2 = Text(
page,
self.page.locator(label_locator).nth(1),
"push_notification_checkbox_label"
)
self.add_content_item("push_notification_checkbox_label", label_2)
# Добавление кнопок действий # Добавление кнопок действий
locator_button_save = self.page.get_by_role("button", name="Сохранить") locator_button_save = self.page.get_by_role("button", name="Сохранить")
@ -212,16 +227,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()
@ -248,7 +263,7 @@ class EditUserModalWindow(ModalWindowComponent):
role (str): Ожидаемая роль пользователя role (str): Ожидаемая роль пользователя
""" """
menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM) menu_locator = self.page.locator(ModalWindowLocators.MENU_INPUT_FORM_USER_DATA)
self.check_by_window_title() self.check_by_window_title()
self.check_toolbar_button_visibility("close") self.check_toolbar_button_visibility("close")

View File

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

View File

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

View File

@ -1,82 +0,0 @@
"""Модуль modal_view_task содержит класс для работы с модальным окном отображения задачи вкладки
Действия панели событий.
Класс ViewTaskModalWindow наследует базовый функционал ModalWindowComponent
и реализует методы просмотра модального окна отображения задачи.
"""
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.modal_window_locators import ModalWindowLocators
from components.table_component import TableComponent
from components.modal_window_component import ModalWindowComponent
logger = get_logger("VIEW_TASK_MODAL_WINDOW")
class ViewTaskModalWindow(ModalWindowComponent):
"""Модальное окно отображения отображения задачи.
Наследует ModalWindowComponent и добавляет функционал для:
1. Инициализации модального окна
2. Закрытия модального окна через тулбар
3. Проверки содержимого модального окна
"""
def __init__(self, page: Page):
"""Инициализирует элементы формы модального окна отображения задачи."""
super().__init__(page)
self.window_title_locator = page.locator(ModalWindowLocators.TASK_MODAL_WINDOW). \
locator("//div[@class='v-toolbar__title']")
# Настройка кнопки закрытия
button_close_locator = page.locator(ModalWindowLocators.TASK_MODAL_WINDOW).get_by_role("button")
self.add_toolbar_button(button_close_locator, "close")
self.task_stages_table_locator = page.locator(ModalWindowLocators.TASK_MODAL_WINDOW). \
locator("//div[@class='scrolltable']/div/table")
self.task_stages_table = TableComponent(page)
# Действия:
def close(self):
"""Закрывает окно кнопкой на тулбаре."""
self.click_toolbar_close_button()
def get_stages_table_content(self) -> list[list[str]]:
"""Возвращает содержимое таблицы, включая заголовки.
Returns:
Двумерный список с содержимым таблицы.
"""
return self.task_stages_table.read(self.task_stages_table_locator)
def get_window_title(self) -> str:
"""Возвращает заголовок окна"""
return self.toolbar.get_toolbar_title_text(self.window_title_locator)
# Проверки:
def check_content(self) -> None:
"""Проверяет наличие элементов окна.
"""
self.toolbar.check_toolbar_presence_by_locator(self.page.locator(ModalWindowLocators.TASK_MODAL_WINDOW). \
locator("//nav"), "Toolbar is missing")
self.check_toolbar_button_visibility("close")
self.check_toolbar_button_tooltip("close", "Закрыть")
self.task_stages_table.check_visibility(self.task_stages_table_locator, "Task stages table is missing")
def check_stages_table_headers(self, actual_headers, expected_headers) -> None:
""" Проверка соответствия заголовка таблицы ожидаемому"""
self.task_stages_table.check_table_headers(actual_headers, expected_headers)
def check_stages_table_row_highlighting(self, row_index: int) -> None:
"""Проверяет выделение указанной строки таблицы.
"""
self.task_stages_table.check_row_highlighting(self.task_stages_table_locator, row_index)

View File

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

View File

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

View File

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

View File

@ -1,58 +1,59 @@
"""Модуль панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс """Модуль компонента боковой панели формы ввода полей фильтрации отображения данных в панели событий. Содержит класс
для работы с формами ввода, их элементами и проверками.""" для работы с формами ввода, их элементами и проверками."""
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.toolbar_component import ToolbarComponent
from components.base_component import BaseComponent from components.base_component import BaseComponent
from components_derived.selection_bar_component import SelectionBarComponent from components_derived.selection_bar_component import SelectionBarComponent
from components_derived.date_input_component import DateInput from components_derived.date_input_component import DateInput
logger = get_logger("EVENTS_FILTER_PANEL") logger = get_logger("SIDEBAR_FILTER")
class EventsFilterPanel(BaseComponent): class SidebarFilterComponent(BaseComponent):
"""Панель формы ввода полей фильтрации отображения данных в панели событий. Предоставляет методы """Компонент боковой панели формы ввода полей фильтрации отображения данных в панели событий. Предоставляет методы
для взаимодействия с формой, ее содержимым и проверок.""" для взаимодействия с формой, ее содержимым и проверок."""
def __init__(self, page: Page): def __init__(self, page: Page, locator: str | Locator):
"""Инициализирует компонент модального окна формы ввода параметров фильтрации. """Инициализирует компонент боковой панели формы ввода параметров фильтрации.
Args: Args:
page: Экземпляр страницы Playwright page: Экземпляр страницы Playwright
locator: Локатор контейнера (строка или объект Locator) для которого вызывается панель
""" """
super().__init__(page) super().__init__(page)
self.events_filter_locator = self.page.locator("div.menuable__content__active div.scrollarea__body") self.sidebar_locator = self.get_locator(locator)
self.toolbar = ToolbarComponent(page, "Настройки и параметры")
# Поля ввода даты начала и даты окончания события # Поля ввода даты начала и даты окончания события
loc = self.page. \ self.start_time_filter = DateInput(page,
locator("div.menuable__content__active div.scrollarea__body > div:nth-child(1) > div:nth-child(1)") self.sidebar_locator.locator(
self.start_time_filter = DateInput(page, loc) "//div[contains(@class, 'scrollarea__body')]/div").nth(0))
loc = self.page. \ self.finish_time_filter = DateInput(page,
locator("div.menuable__content__active div.scrollarea__body > div:nth-child(1) > div:nth-child(2)") self.sidebar_locator.locator(
self.finish_time_filter = DateInput(page, loc) "//div[contains(@class, 'scrollarea__body')]/div").nth(1))
# Поля задания параметров фильтрации (произвольное количество) # Поля задания параметров фильтрации (произвольное количество)
self.filtering_parameters = {} self.filtering_parameters = {}
# Кнопки задания/сброса параметров фильтрации # Кнопки задания/сброса параметров фильтрации
self.apply_button = Button(page, self.apply_button = Button(page,
self.page.get_by_role("button").filter(has_text='Применить Фильтры'), self.sidebar_locator.get_by_role("button").filter(has_text='Применить Фильтры'),
"apply_button") "apply_button")
self.reset_button = Button(page, self.reset_button = Button(page,
self.page.get_by_role("button").filter(has_text='Сбросить Фильтры'), self.sidebar_locator.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.sidebar_locator.locator("//div[contains(@class, 'scrollarea__body')]").\
loc = self.events_filter_locator.get_by_role("combobox").get_by_placeholder(title) get_by_role("combobox").filter(has_text=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,21 +81,17 @@ 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:
"""Проверяет наличие постоянных полей панели параметров фильтрации.""" """Проверяет наличие постоянных полей панели параметров фильтрации."""
self.should_be_toolbar()
self.start_time_filter.check_content("Дата начала") self.start_time_filter.check_content("Дата начала")
self.finish_time_filter.check_content("Дата окончания") self.finish_time_filter.check_content("Дата окончания")
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 +103,17 @@ 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: def should_be_toolbar(self) -> None:
"""Проверяет наличие поля панели параметров фильтрации по его заголовку.""" """Проверяет наличие тулбара.
loc = self.events_filter_locator.get_by_role("combobox").get_by_placeholder(title) Raises:
expect(loc).to_be_visible(), f"Filtering parameter bar '{title}' is missing" AssertionError: Если тулбар отсутствует.
"""
self.toolbar.check_toolbar_presence_by_locator_and_title(self.sidebar_locator,
"Sidebar Filter form toolbar is missing")

View File

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

View File

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

View File

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

View File

@ -58,11 +58,6 @@ class BaseElement:
logger.info(f"Get text for {self.type_of} '{self.name}'") logger.info(f"Get text for {self.type_of} '{self.name}'")
return self.locator.nth(index).text_content() return self.locator.nth(index).text_content()
def get_locator(self) -> Locator:
"""Возвращает локатор элемента."""
return self.locator
def update_locator(self, new_locator: Locator) -> None: def update_locator(self, new_locator: Locator) -> None:
"""Меняет значение локатора для элемента""" """Меняет значение локатора для элемента"""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,338 +0,0 @@
"""Модуль фрейма создания дочернего элемента."""
import re
from typing import Dict, Any, Optional
from playwright.sync_api import Page, Locator
from tools.logger import get_logger
from locators.rack_locators import RackLocators
from locators.selection_bar_locators import SelectionBarLocators
from components.alert_component import AlertComponent
from components.base_component import BaseComponent
from components.toolbar_component import ToolbarComponent
from components_derived.selection_bar_component import SelectionBarComponent
from forms.create_rack_form import CreateRackForm, CreateRackData
logger = get_logger("CREATE_ELEMENT_FRAME")
logger.setLevel("INFO")
class CreateElementFrame(BaseComponent):
"""Фрейм создания дочернего элемента."""
def __init__(self, page: Page) -> None:
"""
Инициализирует фрейм создания дочернего элемента.
Args:
page (Page): Экземпляр страницы Playwright
"""
super().__init__(page)
# Инициализация формы создания стойки
self.rack_form = CreateRackForm(page)
# Инициализация компонентов
self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в")
self.selection_bar = SelectionBarComponent(page, "Класс объекта учета")
self.alert = AlertComponent(page)
# Кнопка "Добавить" - первая кнопка в тулбаре фрейма создания
add_button_locator = self.page.get_by_role("navigation").filter(
has_text="Создать дочерний элемент в"
).get_by_role("button").nth(0)
# Кнопка "Отменить" - используем рабочий локатор
cancel_button_locator = self.page.get_by_role("navigation").filter(
has_text=re.compile('Создать дочерний элемент в')
).get_by_role("button").nth(1)
# Инициализация кнопок
self.toolbar.add_tooltip_button(add_button_locator, "add")
self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")
# Делегирование методов форме создания стойки
def 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:
"""
Очищает combobox поле по его названию.
Args:
field_name (str): Название поля для очистки
"""
logger.debug(f"Clearing combobox field '{field_name}'...")
# Получаем контейнер формы
container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER).nth(1)
fields_locators = self.get_input_fields_locators(container_locator)
if field_name not in fields_locators:
logger.warning(f"Field '{field_name}' not found in form")
return
field_container = fields_locators[field_name]
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(300)
if not field_container.is_visible():
logger.debug(f"Field '{field_name}' is not visible after scrolling")
return
# Ищем кнопку закрытия (крестик) внутри контейнера поля
close_button = field_container.locator("i.mdi-close").first
if close_button.count() > 0:
close_button.click(force=True)
self.wait_for_timeout(300)
logger.debug(f"Combobox field '{field_name}' cleared using close button")
else:
logger.debug(f"Close button (i.mdi-close) not found for field '{field_name}'")
def click_add_button(self) -> None:
"""Кликает на кнопку 'Добавить'."""
logger.debug("Clicking on 'Add' button...")
self.toolbar.click_button("add")
def click_cancel_button(self) -> None:
"""Кликает на кнопку 'Отменить'."""
logger.debug("Clicking on 'Cancel' button...")
self.toolbar.click_button("cancel")
def get_selected_object_class(self) -> str:
"""
Получает выбранный класс объекта учета.
Returns:
str: Выбранный класс объекта или пустая строка если ничего не выбрано
"""
return self.selection_bar.get_selection_bar_title()
def is_field_filled(self, field_name: str, container_locator: Locator = None) -> bool:
"""
Проверяет, заполнено ли combobox или текстовое поле.
Args:
field_name (str): Название поля для проверки
container_locator (Locator, optional): Локатор контейнера формы
Returns:
bool: True если поле заполнено, False в противном случае
"""
logger.debug(f"Checking if field '{field_name}' is filled...")
if container_locator is None:
container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER).nth(1)
fields_locators = self.get_input_fields_locators(container_locator)
if field_name not in fields_locators:
logger.debug(f"Field '{field_name}' not found in fields_locators")
return False
field_container = fields_locators[field_name]
if not field_container.is_visible():
logger.debug(f"Field '{field_name}' not visible")
return False
selected_chip = field_container.locator(".v-chip").first
field_text = field_container.text_content() or ""
has_text = bool(field_text.strip())
has_chip = selected_chip.count() > 0 and selected_chip.is_visible()
if not has_chip:
input_field = field_container.locator("input").first
if input_field.count() > 0:
input_value = input_field.input_value() or ""
has_input_value = bool(input_value.strip())
logger.debug(f"Field '{field_name}' - has input value: {has_input_value}")
has_text = has_text or has_input_value
logger.debug(f"Field '{field_name}' - has chip: {has_chip}, has text: {has_text}")
return has_chip or has_text
def open_object_class_combobox(self) -> None:
"""Открывает выпадающий список combobox."""
container_locator = self.page.locator(RackLocators.CREATE_RACK_FORM_CONTAINER)
fields_locators = self.get_input_fields_locators(container_locator)
combobox_container = fields_locators.get("Класс объекта учета")
if not combobox_container:
logger.error("Combobox 'Класс объекта учета' not found")
return
menu_selector = "div.v-menu__content.menuable__content__active"
is_menu_open = self.page.locator(menu_selector).count() > 0
if not is_menu_open:
open_button = combobox_container.locator(SelectionBarLocators.OPEN_PARAMETERS_LIST_BUTTON)
open_button.click(force=True, timeout=5000)
else:
logger.debug("Combobox menu is already open")
def select_object_class(self, class_name: str) -> None:
"""
Выбирает класс объекта из выпадающего списка.
Args:
class_name (str): Название класса объекта для выбора
"""
logger.debug(f"Selecting object class: '{class_name}'...")
self.open_object_class_combobox()
self.selection_bar.select_value(class_name)
self.wait_for_timeout(300)
logger.debug(f"Object class '{class_name}' successfully selected")
# Проверки:
def check_field_error_highlighted(self, field_name: str) -> None:
"""
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
Args:
field_name (str): Название поля для проверки
Raises:
ValueError: Если поле не найдено в форме
AssertionError: Если поле не подсвечено ошибкой
"""
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"
)
logger.debug(f"Field '{field_name}' is correctly highlighted with error color")
def check_field_error_not_highlighted(self, field_name: str) -> None:
"""
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args:
field_name (str): Название поля для проверки
Raises:
ValueError: Если поле не найдено в форме
AssertionError: Если поле подсвечено ошибкой
"""
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"
)
logger.debug(f"Field '{field_name}' correctly has no error highlighting")
def check_object_class_selected(self, expected_class: str) -> None:
"""
Проверяет что выбран указанный класс объекта.
Args:
expected_class (str): Ожидаемый выбранный класс объекта
Raises:
AssertionError: Если выбранный класс не соответствует ожидаемому
"""
logger.debug(f"Checking selected object class: '{expected_class}'...")
self.wait_for_timeout(500)
actual_class = self.get_selected_object_class()
is_match = (expected_class.lower() in actual_class.lower() or
actual_class.lower() in expected_class.lower())
assert is_match, (
f"Selected class does not match expected. "
f"Expected: '{expected_class}', Got: '{actual_class}'"
)
logger.debug(f"Object class '{expected_class}' successfully selected (actual: '{actual_class}')")
def check_toolbar_title(self, expected_title: str) -> None:
"""
Проверяет заголовок тулбара.
Args:
expected_title (str): Ожидаемый заголовок тулбара
Raises:
AssertionError: Если заголовок не соответствует ожидаемому
"""
logger.debug(f"Checking toolbar title: '{expected_title}'...")
actual_text = self.toolbar.get_toolbar_title_text(
filter_text="Создать дочерний элемент в"
)
assert expected_title in actual_text, (
f"Title does not match. Expected: '{expected_title}', Got: '{actual_text}'"
)
logger.debug(f"Toolbar title is correct: '{actual_text}'")
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
"""
self.toolbar.check_button_visibility("add")
self.toolbar.check_button_tooltip("add", "Добавить")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.click_button("cancel")
self.wait_for_timeout(500)

View File

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

View File

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

View File

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

View File

@ -15,6 +15,6 @@ 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 = "//div[@class='v-card__title']/h3"
BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']" BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']"
TEXT = f"{CONFIRM}//div[contains(@class, 'v-card__text')]" TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']"

View File

@ -8,54 +8,31 @@ class EventPanelLocators:
"""Локаторы элементов панели событий. """Локаторы элементов панели событий.
Содержит XPath локаторы для: Содержит XPath локаторы для:
AREA_EVENTS (str): рабочей области страницы. TABS_BLOCK (str): блока кнопок в панели событий.
BUTTON_EXPAND_LESS (str): кнопки сжатия рабочей области отображения содержимого панели событий. TAB_EXPAND_BUTTONS (str): блока кнопок расширения панели событий.
BUTTON_EXPAND_MORE (str): кнопки расширения рабочей области отображения содержимого панели событий.
TAB_STATES (str): кнопки Состояния. TAB_STATES (str): кнопки Состояния.
TAB_ACTIONS (str): кнопки Действия. TAB_ACTIONS (str): кнопки Действия.
TAB_EVENTS (str): кнопки События. TAB_EVENTS (str): кнопки События.
TAB_MAINTENANCE (str): кнопки Обслуживания. TAB_MAINTENANCE (str): кнопки Обслуживания.
TAB_SYSTEM_LOG (str): кнопки Системный журнал. TAB_SYSTEM_LOG (str): кнопки Системный журнал.
TAB_AUDIT (str): кнопки Аудит.
BUTTONS_EVENT (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): контейнера для отображения событий аудита. BUTTONS_EVENT (str): блока кнопок-счетчиков событий.
BUTTONS_SERVICE (str): блока кнопок, содержащего кнопки Поиска и Текущего пользователя.
AREA_EVENTS (str): рабочей области страницы.
""" """
AREA_EVENTS = "#app > div.application--wrap > div > div:nth-child(1)" TABS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][1]"
TAB_EXPAND_BUTTONS = f"{TABS_BLOCK}/div[1]/div[1]/div"
TAB_STATES = f"{TABS_BLOCK}//div[@class='v-tabs']//div[@class='v-tabs__container']/div[2]"
TAB_ACTIONS = f"{TABS_BLOCK}//div[@class='v-tabs']//div[@class='v-tabs__container']/div[3]"
TAB_EVENTS = f"{TABS_BLOCK}//div[@class='v-tabs']//div[@class='v-tabs__container']/div[4]"
TAB_MAINTENANCE = f"{TABS_BLOCK}//div[@class='v-tabs']//div[@class='v-tabs__container']/div[5]"
TAB_SYSTEM_LOG = f"{TABS_BLOCK}//div[@class='v-tabs']//div[@class='v-tabs__container']/div[6]"
BUTTON_EXPAND_LESS = "//button[contains(@data-testid, 'BASELINE__btn__toolbar_close')]"
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_ACTIONS = "//div[@data-testid='BASELINE__actions_tab__toolbar']"
TAB_EVENTS = "//div[@data-testid='BASELINE__events_tab__toolbar']"
TAB_MAINTENANCE = "//div[@data-testid='BASELINE__service_tab__toolbar']"
TAB_SYSTEM_LOG = "//div[@data-testid='BASELINE__system journal_tab__toolbar']"
TAB_AUDIT = "//div[@data-testid='BASELINE__audit_tab__toolbar']"
BUTTONS_EVENT = "//button[@data-testid='BASELINE__btn__user']/preceding-sibling::div//span[contains(@class, 'v-tooltip')]"
BUTTON_USER = "//button[@data-testid='BASELINE__btn__user']"
TOOLBAR = "//nav[contains(@class, 'v-toolbar')]"
TABLE = "//div[@class='scrolltable']/div/table"
FILTER_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='Фильтр']"
EVENTS_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='События']"
MAINTENANCE_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='Обслуживание']"
REAL_TIME_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='Реальное время']"
ARCHIVE_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='Архив']"
PDF_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='pdf']"
CSV_TOOLBAR_BUTTON = "//div[contains(@class, 'journal-tab-button')]//span[text()='csv']"
CONTAINER_ACTIONS_TAB = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(2)"
CONTAINER_EVENTS_TAB = "#app > div.application--wrap > div > div:nth-child(3) > div:nth-child(3)"
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)"
BUTTONS_EVENT = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]//span[contains(@class, 'v-tooltip')]"
BUTTONS_SERVICE = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]"
AREA_EVENTS = "#app > div.application--wrap > div > div:nth-child(3)"

View File

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

View File

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

View File

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

View File

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

View File

@ -16,75 +16,67 @@ class RackLocators:
- Контейнеры и структурные элементы - Контейнеры и структурные элементы
""" """
# Основной контейнер вкладок стойки (верхние вкладки) # Основной контейнер вкладок стойки (верхние вкладки)
TABS_CONTAINER = "//div[@data-testid='CABINET_SHOW__tabs' and contains(@class, 'v-tabs')]"
# Все элементы верхних вкладок стойки
ALL_TABS = "//div[@data-testid='CABINET_SHOW__tabs']//a[contains(@class, 'v-tabs__item')]" ALL_TABS = "//div[@data-testid='CABINET_SHOW__tabs']//a[contains(@class, 'v-tabs__item')]"
# Кнопка редактирования свойств стойки
EDIT_BUTTON ="//button[@data-testid='CABINET_SHOW__btn__edit']"
# Кнопка "Скрыть стойку"
HIDE_RACK_BUTTON = "//div[@data-testid='CABINET_SHOW__div__hideCabinet' and contains(@class, 'cabinet_hide_button_trigger_show')]"
# Кнопка "Показать стойку"
SHOW_RACK_BUTTON = "//div[@data-testid='CABINET_SHOW__div__hideCabinet' and contains(@class, 'cabinet_hide_button_trigger_hide')]"
# Универсальный локатор для любой вкладки по имени # Универсальный локатор для любой вкладки по имени
TAB_BY_NAME = ("//div[starts-with(@data-testid, 'CABINET_SHOW__') and " TAB_BY_NAME = "//div[starts-with(@data-testid, 'CABINET_SHOW__') and contains(@class, 'v-tabs__div')]//a[contains(@class, 'v-tabs__item') and .//*[contains(., '{}')]]"
"contains(@class, 'v-tabs__div')]//a[contains(@class, 'v-tabs__item') and "
".//*[contains(., '{}')]]") # Конкретные вкладки по тексту
COMPOSITION_TAB = "//div[@data-testid='CABINET_SHOW__composition_tab']//a[contains(@class, 'v-tabs__item')]"
GENERAL_INFO_TAB = "//div[@data-testid='CABINET_SHOW__main_tab']//a[contains(@class, 'v-tabs__item')]"
MAINTENANCE_TAB = "//div[@data-testid='CABINET_SHOW__service_tab']//a[contains(@class, 'v-tabs__item')]"
EVENTS_TAB = "//div[@data-testid='CABINET_SHOW__events_tab']//a[contains(@class, 'v-tabs__item')]"
SERVICES_TAB = "//div[@data-testid='CABINET_SHOW__services_tab']//a[contains(@class, 'v-tabs__item')]"
# Классы для проверки активности
ACTIVE_TAB_CLASSES = ["accent--text", "v-tabs__item--active"]
# Локатор для активной вкладки # Локатор для активной вкладки
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_CONTAINER = "//div[contains(@class, 'container')]"
# Контейнер формы создания стойки # Локаторы полей формы редактирования стойки
CREATE_RACK_FORM_CONTAINER = "//div[contains(@class, 'flex xs6 pa-0')]" NAME_FIELD = "//input[@aria-label='Имя']"
SERIAL_NUMBER_FIELD = "//input[@aria-label='Серийный номер']"
INVENTORY_NUMBER_FIELD = "//input[@aria-label='Инвентарный номер']"
CABLE_ENTRY_FIELD = "//input[@aria-label='Ввод кабеля']"
STATUS_FIELD = "//input[@aria-label='Состояние']"
HEIGHT_FIELD = "//input[@aria-label='Высота в юнитах']"
OWNER_FIELD = "//input[@aria-label='Владелец']"
SERVICE_ORG_FIELD = "//input[@aria-label='Обслуживающая организация']"
PROJECT_FIELD = "//input[@aria-label='Проект/Титул']"
# Text # Локаторы полей формы создания стойки
CREATE_RACK_FORM_FIELD_NAME = "[data-testid='create-location-bar__text-field__name']" RACK_NAME_FIELD = "//div[contains(@class, 'container')]//label[text()='Имя']/following-sibling::input"
CREATE_RACK_FORM_FIELD_COMMENT = "[data-testid='create-location-bar__text-field__comment']" RACK_HEIGHT_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Высота в юнитах']]"
CREATE_RACK_FORM_FIELD_SERIAL = "[data-testid='create-location-bar__text-field__serial_number']" RACK_DEPTH_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Глубина (мм)']]"
CREATE_RACK_FORM_FIELD_INVENTORY = "[data-testid='create-location-bar__text-field__inventory_number']" RACK_SERIAL_FIELD = "//div[contains(@class, 'container')]//label[text()='Серийный номер']/following-sibling::input"
RACK_INVENTORY_FIELD = "//div[contains(@class, 'container')]//label[text()='Инвентарный номер']/following-sibling::input"
RACK_COMMENT_FIELD = "//div[contains(@class, 'container')]//label[text()='Комментарий']/following-sibling::input"
RACK_CABLE_ENTRY_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Ввод кабеля']]"
RACK_STATE_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot white') and .//label[text()='Состояние']]"
RACK_OWNER_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Владелец']]"
RACK_SERVICE_ORG_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Обслуживающая организация']]"
RACK_PROJECT_FIELD = "//div[contains(@class, 'container')]//div[contains(@class, 'v-input__slot') and .//label[text()='Проект/Титул']]"
# Сombobox # Локатор для родительского контейнера поля ввода
CREATE_RACK_FORM_SELECT_USIZE = "[data-testid='create-location-bar__select__usize']" INPUT_PARENT_CONTAINER = "xpath=./ancestor::div[contains(@class, 'v-input')]"
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']"
# Text
EDIT_RACK_FORM_FIELD_NAME = "[data-testid='cabinet-bar__main__text-field__name']"
EDIT_RACK_FORM_FIELD_COMMENT = "[data-testid='cabinet-bar__main__text-field__comment']"
EDIT_RACK_FORM_FIELD_SERIAL = "[data-testid='cabinet-bar__main__text-field__serial_number']"
EDIT_RACK_FORM_FIELD_INVENTORY = "[data-testid='cabinet-bar__main__text-field__inventory_number']"
EDIT_RACK_FORM_FIELD_POWER = "[data-testid='cabinet-bar__main__text-field__allocated_power']"
# Сombobox
EDIT_RACK_FORM_SELECT_CABLE_INPUT = "[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']"
EDIT_RACK_FORM_SELECT_DEPTH = "[data-testid='cabinet-bar__select_enum__select-field__depth']"
EDIT_RACK_FORM_SELECT_USIZE = "[data-testid='cabinet-bar__select_enum__select-field__usize']"
EDIT_RACK_FORM_SELECT_OWNER = "[data-testid='cabinet-bar__select__select-field__owner']"
EDIT_RACK_FORM_SELECT_SERVICE_PROVIDER = "[data-testid='cabinet-bar__select__select-field__service_provider']"
EDIT_RACK_FORM_SELECT_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']"
EDIT_RACK_FORM_CHECKBOX_VENTILATION_LABEL = "label:has-text('Вентиляционная панель')"
EDIT_RACK_FORM_DATA_CHECKBOX_VENTILATION_CONTAINER = "[data-testid='cabinet-bar__main__checkbox__available_ventilation_panel']"
# ================ ЛОКАТОРЫ ДЛЯ ВЫПАДАЮЩИХ СПИСКОВ ===================
# Локаторы для меню combobox
MENU_ACTIVE_RACK_FORM = "//div[contains(@class, 'menuable__content__active')]"
MENU_ACTIVE_ITEMS = "//div[@role='list']//div[@role='listitem']"
# Локаторы для выпадающего меню (которые использовались в старом коде)
DROPDOWN_LIST = 'div.menuable__content__active div[role="list"]'
DROPDOWN_ITEM_BY_TEXT = ('div.menuable__content__active '
'div[role="listitem"]:has(span:has-text("{}"))')
# CSS селекторы для ошибок валидации # CSS селекторы для ошибок валидации
ERROR_CSS_SELECTORS = ".error--text, .v-input--error" ERROR_CSS_SELECTORS = ".error--text, .v-input--error"
@ -92,8 +84,7 @@ class RackLocators:
# ================ ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОЙКИ =================== # ================ ЛОКАТОРЫ ДЛЯ СТРУКТУРЫ СТОЙКИ ===================
# Общий контейнер стойки (включает кнопки переключения сторон и MAIN_CONTAINER) # Общий контейнер стойки (включает кнопки переключения сторон и MAIN_CONTAINER)
RACK_CONTAINER = ("//div[contains(@class, 'layout active') and " RACK_CONTAINER = "//div[contains(@class, 'layout active') and contains(@class, 'row') and contains(@class, 'shrink')]"
"contains(@class, 'row') and contains(@class, 'shrink')]")
# Основной контейнер стойки (изображение стойки) # Основной контейнер стойки (изображение стойки)
MAIN_CONTAINER = "//div[contains(@class, 'layout cabinet')]" MAIN_CONTAINER = "//div[contains(@class, 'layout cabinet')]"
@ -104,6 +95,10 @@ class RackLocators:
# Локаторы для определения активной стороны # Локаторы для определения активной стороны
ACTIVE_SIDE_BUTTON = "//button[contains(@class, 'primary--text')]" ACTIVE_SIDE_BUTTON = "//button[contains(@class, 'primary--text')]"
INACTIVE_SIDE_BUTTON = "//button[contains(@class, 'secondary--text')]"
# Для получения текста активной стороны
ACTIVE_SIDE_BUTTON_TEXT = "//button[contains(@class, 'primary--text')]//div[contains(@class, 'v-btn__content')]"
# Кнопка добавления (add_circle) # Кнопка добавления (add_circle)
ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]" ADD_CIRCLE_BUTTON = "//i[contains(text(), 'add_circle')]"
@ -112,9 +107,7 @@ class RackLocators:
ALL_UNITS = "//div[contains(@class, 'unit')]" ALL_UNITS = "//div[contains(@class, 'unit')]"
# Позиции юнитов # Позиции юнитов
UNIT_POSITIONS = ("//div[contains(@class, 'headline') and " UNIT_POSITIONS = "//div[contains(@class, 'headline') and contains(@class, 'test-xs-center') and contains(@class, 'unit-positions')]"
"contains(@class, 'test-xs-center') and "
"contains(@class, 'unit-positions')]")
# Локатор для устройств # Локатор для устройств
DEVICE_ELEMENTS = "//div[contains(@class, 'parent-class')]" DEVICE_ELEMENTS = "//div[contains(@class, 'parent-class')]"
@ -122,57 +115,3 @@ class RackLocators:
# Локатор для слотов в устройствах # Локатор для слотов в устройствах
DEVICE_SLOTS = "//div[contains(@class, 'slot')]" DEVICE_SLOTS = "//div[contains(@class, 'slot')]"
# Кнопка редактирования свойств стойки
EDIT_BUTTON = "//button[@data-testid='CABINET_SHOW__btn__edit']"
# Кнопка "Скрыть стойку"
HIDE_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and "
"contains(@class, 'cabinet_hide_button_trigger_show')]")
# Кнопка "Показать стойку"
SHOW_RACK_BUTTON = ("//div[@data-testid='CABINET_SHOW__div__hideCabinet' and "
"contains(@class, 'cabinet_hide_button_trigger_hide')]")
# Кнопки тулбара стойки
TOOLBAR_REPLACE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__replace']"
TOOLBAR_DONE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__done']"
TOOLBAR_CLOSE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__close']"
TOOLBAR_REMOVE_BUTTON = "[data-testid='cabinet-bar__toolbar__btn__remove']"
# Диалог удаления
REMOVE_DIALOG = "[data-testid='cabinet-bar__toolbar__dialog-remove']"
# Кнопки подтверждения удаления
CONFIRM_REMOVE_YES_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__yes']"
CONFIRM_REMOVE_NO_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__no']"
# ================ ЛОКАТОРЫ ДЛЯ ВКЛАДОК в модальном окне редактирования ==
# Локаторы для вкладок в модальном окне редактирования
MODAL_TAB_GENERAL = "[data-testid='cabinet-bar__main_tab']"
MODAL_TAB_IMAGE = "[data-testid='cabinet-bar__photo_tab']"
MODAL_TAB_SETTINGS = "[data-testid='cabinet-bar__settings_tab']"
# ================ ЛОКАТОРЫ ДЛЯ ВКЛАДКИ "Изображение" ===================
IMAGE_UPLOAD_CONTAINER = "div.layout.column.fill-height.justify-center.align-center"
IMAGE_UPLOAD_ICON = "i.mdi-add_photo_alternate"
IMAGE_UPLOAD_INPUT = "input.button-file-upload__input[type='file']"
IMAGE_PREVIEW = "img"
IMAGE_CONTAINER = "div.layout.column.fill-height.justify-center.align-center"
# ================ ЛОКАТОРЫ ДЛЯ ВКЛАДКИ "НАСТРОЙКИ" ===================
# Контейнер вкладки "Настройки"
SETTINGS_CONTAINER = "div.layout.back.fill-height.justify-start"
SETTINGS_ACCESS_MANAGER_TITLE = "div.v-toolbar__title:has-text('Менеджер доступа')"
# Локаторы для полей правил доступа
SETTINGS_READ_RULES = "[data-testid='LOCATION_SETTINGS__select__rules.read']"
SETTINGS_WRITE_RULES = "[data-testid='LOCATION_SETTINGS__select__rules.write']"
SETTINGS_SMS_RULES = "[data-testid='LOCATION_SETTINGS__select__rules.sms']"
SETTINGS_EMAIL_RULES = "[data-testid='LOCATION_SETTINGS__select__rules.email']"
SETTINGS_PUSH_RULES = "[data-testid*='rules.push']"
# Кнопки вкладки "Настройки"
SETTINGS_CANCEL_BUTTON = "[data-testid='LOCATION_SETTINGS__btn__cancel']"

View File

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

View File

@ -18,13 +18,6 @@ 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_FIELD = "div.v-text-field__slot > input"
SETTINGS_FORM_INPUT_VALUE_SUFFIX = ".v-text-field__suffix"
DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]" DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]"
SELECTED_VALUES = "//div[@class='v-select__selections']" SELECTED_VALUES = "//div[@class='v-select__selections']"
CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear" CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear"
PUSH_NOTIFICATIONS_BUTTON_SUBMIT = "//button[@data-testid='PUSH_NOTIFICATIONS__btn__submit']"

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,355 @@
"""Модуль страницы создания дочернего элемента.
Содержит класс для работы с формой создания дочернего элемента.
"""
from playwright.sync_api import Page, expect
from elements.tooltip_button_element import TooltipButton
from components.toolbar_component import ToolbarComponent
from components.dropdown_list_component import DropdownList
from pages.base_page import BasePage
from tools.logger import get_logger
logger = get_logger("CREATE_CHILD_ELEMENT")
# =============== Локаторы ================================================
PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i"
TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']"
CREATE_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button"
PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]"
CREATE_CHILD_TITLE = "//div[contains(@class, 'v-toolbar__title') and contains(., 'Создать дочерний элемент в')]"
OBJECT_CLASS_COMBOBOX = "//div[@role='combobox' and .//label[text()='Класс объекта учета']]"
CANCEL_BUTTON = "//div[contains(@class, 'v-toolbar__title') and contains(., 'Создать дочерний элемент в')]/..//button[contains(@class, 'v-btn--icon')]"
# Локаторы для работы с combobox
COMBOBOX_LABEL = "label"
COMBOBOX_INPUT = "input[name='entity']"
COMBOBOX_ICON = ".v-input__icon--append"
COMBOBOX_ICON_ARROW = ".v-input__icon--append .mdi-menu-down"
# Локаторы для выпадающего списка combobox - уточненные
LISTBOX_SELECTOR = "//div[contains(@class, 'v-menu__content')]//div[@role='list']"
OPTIONS_SELECTOR = "//div[contains(@class, 'v-menu__content')]//div[@role='listitem']//span"
# Локаторы для получения выбранного значения
SELECTED_VALUE_SPAN = "span"
#========================================================================================================
class CreateChildElementTab(BasePage):
"""Класс для работы с формой создания дочернего элемента."""
def __init__(self, page: Page) -> None:
"""
Инициализирует объект формы создания дочернего элемента.
Args:
page: Экземпляр страницы Playwright
"""
super().__init__(page)
# Локаторы для кнопок
panel_header_locator = self.page.locator(PANEL_HEADER)
# Кнопка "Создать" - первая кнопка в тулбаре
create_button_locator = panel_header_locator.locator(CREATE_BUTTON_ANCESTOR_DIV3).nth(0)
# Кнопка "Отменить" - ищем глобально на странице
cancel_button_locator = self.page.locator(CANCEL_BUTTON)
# Инициализация кнопок
self.create_button = TooltipButton(page, create_button_locator, "add")
self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel")
# Инициализация тулбара с обеими кнопками
self.toolbar = ToolbarComponent(page, "")
self.toolbar.add_tooltip_button(create_button_locator, "add")
self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")
# Инициализация компонента выпадающего списка
self.dropdown = DropdownList(page)
def get_toolbar_title(self) -> list[str]:
"""
Получает заголовок панели инструментов.
Returns:
list[str]: Список элементов заголовка панели инструментов
"""
toolbar_title_locator = self.page.locator(PANEL_HEADER).\
locator(PANEL_HEADER_ANCESTOR_DIV2).get_by_role("navigation").\
locator(TOOLBAR_CONTENT)
return self.toolbar.get_toolbar_composite_title_text(toolbar_title_locator)
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если кнопки недоступны или подсказки неверны.
"""
self.wait_for_timeout(2000)
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.get_button_by_name("cancel").click()
self.wait_for_timeout(2000)
def click_create_button(self) -> None:
"""
Кликает на кнопку 'Создать'.
"""
logger.info("Клик на кнопку 'Создать'...")
self.toolbar.get_button_by_name("add").click()
def click_cancel_button(self) -> None:
"""
Кликает на кнопку 'Отменить'.
"""
logger.info("Клик на кнопку 'Отменить'...")
self.toolbar.get_button_by_name("cancel").click()
def check_toolbar_title(self, expected_title: str) -> None:
"""
Проверяет заголовок тулбара.
Args:
expected_title: Ожидаемый заголовок тулбара
Raises:
AssertionError: Если заголовок не соответствует ожидаемому
"""
# Используем метод тулбара с нашим специфичным локатором
self.toolbar.check_toolbar_presence_by_locator(CREATE_CHILD_TITLE,
f"Заголовок тулбара '{expected_title}' не найден")
# Получаем текст и проверяем его
actual_text = self.toolbar.get_toolbar_title_text(CREATE_CHILD_TITLE)
assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'"
logger.info(f"Заголовок тулбара корректен: '{actual_text}'")
def check_object_class_combobox_presence(self) -> None:
"""
Проверяет наличие combobox 'Класс объекта учета'.
Raises:
AssertionError: Если combobox не найден
"""
logger.info("Проверка наличия combobox 'Класс объекта учета'...")
combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX)
expect(combobox_locator).to_be_visible()
logger.info("Combobox 'Класс объекта учета' найден")
def check_object_class_combobox_content(self) -> None:
"""
Проверяет содержимое combobox 'Класс объекта учета'.
Raises:
AssertionError: Если содержимое не соответствует ожидаемому
"""
logger.info("Проверка содержимого combobox 'Класс объекта учета'...")
combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX)
# Проверяем что combobox видим
expect(combobox_locator).to_be_visible()
# Проверяем наличие label
label_locator = combobox_locator.locator(COMBOBOX_LABEL)
expect(label_locator).to_have_text("Класс объекта учета")
# Проверяем наличие input поля
input_locator = combobox_locator.locator(COMBOBOX_INPUT)
expect(input_locator).to_be_visible()
# Для combobox нормально иметь readonly атрибут - это стандартное поведение
# Проверяем что поле доступно для выбора (не disabled)
expect(input_locator).not_to_have_attribute("disabled", "disabled")
# Проверяем наличие иконки стрелки
icon_locator = combobox_locator.locator(COMBOBOX_ICON_ARROW)
expect(icon_locator).to_be_visible()
logger.info("Содержимое combobox 'Класс объекта учета' корректно")
def open_object_class_combobox(self) -> None:
"""
Открывает выпадающий список combobox 'Класс объекта учета'.
"""
logger.info("Открытие combobox 'Класс объекта учета'...")
combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX)
listbox_locator = self.page.locator(LISTBOX_SELECTOR)
icon_locator = combobox_locator.locator(COMBOBOX_ICON)
# Проверяем, не открыт ли уже список
listbox_already_open = False
listbox_count = listbox_locator.count()
if listbox_count > 0:
listbox_already_open = listbox_locator.first.is_visible()
if not listbox_already_open:
# Только если список не открыт, кликаем на иконку
icon_locator.click(timeout=10000)
logger.info("Клик на иконку combobox выполнен")
self.wait_for_timeout(1000)
# Проверяем что список открылся
listbox_count_after = listbox_locator.count()
listbox_visible = False
if listbox_count_after > 0:
listbox_visible = listbox_locator.first.is_visible()
if listbox_visible:
logger.info("Выпадающий список найден и открыт")
else:
logger.warning("Не удалось открыть выпадающий список")
def get_object_class_options(self) -> list[str]:
"""
Получает список доступных опций из combobox.
Returns:
list[str]: Список доступных классов объектов
"""
logger.info("Получение списка опций combobox 'Класс объекта учета'...")
# Открываем combobox (если еще не открыт)
self.open_object_class_combobox()
# Используем метод get_item_names из DropdownList
options_list = self.dropdown.get_item_names(LISTBOX_SELECTOR)
# Закрываем combobox (кликаем вне его)
self.page.mouse.click(10, 10)
self.wait_for_timeout(500)
logger.info(f"Найдено опций: {len(options_list)} - {options_list}")
return options_list
def select_object_class(self, class_name: str) -> None:
"""
Выбирает класс объекта из выпадающего списка.
Args:
class_name: Название класса объекта для выбора
Raises:
AssertionError: Если класс не найден в списке
"""
logger.info(f"Выбор класса объекта: '{class_name}'...")
# Открываем combobox
self.open_object_class_combobox()
self.dropdown.click_item_with_text(class_name)
# Проверяем что выбор произошел
self.wait_for_timeout(1000)
selected_value = self.get_selected_object_class()
if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower():
# Если выбор не произошел, получаем доступные опции для отладки
available_options = self.get_object_class_options()
logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}")
raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'")
logger.info(f"Класс объекта '{class_name}' успешно выбран")
def get_selected_object_class(self) -> str:
"""
Получает выбранный класс объекта учета.
Returns:
str: Выбранный класс объекта или пустая строка если ничего не выбрано
"""
combobox_locator = self.page.locator(OBJECT_CLASS_COMBOBOX)
selected_value = ""
# Ищем в span элементах
span_locator = combobox_locator.locator(SELECTED_VALUE_SPAN)
if span_locator.count() > 0:
for i in range(span_locator.count()):
span_text = span_locator.nth(i).text_content().strip()
if span_text and span_text not in ["Класс объекта учета"]:
selected_value = span_text
break
logger.info(f"Выбранный класс объекта: '{selected_value}'")
return selected_value
def check_object_class_selected(self, expected_class: str) -> None:
"""
Проверяет что выбран указанный класс объекта.
Args:
expected_class: Ожидаемый выбранный класс объекта
Raises:
AssertionError: Если выбранный класс не соответствует ожидаемому
"""
logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...")
# Даем время на обновление значения
self.wait_for_timeout(1000)
actual_class = self.get_selected_object_class()
# Проверка - допускаем частичное совпадение
if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower():
logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')")
else:
raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'")
def check_object_class_options_content(self, expected_options: list = None) -> None:
"""
Проверяет содержимое списка опций combobox.
Args:
expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой.
Raises:
AssertionError: Если список опций не соответствует ожидаемому
"""
logger.info("Проверка содержимого списка опций combobox...")
# Получаем доступные опции
available_options = self.get_object_class_options()
if expected_options is not None:
# Проверяем соответствие ожидаемому списку
assert set(available_options) == set(expected_options), (
f"Список опций не соответствует ожидаемому. "
f"Ожидалось: {expected_options}, Получено: {available_options}"
)
else:
# Проверяем что список не пустой
assert len(available_options) > 0, "Список опций combobox пустой"
logger.info(f"Содержимое списка опций корректно: {available_options}")
def check_dropdown_item_presence(self, item_text: str) -> None:
"""
Проверяет наличие элемента в выпадающем списке.
Args:
item_text: Текст элемента для проверки
"""
logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...")
# Получаем все опции и проверяем наличие
available_options = self.get_object_class_options()
if item_text not in available_options:
raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}")
logger.info(f"Элемент '{item_text}' присутствует в списке")

View File

@ -0,0 +1,678 @@
"""Модуль страницы создания дочернего элемента.
Содержит класс для работы с формой создания дочернего элемента.
"""
import re
from playwright.sync_api import Page, expect
from elements.tooltip_button_element import TooltipButton
from components.toolbar_component import ToolbarComponent
from components_derived.selection_bar_component import SelectionBarComponent
from pages.main_page import MainPage
from pages.base_page import BasePage
from components.base_component import BaseComponent
from components.alert_component import AlertComponent
from components.navbar_component import NavigationPanelComponent
from locators.navigation_panel_locators import NavigationPanelLocators
from locators.combobox_locators import ComboboxLocators
from locators.rack_locators import RackLocators
from locators.alert_locators import AlertLocators
from tools.logger import get_logger
logger = get_logger("CREATE_RACK_ELEMENT")
# Словарь для сопоставления названий полей с локаторами
COMBOBOX_FIELDS_MAP = {
# Обязательные поля
"Имя": RackLocators.RACK_NAME_FIELD,
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD,
# Дополнительные текстовые поля
"Серийный номер": RackLocators.RACK_SERIAL_FIELD,
"Инвентарный номер": RackLocators.RACK_INVENTORY_FIELD,
"Комментарий": RackLocators.RACK_COMMENT_FIELD,
# Combobox поля
"Ввод кабеля": RackLocators.RACK_CABLE_ENTRY_FIELD,
"Состояние": RackLocators.RACK_STATE_FIELD,
"Владелец": RackLocators.RACK_OWNER_FIELD,
"Обслуживающая организация": RackLocators.RACK_SERVICE_ORG_FIELD,
"Проект/Титул": RackLocators.RACK_PROJECT_FIELD
}
class CreateRackElementTab(BasePage):
"""Класс для работы с формой создания дочернего элемента."""
def __init__(self, page: Page) -> None:
"""
Инициализирует объект формы создания дочернего элемента.
Args:
page: Экземпляр страницы Playwright
"""
super().__init__(page)
# Инициализация BaseComponent
self.base_component = BaseComponent(page)
# Инициализация AlertComponent
self.alert = AlertComponent(page)
# Инициализация MainPage для работы с навигацией
self.main_page = MainPage(page)
# Инициализация NavigationPanelComponent
self.navigation_panel = NavigationPanelComponent(page)
# Кнопка "Добавить" - первая кнопка в тулбаре
create_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(0)
# Кнопка "Отменить" - вторая кнопка в тулбаре
cancel_button_locator = self.page.get_by_role("navigation").filter(has_text=re.compile('Создать дочерний элемент в')).get_by_role("button").nth(1)
# Инициализация кнопок
self.create_button = TooltipButton(page, create_button_locator, "add")
self.cancel_button = TooltipButton(page, cancel_button_locator, "cancel")
# Инициализация тулбара с обеими кнопками
self.toolbar = ToolbarComponent(page, "Создать дочерний элемент в")
self.toolbar.add_tooltip_button(create_button_locator, "add")
self.toolbar.add_tooltip_button(cancel_button_locator, "cancel")
# Инициализация компонента панели выбора значения для работы с combobox
self.selection_bar = SelectionBarComponent(page, ComboboxLocators.OBJECT_CLASS_COMBOBOX)
# =============== МЕТОДЫ ДЕЙСТВИЙ ========================
def click_add_button(self) -> None:
"""
Кликает на кнопку 'Добавить'.
"""
self.toolbar.click_button("add")
def click_cancel_button(self) -> None:
"""
Кликает на кнопку 'Отменить'.
"""
self.toolbar.click_button("cancel")
def open_object_class_combobox(self) -> None:
"""
Открывает выпадающий список combobox 'Класс объекта учета'.
"""
logger.info("Открытие combobox 'Класс объекта учета'...")
self.selection_bar.open_values_list()
def select_object_class(self, class_name: str) -> None:
"""
Выбирает класс объекта из выпадающего списка.
Args:
class_name: Название класса объекта для выбора
Raises:
AssertionError: Если класс не найден в списке
"""
logger.info(f"Выбор класса объекта: '{class_name}'...")
# Открываем combobox
self.open_object_class_combobox()
# Выбираем значение из списка
self.selection_bar.select_value(class_name)
# Проверяем что выбор произошел
self.wait_for_timeout(1000)
selected_value = self.get_selected_object_class()
if class_name.lower() not in selected_value.lower() and selected_value.lower() not in class_name.lower():
# Если выбор не произошел, получаем доступные опции для отладки
available_options = self.get_object_class_options()
logger.warning(f"Класс '{class_name}' не выбран. Текущее значение: '{selected_value}'. Доступные опции: {available_options}")
raise AssertionError(f"Не удалось выбрать класс объекта '{class_name}'")
logger.info(f"Класс объекта '{class_name}' успешно выбран")
def get_object_class_options(self) -> list[str]:
"""
Получает список доступных опций из combobox.
Returns:
list[str]: Список доступных классов объектов
"""
logger.info("Получение списка опций combobox 'Класс объекта учета'...")
available_options = self.selection_bar.get_available_options()
logger.info(f"Доступные опции класса объекта: {available_options}")
return available_options
def get_selected_object_class(self) -> str:
"""
Получает выбранный класс объекта учета.
Returns:
str: Выбранный класс объекта или пустая строка если ничего не выбрано
"""
# Получаем заголовок панели выбора
return self.selection_bar.get_selection_bar_title()
def fill_rack_data(self, name: str, height: str = "42", depth: str = "1000",
serial: str = "", inventory: str = "", comment: str = "",
cable_entry: str = "", state: str = "", owner: str = "",
service_org: str = "", project: str = "") -> None:
"""
Заполняет данные для создания стойки.
Args:
name: Наименование стойки
height: Высота в юнитах (по умолчанию 42)
depth: Глубина в мм (по умолчанию 1000)
serial: Серийный номер
inventory: Инвентарный номер
comment: Комментарий
cable_entry: Ввод кабеля
state: Состояние
owner: Владелец
service_org: Обслуживающая организация
project: Проект/Титул
"""
logger.info(f"Заполнение данных стойки: {name}")
# Заполняем обязательные поля
name_field = self.page.locator(RackLocators.RACK_NAME_FIELD)
name_field.fill(name)
logger.info(f"Заполнено поле 'Имя': {name}")
self._select_combobox("Высота в юнитах", height)
logger.info(f"Выбрана высота: {height} юнитов")
self._select_combobox("Глубина (мм)", depth)
logger.info(f"Выбрана глубина: {depth} мм")
# Заполняем опциональные поля
if serial:
serial_field = self.page.locator(RackLocators.RACK_SERIAL_FIELD)
serial_field.fill(serial)
logger.info(f"Заполнен серийный номер: {serial}")
if inventory:
inventory_field = self.page.locator(RackLocators.RACK_INVENTORY_FIELD)
inventory_field.fill(inventory)
logger.info(f"Заполнен инвентарный номер: {inventory}")
if comment:
comment_field = self.page.locator(RackLocators.RACK_COMMENT_FIELD)
comment_field.fill(comment)
logger.info(f"Добавлен комментарий: {comment}")
# Заполняем дополнительные combobox поля
if cable_entry:
self._select_combobox("Ввод кабеля", cable_entry)
logger.info(f"Выбран ввод кабеля: {cable_entry}")
if state:
self._select_combobox("Состояние", state)
logger.info(f"Выбрано состояние: {state}")
if owner:
self._select_combobox("Владелец", owner)
logger.info(f"Выбран владелец: {owner}")
if service_org:
self._select_combobox("Обслуживающая организация", service_org)
logger.info(f"Выбрана обслуживающая организация: {service_org}")
if project:
self._select_combobox("Проект/Титул", project)
logger.info(f"Выбран проект/титул: {project}")
logger.info("Данные стойки заполнены")
def _select_combobox(self, field_name: str, value: str) -> None:
"""
Выбор значения в combobox.
Args:
field_name: Название поля
value: Значение для выбора
"""
logger.info(f"Выбор '{value}' в поле '{field_name}'...")
# Получаем статический локатор из словаря
if field_name not in COMBOBOX_FIELDS_MAP:
raise ValueError(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP")
field_locator = COMBOBOX_FIELDS_MAP[field_name]
# Для всех полей используем first() чтобы избежать strict mode violation
field_container = self.page.locator(field_locator).first
# Прокручиваем до поля
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(500)
# Проверяем видимость поля
self.base_component.check_visibility(field_container, f"Поле '{field_name}' не найдено")
# Универсальный клик с force=True для всех полей
field_container.click(force=True)
self.wait_for_timeout(1000)
# Вводим значение
self.page.keyboard.type(value)
self.wait_for_timeout(500)
self.page.keyboard.press("Enter")
logger.info(f"Поле '{field_name}' заполнено")
def create_rack(self, rack_name: str, **kwargs) -> None:
"""
Полный процесс создания стойки.
Args:
rack_name: Наименование стойки
**kwargs: Дополнительные параметры стойки
"""
logger.info(f"Начало процесса создания стойки: {rack_name}")
# Выбираем класс объекта "Стойка"
self.select_object_class("Стойка")
self.wait_for_timeout(1000)
# Проверяем наличие полей стойки
self.check_rack_fields_presence()
# Заполняем данные
self.fill_rack_data(rack_name, **kwargs)
# Создаем стойку
self.click_add_button()
logger.info(f"Процесс создания стойки '{rack_name}' завершен")
def clear_combobox_field(self, field_name: str) -> None:
"""
Очищает значение в combobox поле с помощью кнопки закрытия (крестика).
Args:
field_name: Название поля для очистки
"""
logger.info(f"Очистка combobox поля '{field_name}' с помощью кнопки закрытия...")
if field_name not in COMBOBOX_FIELDS_MAP:
logger.warning(f"Локатор для поля '{field_name}' не найден в COMBOBOX_FIELDS_MAP")
return
field_locator = COMBOBOX_FIELDS_MAP[field_name]
# Находим поле по локатору
field_container = self.page.locator(field_locator).first
# Проверяем что поле видимо
if not field_container.is_visible():
logger.info(f"Поле '{field_name}' не видимо, пропускаем очистку")
return
# Прокручиваем до поля
field_container.scroll_into_view_if_needed()
self.wait_for_timeout(500)
# Ищем кнопку закрытия (крестик) внутри контейнера поля
close_button = field_container.locator(ComboboxLocators.COMBOBOX_CLOSE_BUTTON)
# Проверяем наличие и видимость кнопки закрытия
if close_button.count() > 0 and close_button.is_visible():
# Если кнопка закрытия видима - кликаем на нее
close_button.click()
self.wait_for_timeout(500)
logger.info(f"Combobox поле '{field_name}' очищено с помощью кнопки закрытия")
else:
# Если кнопки закрытия нет, просто логируем этот факт
logger.info(f"Кнопка закрытия не найдена для поля '{field_name}', очистка не выполнена")
def clear_rack_fields(self) -> None:
"""
Очищает все поля формы создания стойки.
"""
logger.info("Очистка всех полей формы стойки...")
# Очищаем текстовые поля
text_fields = [
(RackLocators.RACK_NAME_FIELD, "Имя"),
(RackLocators.RACK_SERIAL_FIELD, "Серийный номер"),
(RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"),
(RackLocators.RACK_COMMENT_FIELD, "Комментарий")
]
for field_locator, field_name in text_fields:
field = self.page.locator(field_locator)
if field.count() > 0 and field.first.is_visible():
field.fill("")
logger.info(f"Текстовое поле '{field_name}' очищено")
# Очищаем combobox поля
combobox_fields = [
"Высота в юнитах",
"Глубина (мм)",
"Ввод кабеля",
"Состояние",
"Владелец",
"Обслуживающая организация",
"Проект/Титул"
]
for field_name in combobox_fields:
self.clear_combobox_field(field_name)
logger.info("Все поля формы стойки очищены")
# =============== МЕТОДЫ ПРОВЕРОК ========================
def check_rack_exists(self, rack_name: str) -> bool:
"""
Проверяет, существует ли уже стойка с указанным именем в навигационной панели.
Args:
rack_name: Имя стойки для проверки
Returns:
bool: True если стойка существует, False если нет
"""
logger.info(f"Проверка существования стойки с именем '{rack_name}'")
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.click_main_navigation_panel_item("Объекты")
self.wait_for_timeout(1000)
self.main_page.click_subpanel_item("test-zone")
self.wait_for_timeout(3000)
nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента через is_visible
element = self.page.locator(nav_panel_locator).get_by_text(rack_name).first
if element.is_visible():
logger.info(f"Стойка с именем '{rack_name}' найдена")
return True
else:
logger.info(f"Стойки с именем '{rack_name}' не найдена")
return False
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если кнопки недоступны или подсказки неверны.
"""
self.wait_for_timeout(2000)
self.toolbar.check_button_visibility("add")
self.toolbar.check_button_tooltip("add", "Добавить")
self.toolbar.check_button_visibility("cancel")
self.toolbar.check_button_tooltip("cancel", "Отменить")
self.toolbar.click_button("cancel")
self.wait_for_timeout(2000)
def check_toolbar_title(self, expected_title: str) -> None:
"""
Проверяет заголовок тулбара.
Args:
expected_title: Ожидаемый заголовок тулбара
Raises:
AssertionError: Если заголовок не соответствует ожидаемому
"""
logger.info(f"Проверка заголовок тулбара: '{expected_title}'...")
# Используем метод тулбара с фильтрацией по тексту
actual_text = self.toolbar.get_toolbar_title_text(
filter_text="Создать дочерний элемент в"
)
assert expected_title in actual_text, f"Заголовок не совпадает. Ожидалось: '{expected_title}', Получено: '{actual_text}'"
logger.info(f"Заголовок тулбара корректен: '{actual_text}'")
def check_object_class_combobox_presence(self) -> None:
"""
Проверяет наличие combobox 'Класс объекта учета'.
Raises:
AssertionError: Если combobox не найден
"""
logger.info("Проверка наличия combobox 'Класс объекта учета'...")
self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не найден")
logger.info("Combobox 'Класс объекта учета' найден")
def check_object_class_combobox_content(self) -> None:
"""
Проверяет содержимое combobox 'Класс объекта учета'.
Raises:
AssertionError: Если содержимое не соответствует ожидаемому
"""
logger.info("Проверка содержимого combobox 'Класс объекта учета'...")
combobox_locator = self.page.locator(ComboboxLocators.OBJECT_CLASS_COMBOBOX)
# Проверяем что combobox видим
self.base_component.check_visibility(ComboboxLocators.OBJECT_CLASS_COMBOBOX, "Combobox 'Класс объекта учета' не виден")
# Проверяем наличие label
label_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_LABEL)
expect(label_locator).to_have_text("Класс объекта учета")
# Проверяем наличие input поля
input_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_INPUT)
self.base_component.check_visibility(input_locator, "Input поле combobox не найдено")
# Для combobox нормально иметь readonly атрибут - это стандартное поведение
# Проверяем что поле доступно для выбора (не disabled)
expect(input_locator).not_to_have_attribute("disabled", "disabled")
# Проверяем наличие иконки стрелки
icon_locator = combobox_locator.locator(ComboboxLocators.COMBOBOX_ICON_ARROW)
self.base_component.check_visibility(icon_locator, "Иконка стрелки combobox не найдена")
logger.info("Содержимое combobox 'Класс объекта учета' корректно")
def check_object_class_selected(self, expected_class: str) -> None:
"""
Проверяет что выбран указанный класс объекта.
Args:
expected_class: Ожидаемый выбранный класс объекта
Raises:
AssertionError: Если выбранный класс не соответствует ожидаемому
"""
logger.info(f"Проверка выбранного класса объекта: '{expected_class}'...")
# Даем время на обновление значения
self.wait_for_timeout(1000)
actual_class = self.get_selected_object_class()
# Проверка - допускаем частичное совпадение
if expected_class.lower() in actual_class.lower() or actual_class.lower() in expected_class.lower():
logger.info(f"Класс объекта '{expected_class}' успешно выбран (фактически: '{actual_class}')")
else:
raise AssertionError(f"Выбранный класс не соответствует ожидаемому. Ожидалось: '{expected_class}', Получено: '{actual_class}'")
def check_object_class_options_content(self, expected_options: list = None) -> None:
"""
Проверяет содержимое списка опций combobox.
Args:
expected_options: Ожидаемый список опций. Если None, проверяет только что список не пустой.
Raises:
AssertionError: Если список опций не соответствует ожидаемому
"""
logger.info("Проверка содержимого списка опций combobox...")
# Получаем доступные опции
available_options = self.get_object_class_options()
if expected_options is not None:
# Проверяем соответствие ожидаемому списку
assert set(available_options) == set(expected_options), (
f"Список опций не соответствует ожидаемому. "
f"Ожидалось: {expected_options}, Получено: {available_options}"
)
else:
# Проверяем что список не пустой
assert len(available_options) > 0, "Список опций combobox пустой"
logger.info(f"Содержимое списка опций корректно: {available_options}")
def check_dropdown_item_presence(self, item_text: str) -> None:
"""
Проверяет наличие элемента в выпадающем списке.
Args:
item_text: Текст элемента для проверки
"""
logger.info(f"Проверка наличия элемента '{item_text}' в выпадающем списке...")
# Получаем все опции и проверяем наличие
available_options = self.get_object_class_options()
if item_text not in available_options:
raise AssertionError(f"Элемент '{item_text}' не найден в списке опций. Доступные опции: {available_options}")
logger.info(f"Элемент '{item_text}' присутствует в списке")
def check_rack_fields_presence(self) -> None:
"""
Проверяет наличие полей специфичных для стойки.
Raises:
AssertionError: Если какое-либо поле не найдено
"""
logger.info("Проверка наличия полей для стойки...")
# Основные обязательные поля
required_fields = [
(RackLocators.RACK_NAME_FIELD, "Имя"),
(RackLocators.RACK_HEIGHT_FIELD, "Высота в юнитах"),
(RackLocators.RACK_DEPTH_FIELD, "Глубина (мм)")
]
# Дополнительные поля
optional_fields = [
(RackLocators.RACK_SERIAL_FIELD, "Серийный номер"),
(RackLocators.RACK_INVENTORY_FIELD, "Инвентарный номер"),
(RackLocators.RACK_COMMENT_FIELD, "Комментарий"),
(RackLocators.RACK_CABLE_ENTRY_FIELD, "Ввод кабеля"),
(RackLocators.RACK_STATE_FIELD, "Состояние"),
(RackLocators.RACK_OWNER_FIELD, "Владелец"),
(RackLocators.RACK_SERVICE_ORG_FIELD, "Обслуживающая организация"),
(RackLocators.RACK_PROJECT_FIELD, "Проект/Титул")
]
# Проверяем обязательные поля
for field_locator, field_name in required_fields:
self.base_component.check_visibility(field_locator, f"Обязательное поле '{field_name}' не найдено")
logger.info(f"Обязательное поле '{field_name}' найдено")
# Проверяем дополнительные поля
for field_locator, field_name in optional_fields:
field = self.page.locator(field_locator)
if field.count() > 0 and field.first.is_visible():
logger.info(f"Дополнительное поле '{field_name}' найдено")
else:
logger.info(f"Дополнительное поле '{field_name}' не найдено или не отображается")
logger.info("Все основные поля для стойки присутствуют")
def check_field_highlighted_error(self, field_name: str) -> None:
"""
Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
Args:
field_name: Название поля для проверки
"""
logger.info(f"Проверка подсветки поля '{field_name}' цветом ошибки...")
# Локаторы только для обязательных полей
required_fields = {
"Имя": RackLocators.RACK_NAME_FIELD,
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
}
if field_name not in required_fields:
raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается")
field_locator = required_fields[field_name]
field_element = self.page.locator(field_locator)
# Проверяем что поле видимо
self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено")
# Ищем родительский контейнер с использованием константы
parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first
# Проверка классов ошибки
if parent_container.count() > 0:
error_classes = AlertLocators.ERROR_CLASSES
is_error_highlighted = False
for error_class in error_classes:
error_element = parent_container.locator(f".{error_class}")
if error_element.count() > 0:
is_error_highlighted = True
logger.info(f"Поле '{field_name}' подсвечено ошибкой")
break
if not is_error_highlighted:
raise AssertionError(f"Поле '{field_name}' не подсвечено цветом ошибки ")
logger.info(f"Поле '{field_name}' корректно подсвечено цветом ошибки")
def check_field_not_highlighted_error(self, field_name: str) -> None:
"""
Проверяет, что поле НЕ подсвечено цветом ошибки (валидация успешна).
Args:
field_name: Название поля для проверки
"""
logger.info(f"Проверка отсутствия подсветки ошибки у поля '{field_name}'...")
# Локаторы только для обязательных полей
required_fields = {
"Имя": RackLocators.RACK_NAME_FIELD,
"Высота в юнитах": RackLocators.RACK_HEIGHT_FIELD,
"Глубина (мм)": RackLocators.RACK_DEPTH_FIELD
}
if field_name not in required_fields:
raise ValueError(f"Поле '{field_name}' не является обязательным или не поддерживается")
field_locator = required_fields[field_name]
field_element = self.page.locator(field_locator)
# Проверяем что поле видимо
self.base_component.check_visibility(field_element, f"Поле '{field_name}' не найдено")
# Ищем родительский контейнер с использованием константы
parent_container = field_element.locator(RackLocators.INPUT_PARENT_CONTAINER).first
# Поверка отсутствия классов ошибки
if parent_container.count() > 0:
error_classes = AlertLocators.ERROR_CLASSES
for error_class in error_classes:
error_element = parent_container.locator(f".{error_class}")
if error_element.count() > 0:
raise AssertionError(f"Поле '{field_name}' подсвечено ошибкой")
logger.info(f"Поле '{field_name}' корректно не подсвечено цветом ошибки")

View File

@ -218,7 +218,7 @@ class CurrentSessionsTab(BasePage):
expected_headers = [ expected_headers = [
'ID сеанса', 'ID сеанса',
'ID пользователя', 'ID пользователя',
'Время сеанса', 'Время жизни',
'Роль', 'Роль',
'Адрес' 'Адрес'
] ]

View File

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

View File

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

View File

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

View File

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

View File

@ -6,10 +6,6 @@
from playwright.sync_api import Page from playwright.sync_api import Page
from locators.navigation_panel_locators import NavigationPanelLocators from locators.navigation_panel_locators import NavigationPanelLocators
from components_derived.container_actions_events import ActionsEventsContainer
from components_derived.container_audit_events import AuditEventsContainer
from components_derived.container_events import EventsTabContainer
from components_derived.container_maintenance_events import MaintenanceEventsContainer
from components_derived.container_system_log_events import SystemLogEventsContainer from components_derived.container_system_log_events import SystemLogEventsContainer
from components_derived.user_card import UserCard from components_derived.user_card import UserCard
from components.eventbar_component import EventPanelComponent from components.eventbar_component import EventPanelComponent
@ -49,26 +45,6 @@ class MainPage(BasePage):
self.event_panel.click_expand_more_button() self.event_panel.click_expand_more_button()
def click_events_panel_actions_tab(self) -> ActionsEventsContainer:
"""Выполняет нажатие tab-кнопки Действия."""
return self.event_panel.click_actions_tab()
def click_events_panel_audit_tab(self) -> AuditEventsContainer:
"""Выполняет нажатие tab-кнопки Аудит."""
return self.event_panel.click_audit_tab()
def click_events_panel_events_tab(self) -> EventsTabContainer:
"""Выполняет нажатие tab-кнопки События."""
return self.event_panel.click_events_tab()
def click_events_panel_maintenance_tab(self) -> MaintenanceEventsContainer:
"""Выполняет нажатие tab-кнопки Обслуживание."""
return self.event_panel.click_maintenance_tab()
def click_events_panel_system_log_tab(self) -> SystemLogEventsContainer: def click_events_panel_system_log_tab(self) -> SystemLogEventsContainer:
"""Выполняет нажатие tab-кнопки Системный журнал.""" """Выполняет нажатие tab-кнопки Системный журнал."""
@ -103,16 +79,6 @@ class MainPage(BasePage):
node_locator, item_name, parent node_locator, item_name, parent
) )
def click_expand_workarea_button(self) -> None:
"""Выполняеи нажатие кнопки расширения рабочей области страницы"""
self.navigation_panel.expand_workarea()
def click_reduce_workarea_button(self) -> None:
"""Выполняеи нажатие кнопки сжатия рабочей области страницы"""
self.navigation_panel.reduce_workarea()
def click_user_button(self) -> UserCard: def click_user_button(self) -> UserCard:
"""Выполняет нажатие кнопки пользователя.""" """Выполняет нажатие кнопки пользователя."""
@ -190,7 +156,6 @@ class MainPage(BasePage):
return self.event_panel.check_expand_more_button() return self.event_panel.check_expand_more_button()
def check_navigation_item_exists(self, item_name: str) -> bool: def check_navigation_item_exists(self, item_name: str) -> bool:
"""Проверяет существование элемента в навигационной панели. """Проверяет существование элемента в навигационной панели.
@ -205,7 +170,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,28 +179,9 @@ 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:
"""Выполняет рекурсивный поиск по панели навигации
заданного элемента, делает клик по нему, проверяет наличие индикатора состояния.
Если индикатор состояния присутствует, возвращается его цвет. Иначе None"""
active_item_locator = self.page.locator(
NavigationPanelLocators.PANEL_MAIN
).locator(NavigationPanelLocators.ACTIVE_CONTAINER)
node_locator = active_item_locator.locator(
NavigationPanelLocators.SUB_PANEL_MAIN
).locator(NavigationPanelLocators.TREEVIEW).first
# Рекурсивный поиск в дереве v-treeview заданного элемента
# и клик по нему
return self.navigation_panel.check_sub_item_state(
node_locator, item_name, parent
)
def check_navigation_panel_verticall_scrolling(self) -> bool: def check_navigation_panel_verticall_scrolling(self) -> bool:
"""Проверяет возможность вертикальной прокрутки панели. """Проверяет возможность вертикальной прокрутки панели.
@ -253,6 +199,7 @@ class MainPage(BasePage):
## to-do: кнопки галочки??? ## to-do: кнопки галочки???
self.event_panel.should_be_tab_buttons() self.event_panel.should_be_tab_buttons()
self.event_panel.should_be_event_buttons() self.event_panel.should_be_event_buttons()
self.event_panel.should_be_search_button()
self.event_panel.should_be_user_button() self.event_panel.should_be_user_button()
def should_be_navigation_panel(self) -> None: def should_be_navigation_panel(self) -> None:
@ -262,13 +209,3 @@ class MainPage(BasePage):
NavigationPanelLocators.PANEL_MAIN, NavigationPanelLocators.PANEL_MAIN,
"Navigation panel is missing" "Navigation panel is missing"
) )
def should_be_expand_workarea_button(self) -> None:
"""Проверяет наличие кнопки расширения рабочей области страницы."""
self.navigation_panel.should_be_expand_workarea_button()
def should_be_reduce_workarea_button(self) -> None:
"""Проверяет наличие кнопки сжатия рабочей области страницы."""
self.navigation_panel.should_be_reduce_workarea_button()

View File

@ -4,12 +4,15 @@
Позволяет проверять состояние и взаимодействовать с элементами вкладки. Позволяет проверять состояние и взаимодействовать с элементами вкладки.
""" """
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 elements.text_element import Text
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 +30,38 @@ 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) message_setting_label = Text(page,
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator) page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
print(self.input_fields_locators) get_by_text('Сообщение'),
"message_setting_label")
self.settings_form.add_content_item("message_setting_label", message_setting_label)
loc = self.input_fields_locators.get("Сообщение") loc_message_input = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
loc_message_input = loc.locator("//input[@data-testid='PUSH_NOTIFICATIONS__text-field__message']") get_by_label('Сообщение').nth(1)
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("Пользователи") users_settings_locator = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_label('Пользователи')
users_setting_label = Text(page, users_settings_locator, "users_setting_label")
self.settings_form.add_content_item("users_setting_label", users_setting_label)
users_setting_input = TextInput(page, users_setting_input = TextInput(page,
loc.get_by_role("combobox"), page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_role("combobox"),
"users_setting_input") "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 +72,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 +99,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 +117,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 +131,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)
@ -130,22 +143,15 @@ class PushNotificationsSettingsTab(BasePage):
def check_content(self): def check_content(self):
"""Проверяет наличие и корректность всех элементов страницы.""" """Проверяет наличие и корректность всех элементов страницы."""
expected_input_field_names = ["Сообщение", "Пользователи"] self.should_be_toolbar()
self.should_be_form_toolbar() self.should_be_form_toolbar()
self.settings_form.check_button_visibility("submit_button")
self.settings_form.check_button_tooltip("submit_button", "Отправить Push уведомление")
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(): 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 +159,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 +185,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 +193,3 @@ class PushNotificationsSettingsTab(BasePage):
self.alert.check_alert_presence('\nPush-уведомление\nуспешно отправлено\n') self.alert.check_alert_presence('\nPush-уведомление\nуспешно отправлено\n')
self.alert.check_alert_absence('\nPush-уведомление\nуспешно отправлено\n') self.alert.check_alert_absence('\nPush-уведомление\nуспешно отправлено\n')
def should_be_disabled_button(self) -> None:
"""Проверяет, что кнопка 'Отправить' отключена.
Raises:
AssertionError: Если кнопка включена.
"""
submit_button = self.settings_form.get_button_by_name("submit_button")
assert submit_button.is_disabled, "Submit button should be disabled"

View File

@ -31,7 +31,7 @@ class RackPage(BasePage):
Инициализирует объект вкладки стойки. Инициализирует объект вкладки стойки.
Args: Args:
page (Page): Экземпляр страницы Playwright page: Экземпляр страницы Playwright
""" """
super().__init__(page) super().__init__(page)
@ -55,12 +55,6 @@ class RackPage(BasePage):
# Действия # Действия
def click_edit_button(self) -> None:
""" Кликает на кнопку 'Изменить'."""
self.toolbar.get_button_by_name("edit").click()
self.wait_for_timeout(1000)
def get_available_tabs(self) -> list[str]: def get_available_tabs(self) -> list[str]:
""" """
Возвращает список доступных вкладок. Возвращает список доступных вкладок.
@ -78,7 +72,7 @@ class RackPage(BasePage):
tab_elements.first.wait_for(state="visible", timeout=5000) tab_elements.first.wait_for(state="visible", timeout=5000)
total_count = tab_elements.count() total_count = tab_elements.count()
logger.debug("Total top tab elements found: %d", total_count) logger.debug(f"Total top tab elements found: {total_count}")
for i in range(total_count): for i in range(total_count):
element = tab_elements.nth(i) element = tab_elements.nth(i)
@ -90,9 +84,9 @@ class RackPage(BasePage):
tab_text = tab_text.strip() tab_text = tab_text.strip()
if tab_text and tab_text not in tabs: if tab_text and tab_text not in tabs:
tabs.append(tab_text) tabs.append(tab_text)
logger.debug("Top tab found: '%s'", tab_text) logger.debug(f"Top tab found: '{tab_text}'")
logger.debug("Available top tabs found: %s", tabs) logger.debug(f"Available top tabs found: {tabs}")
return tabs return tabs
def get_current_active_side(self) -> Optional[str]: def get_current_active_side(self) -> Optional[str]:
@ -164,13 +158,13 @@ class RackPage(BasePage):
Переключается на указанную вкладку. Переключается на указанную вкладку.
Args: Args:
tab_name (str): Название вкладки для переключения tab_name: Название вкладки для переключения
Raises: Raises:
AssertionError: Если вкладка не найдена или недоступна AssertionError: Если вкладка не найдена или недоступна
""" """
logger.debug("Switching to tab '%s'...", tab_name) logger.debug(f"Switching to tab '{tab_name}'...")
tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name)) tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name))
@ -178,7 +172,7 @@ class RackPage(BasePage):
# Проверяем активность ДО клика # Проверяем активность ДО клика
if self.is_tab_active(tab_name): if self.is_tab_active(tab_name):
logger.debug("Tab '%s' is already active", tab_name) logger.debug(f"Tab '{tab_name}' is already active")
return return
# Находим первую видимую вкладку с нужным именем # Находим первую видимую вкладку с нужным именем
@ -192,7 +186,7 @@ class RackPage(BasePage):
assert target_tab is not None, f"No visible/available tab '{tab_name}' found" assert target_tab is not None, f"No visible/available tab '{tab_name}' found"
# Кликаем на вкладку # Кликаем на вкладку
logger.debug("Clicking on tab '%s'...", tab_name) logger.debug(f"Clicking on tab '{tab_name}'...")
target_tab.click() target_tab.click()
# Ждем изменения активной вкладки # Ждем изменения активной вкладки
@ -226,21 +220,21 @@ class RackPage(BasePage):
if main_container.count() == 0: if main_container.count() == 0:
logger.warning("Main rack container not found") logger.warning("Main rack container not found")
else: else:
logger.debug("Main rack container found (count: %d)", main_container.count()) logger.debug(f"Main rack container found (count: {main_container.count()})")
expect(main_container.first).to_be_attached() expect(main_container.first).to_be_attached()
# Проверяем наличие позиций юнитов # Проверяем наличие позиций юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
if unit_positions.count() > 0: if unit_positions.count() > 0:
logger.debug("Unit positions found: %d", unit_positions.count()) logger.debug(f"Unit positions found: {unit_positions.count()}")
if unit_positions.first.text_content(): if unit_positions.first.text_content():
content = unit_positions.first.text_content().strip() content = unit_positions.first.text_content().strip()
logger.debug("First position: %s", content) logger.debug(f"First position: {content}")
# Проверяем наличие кнопок добавления # Проверяем наличие кнопок добавления
open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON) open_buttons = self.page.locator(RackLocators.ADD_CIRCLE_BUTTON)
if open_buttons.count() > 0: if open_buttons.count() > 0:
logger.debug("'add_circle' buttons found: %d", open_buttons.count()) logger.debug(f"'add_circle' buttons found: {open_buttons.count()}")
logger.debug("Rack interface loaded") logger.debug("Rack interface loaded")
@ -260,8 +254,8 @@ class RackPage(BasePage):
device_title = first_device.get_attribute("title") or "No title" device_title = first_device.get_attribute("title") or "No title"
logger.debug( logger.debug(
"Devices found: %d (first: ID=%s, Title=%s)", f"Devices found: {device_count} "
device_count, device_id, device_title f"(first: ID={device_id}, Title={device_title})"
) )
else: else:
logger.debug("No devices detected") logger.debug("No devices detected")
@ -269,9 +263,6 @@ class RackPage(BasePage):
def check_tab_switching(self) -> None: def check_tab_switching(self) -> None:
""" """
Проверяет переключение между вкладками стойки. Проверяет переключение между вкладками стойки.
Raises:
AssertionError: Если не удалось переключиться на все вкладки
""" """
logger.debug("Testing rack tab switching functionality...") logger.debug("Testing rack tab switching functionality...")
@ -285,14 +276,14 @@ class RackPage(BasePage):
"Сервисы" "Сервисы"
] ]
logger.debug("Defined tabs to test: %s", defined_tabs) logger.debug(f"Defined tabs to test: {defined_tabs}")
successful_switches = 0 successful_switches = 0
failed_switches = [] failed_switches = []
# Тестируем переключение на каждую определенную вкладку # Тестируем переключение на каждую определенную вкладку
for tab_name in defined_tabs: for tab_name in defined_tabs:
logger.debug("Testing switch to tab '%s'...", tab_name) logger.debug(f"Testing switch to tab '{tab_name}'...")
try: try:
# Переключаемся на вкладку # Переключаемся на вкладку
@ -300,15 +291,15 @@ class RackPage(BasePage):
# Проверяем, что вкладка активна # Проверяем, что вкладка активна
if self.is_tab_active(tab_name): if self.is_tab_active(tab_name):
logger.debug("Successfully switched to tab '%s'", tab_name) logger.debug(f"Successfully switched to tab '{tab_name}'")
successful_switches += 1 successful_switches += 1
else: else:
logger.warning("Tab '%s' not active after switching", tab_name) logger.warning(f"Tab '{tab_name}' not active after switching")
failed_switches.append(f"Tab '{tab_name}' is not active after click") failed_switches.append(f"Tab '{tab_name}' is not active after click")
except (AssertionError, TimeoutError) as e: except (AssertionError, TimeoutError) as e:
# Ловим только конкретные исключения, которые могут возникнуть при переключении вкладок # Ловим только конкретные исключения, которые могут возникнуть при переключении вкладок
logger.error("Error switching to tab '%s': %s", tab_name, e) logger.error(f"Error switching to tab '{tab_name}': {e}")
failed_switches.append(f"Tab '{tab_name}' error: {str(e)}") failed_switches.append(f"Tab '{tab_name}' error: {str(e)}")
# Небольшая пауза между переключениями # Небольшая пауза между переключениями
@ -316,12 +307,12 @@ class RackPage(BasePage):
# Формируем итоговый отчет # Формируем итоговый отчет
logger.debug("=== TAB SWITCHING RESULTS ===") logger.debug("=== TAB SWITCHING RESULTS ===")
logger.debug("Successful switches: %d/%d", successful_switches, len(defined_tabs)) logger.debug(f"Successful switches: {successful_switches}/{len(defined_tabs)}")
if failed_switches: if failed_switches:
logger.debug("Failed switches:") logger.debug("Failed switches:")
for failure in failed_switches: for failure in failed_switches:
logger.debug(" - %s", failure) logger.debug(f" - {failure}")
# Требуем успешного переключения на все определенные вкладки # Требуем успешного переключения на все определенные вкладки
assert successful_switches == len(defined_tabs), ( assert successful_switches == len(defined_tabs), (
@ -331,14 +322,14 @@ class RackPage(BasePage):
f"Errors: {', '.join(failed_switches)}" f"Errors: {', '.join(failed_switches)}"
) )
logger.debug("All %d defined tabs successfully switched!", successful_switches) logger.debug(f"All {successful_switches} defined tabs successfully switched!")
def is_tab_active(self, tab_name: str) -> bool: def is_tab_active(self, tab_name: str) -> bool:
""" """
Проверяет, активна ли указанная вкладка. Проверяет, активна ли указанная вкладка.
Args: Args:
tab_name (str): Название вкладки для проверки tab_name: Название вкладки для проверки
Returns: Returns:
bool: True если вкладка активна, False в противном случае bool: True если вкладка активна, False в противном случае
@ -350,10 +341,10 @@ class RackPage(BasePage):
if active_tab.count() > 0 and active_tab.first.is_visible(): if active_tab.count() > 0 and active_tab.first.is_visible():
active_text = active_tab.first.text_content() active_text = active_tab.first.text_content()
if active_text and active_text.strip() == tab_name: if active_text and active_text.strip() == tab_name:
logger.debug("Tab '%s' is active (via active tab class)", tab_name) logger.debug(f"Tab '{tab_name}' is active (via active tab class)")
return True return True
logger.debug("Tab '%s' is not active", tab_name) logger.debug(f"Tab '{tab_name}' is not active")
return False return False
def should_be_panel_header(self, expected_toolbar_title_items: list[str]) -> None: def should_be_panel_header(self, expected_toolbar_title_items: list[str]) -> None:
@ -361,7 +352,7 @@ class RackPage(BasePage):
Проверяет наличие и корректность заголовка панели. Проверяет наличие и корректность заголовка панели.
Args: Args:
expected_toolbar_title_items (list[str]): Ожидаемые элементы заголовка expected_toolbar_title_items: Ожидаемые элементы заголовка
Raises: Raises:
AssertionError: Если заголовок панели не соответствует ожиданиям AssertionError: Если заголовок панели не соответствует ожиданиям
@ -383,12 +374,7 @@ class RackPage(BasePage):
) )
def should_be_rack_sides_displayed(self) -> None: def should_be_rack_sides_displayed(self) -> None:
""" """Проверка отображения и структуры сторон стойки."""
Проверка отображения и структуры сторон стойки.
Raises:
AssertionError: Если стороны стойки не отображаются корректно
"""
logger.debug("Checking rack sides display and structure...") logger.debug("Checking rack sides display and structure...")
@ -410,7 +396,7 @@ class RackPage(BasePage):
# Проверяем, какая сторона активна по умолчанию # Проверяем, какая сторона активна по умолчанию
current_active = self.get_current_active_side() current_active = self.get_current_active_side()
logger.debug("Current active side: %s", current_active) logger.debug(f"Current active side: {current_active}")
# Дополнительная проверка устройств # Дополнительная проверка устройств
self.check_physical_devices_presence() self.check_physical_devices_presence()
@ -423,7 +409,7 @@ class RackPage(BasePage):
# Возвращаемся на исходную активную сторону # Возвращаемся на исходную активную сторону
if current_active: if current_active:
logger.debug("Returning to original active side: %s", current_active) logger.debug(f"Returning to original active side: {current_active}")
if current_active == "Лицевая сторона": if current_active == "Лицевая сторона":
front_side_button.click() front_side_button.click()
else: else:
@ -431,7 +417,7 @@ class RackPage(BasePage):
self.wait_for_timeout(1000) self.wait_for_timeout(1000)
final_active = self.get_current_active_side() final_active = self.get_current_active_side()
logger.debug("Final active side: %s", final_active) logger.debug(f"Final active side: {final_active}")
logger.debug("All rack sides checks passed successfully") logger.debug("All rack sides checks passed successfully")
@ -440,25 +426,19 @@ class RackPage(BasePage):
Проверяет наличие и функциональность кнопок тулбара. Проверяет наличие и функциональность кнопок тулбара.
Raises: Raises:
AssertionError: Если кнопки недоступны или подсказки неверны AssertionError: Если кнопки недоступны или подсказки неверны.
""" """
logger.debug("Checking toolbar buttons...") logger.debug("Checking toolbar buttons...")
# Проверяем основные кнопки
self.toolbar.check_button_visibility("edit") self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Изменить") self.toolbar.check_button_tooltip("edit", "Изменить")
self.toolbar.get_button_by_name("edit").click()
# Кликаем на кнопку "Изменить" для проверки функциональности
#self.toolbar.get_button_by_name("edit").click()
def should_have_hide_rack_button(self) -> None: def should_have_hide_rack_button(self) -> None:
""" """
Проверка кнопки "Скрыть стойку". Проверка кнопки "Скрыть стойку".
Проверяет видимость, тултип, кликабельность и эффект скрытия стойки.
Raises:
AssertionError: Если кнопка не отображается или не работает
""" """
logger.debug("Checking 'Hide rack' button...") logger.debug("Checking 'Hide rack' button...")
@ -485,9 +465,7 @@ class RackPage(BasePage):
def should_have_show_rack_button(self) -> None: def should_have_show_rack_button(self) -> None:
""" """
Проверка кнопки "Показать стойку". Проверка кнопки "Показать стойку".
Проверяет наличие, тултип, кликабельность и эффект показа стойки.
Raises:
AssertionError: Если кнопка не отображается или не работает
""" """
logger.debug("Checking 'Show rack' button...") logger.debug("Checking 'Show rack' button...")
@ -518,56 +496,56 @@ class RackPage(BasePage):
Проверка структуры конкретной стороны стойки. Проверка структуры конкретной стороны стойки.
Args: Args:
side_name (str): Название стороны для логов side_name: Название стороны для логов
side_button: Локатор кнопки стороны side_button: Локатор кнопки стороны
Raises: Raises:
AssertionError: Если структура стороны некорректна AssertionError: Если структура стороны некорректна
""" """
logger.debug("Checking %s...", side_name) logger.debug(f"Checking {side_name}...")
# Логируем текущее состояние кнопки перед кликом # Логируем текущее состояние кнопки перед кликом
button_classes = side_button.get_attribute("class") or "" button_classes = side_button.get_attribute("class") or ""
logger.debug("Button classes before click: %s", button_classes) logger.debug(f"Button classes before click: {button_classes}")
# Проверяем, активна ли уже эта сторона # Проверяем, активна ли уже эта сторона
current_active = self.get_current_active_side() current_active = self.get_current_active_side()
logger.debug("Current active side (before click): '%s'", current_active) logger.debug(f"Current active side (before click): '{current_active}'")
if current_active == side_name: if current_active == side_name:
logger.debug("%s is already active", side_name) logger.debug(f"{side_name} is already active")
else: else:
# Если не активна, кликаем для переключения # Если не активна, кликаем для переключения
logger.debug("Switching to %s...", side_name) logger.debug(f"Switching to {side_name}...")
side_button.click() side_button.click()
# Даем время на перерисовку классов (увеличиваем время) # Даем время на перерисовку классов (увеличиваем время)
self.wait_for_timeout(2500) self.wait_for_timeout(2500)
# Проверяем классы после клика # Проверяем классы после клика
button_classes_after = side_button.get_attribute("class") or "" button_classes_after = side_button.get_attribute("class") or ""
logger.debug("Button classes after click: %s", button_classes_after) logger.debug(f"Button classes after click: {button_classes_after}")
# Проверяем, что нужная сторона стала активной # Проверяем, что нужная сторона стала активной
active_side = self.get_current_active_side() active_side = self.get_current_active_side()
logger.debug("Active side after switching: '%s'", active_side) logger.debug(f"Active side after switching: '{active_side}'")
assert active_side == side_name, \ assert active_side == side_name, \
f"Wrong side is active: '{active_side}', expected: '{side_name}'" f"Wrong side is active: '{active_side}', expected: '{side_name}'"
logger.debug("%s successfully activated", side_name) logger.debug(f"{side_name} successfully activated")
# Проверяем позиции юнитов # Проверяем позиции юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS) unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
total_positions = unit_positions.count() total_positions = unit_positions.count()
logger.debug("Total unit positions: %d", total_positions) logger.debug(f"Total unit positions: {total_positions}")
assert total_positions > 0, f"No unit positions found on {side_name}" assert total_positions > 0, f"No unit positions found on {side_name}"
# Проверяем юниты # Проверяем юниты
all_units = self.page.locator(RackLocators.ALL_UNITS) all_units = self.page.locator(RackLocators.ALL_UNITS)
all_units_count = all_units.count() all_units_count = all_units.count()
units_per_side = all_units_count // 2 units_per_side = all_units_count // 2
logger.debug("Units on %s: %d", side_name, units_per_side) logger.debug(f"Units on {side_name}: {units_per_side}")
# Проверяем устройства # Проверяем устройства
devices = self.page.locator(RackLocators.DEVICE_ELEMENTS) devices = self.page.locator(RackLocators.DEVICE_ELEMENTS)
@ -584,34 +562,34 @@ class RackPage(BasePage):
slots = first_device.locator(RackLocators.DEVICE_SLOTS) slots = first_device.locator(RackLocators.DEVICE_SLOTS)
slot_count = slots.count() slot_count = slots.count()
logger.debug("Devices found: %d (showing first)", device_count) logger.debug(f"Devices found: {device_count} (showing first)")
logger.debug(" Device: ID=%s", device_id) logger.debug(f" Device: ID={device_id}")
logger.debug(" Title: %s", device_title) logger.debug(f" Title: {device_title}")
logger.debug(" Classes: %s", device_classes) logger.debug(f" Classes: {device_classes}")
logger.debug(" Slots: %d", slot_count) logger.debug(f" Slots: {slot_count}")
else: else:
logger.debug("No devices detected") logger.debug("No devices detected")
logger.debug("%s check completed successfully", side_name) logger.debug(f"{side_name} check completed successfully")
def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None: def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None:
""" """
Ожидает активации вкладки. Ожидает активации вкладки.
Args: Args:
tab_name (str): Название вкладки для ожидания tab_name: Название вкладки для ожидания
timeout (int, optional): Время ожидания в миллисекундах, по умолчанию 5000 timeout: Время ожидания в миллисекундах
Raises: Raises:
AssertionError: Если вкладка не активирована в течение таймаута AssertionError: Если вкладка не активирована в течение таймаута
""" """
logger.debug("Waiting for tab '%s' activation...", tab_name) logger.debug(f"Waiting for tab '{tab_name}' activation...")
start_time = self.page.evaluate("Date.now()") start_time = self.page.evaluate("Date.now()")
while self.page.evaluate("Date.now()") - start_time < timeout: while self.page.evaluate("Date.now()") - start_time < timeout:
if self.is_tab_active(tab_name): if self.is_tab_active(tab_name):
logger.debug("Tab '%s' successfully activated", tab_name) logger.debug(f"Tab '{tab_name}' successfully activated")
return return
self.wait_for_timeout(100) self.wait_for_timeout(100)

View File

@ -1,4 +1,4 @@
"""Модуль вкладки 'Статус компонентов'. """Модуль вкладки 'Статус обслуживания'.
Содержит класс ServiceStatusTab для работы с таблицей сервисов. Содержит класс ServiceStatusTab для работы с таблицей сервисов.
Позволяет проверять состояние и взаимодействовать с элементами вкладки. Позволяет проверять состояние и взаимодействовать с элементами вкладки.
@ -9,11 +9,12 @@ from playwright.sync_api import Page, Locator, expect
from elements.text_element import Text from elements.text_element import Text
from elements.button_element import Button from elements.button_element import Button
from components.table_component import TableComponent from components.table_component import TableComponent
from components.expand_button_component import ExpandButton
from pages.base_page import BasePage from pages.base_page import BasePage
class ServiceStatusTab(BasePage): class ServiceStatusTab(BasePage):
"""Класс для работы с вкладкой 'Статус компонентов'. """Класс для работы с вкладкой 'Статус обслуживания'.
Предоставляет методы для взаимодействия с таблицей сервисов и проверки Предоставляет методы для взаимодействия с таблицей сервисов и проверки
её состояния. её состояния.
@ -23,7 +24,7 @@ class ServiceStatusTab(BasePage):
""" """
def __init__(self, page: Page) -> None: def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки 'Статус компонентов'.""" """Инициализирует компоненты вкладки 'Статус обслуживания'."""
super().__init__(page) super().__init__(page)
@ -32,6 +33,7 @@ class ServiceStatusTab(BasePage):
self.table_locator = self.iframe_locator.locator("div.MuiBox-root > div > table") self.table_locator = self.iframe_locator.locator("div.MuiBox-root > div > table")
self.tab_title_locator = self.iframe_locator.locator("//h5[text()='Состояние контейнеров']") self.tab_title_locator = self.iframe_locator.locator("//h5[text()='Состояние контейнеров']")
self.update_button_locator = self.iframe_locator.get_by_role("button", name='Обновить') self.update_button_locator = self.iframe_locator.get_by_role("button", name='Обновить')
self.analyzer_open_button_locator = self.iframe_locator.get_by_role("button", name='открыть анализатор')
self.tab_title = Text(page, self.tab_title = Text(page,
self.tab_title_locator, self.tab_title_locator,
@ -39,7 +41,10 @@ class ServiceStatusTab(BasePage):
self.update_button = Button(page, self.update_button = Button(page,
self.update_button_locator, self.update_button_locator,
"update_button") "update_button")
self.analyzer_open_button = Button(page,
self.analyzer_open_button_locator,
"analyzer_open_button")
self.expand_work_area_button = ExpandButton(page)
self.services_table = TableComponent(page) self.services_table = TableComponent(page)
# Действия: # Действия:
@ -126,11 +131,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:
"""Прокручивает содержимое вкладки вверх.""" """Прокручивает содержимое вкладки вверх."""
@ -170,7 +199,6 @@ class ServiceStatusTab(BasePage):
rows_count = self.services_table.get_rows_count(self.table_locator) rows_count = self.services_table.get_rows_count(self.table_locator)
for i in range(rows_count): for i in range(rows_count):
row_locator = self.services_table.get_row_locator(self.table_locator, i) row_locator = self.services_table.get_row_locator(self.table_locator, i)
row_locator.scroll_into_view_if_needed()
layers_button = row_locator.get_by_role("button", name="Слои") layers_button = row_locator.get_by_role("button", name="Слои")
expect(layers_button).to_be_visible(), f"Layers button is missing in {i} row" expect(layers_button).to_be_visible(), f"Layers button is missing in {i} row"
@ -252,11 +280,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 +314,26 @@ class ServiceStatusTab(BasePage):
"Service statuses table is missing" "Service statuses table is missing"
) )
def should_be_analyzer_open_button(self) -> None:
"""Проверяет наличие кнопки 'navigate_before/navigate_next'.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.analyzer_open_button.check_visibility(
"Analyzer open button on bottom of service statuses tab is missing"
)
def should_be_expand_work_area_button(self) -> None:
"""Проверяет наличие кнопки расширения/сжатия рабочей области вкладки.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.expand_work_area_button.should_be_button()
def should_be_update_button(self) -> None: def should_be_update_button(self) -> None:
"""Проверяет наличие кнопки 'Обновить'. """Проверяет наличие кнопки 'Обновить'.

View File

@ -7,6 +7,7 @@ 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 elements.text_element import Text
from components.toolbar_component import ToolbarComponent 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
@ -27,48 +28,75 @@ 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)
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) self.settings_form.add_toolbar_title("Время жизни сеанса")
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
# Используем локаторы для числовых полей admin_setting_label = Text(page,
loc = self.input_fields_locators.get("Администратор") page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
loc_admin = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__administrator']") get_by_text('Администратор'),
"admin_setting_label")
self.settings_form.add_content_item("admin_setting_label", admin_setting_label)
loc_admin = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_label('Администратор')
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("Оператор") operator_setting_label = Text(page,
loc_oper = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__operator']") page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_text('Оператор'),
"operator_setting_label")
self.settings_form.add_content_item("operator_setting_label", operator_setting_label)
loc_oper = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_label('Оператор')
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("Контактное лицо") manager_setting_label = Text(page,
loc_manager = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__manager']") page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_text('Контактное лицо'),
"manager_setting_label")
self.settings_form.add_content_item("manager_setting_label", manager_setting_label)
loc_manager = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_label('Контактное лицо')
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("Специалист информационной безопасности") inform_secur_user_setting_label = Text(page,
loc_inform_secur_user = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__inform_secur_user']") page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_text('Специалист информационной безопасности'),
"inform_secur_user_setting_label")
self.settings_form.add_content_item("inform_secur_user_setting_label", inform_secur_user_setting_label)
loc_inform_secur_user = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_label('Специалист информационной безопасности')
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') collector_setting_label = Text(page,
loc_collector = loc.locator("//input[@data-testid='SESSION_SETTINGS__text-field__$collector']") page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_text('$collector'),
"collector_setting_label")
self.settings_form.add_content_item("collector_setting_label", collector_setting_label)
loc_collector = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
get_by_label('$collector')
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)
@ -127,6 +155,27 @@ class SessionSettingsTab(BasePage):
assert False, f"Got unsupported field name {field_name}" assert False, f"Got unsupported field name {field_name}"
return field return field
def get_label_by_name(self, label_name: str) -> Text:
"""Возвращает элемент название поля ввода по его имени.
Returns:
Text: Элемент название поле ввода.
"""
if label_name == "administrator":
label = self.settings_form.get_content_item("admin_setting_label")
elif label_name == "operator":
label = self.settings_form.get_content_item("operator_setting_label")
elif label_name == "manager":
label = self.settings_form.get_content_item("manager_setting_label")
elif label_name == "inform_secur_user":
label = self.settings_form.get_content_item("inform_secur_user_setting_label")
elif label_name == '$collector':
label = self.settings_form.get_content_item("collector_setting_label")
else:
assert False, f"Got unsupported label name {label_name}"
return label
def get_setting_value(self, field_name: str) -> str: def get_setting_value(self, field_name: str) -> str:
"""Возвращает текущее значение требуемого поля настроек. """Возвращает текущее значение требуемого поля настроек.
@ -168,6 +217,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,30 +232,26 @@ 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)
# Проверки: # Проверки:
def check_content(self): def check_content(self):
"""Проверяет наличие и корректность всех элементов формы.""" """Проверяет наличие и корректность всех элементов формы."""
expected_input_field_names = ["Администратор", "Оператор",
"Специалист информационной безопасности",
"Контактное лицо", "$collector"]
self.should_be_toolbar() self.should_be_toolbar()
self.should_be_toolbar_buttons() self.should_be_toolbar_buttons()
actual_input_field_names = self.input_fields_locators.keys() self.should_be_form_toolbar()
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(): for name in self.settings_form.content_items.keys():
item = self.settings_form.get_content_item(name) item = self.settings_form.get_content_item(name)
@ -212,18 +259,12 @@ class SessionSettingsTab(BasePage):
f"Session settings input form item with name '{name}' is missing" f"Session settings input form item with name '{name}' is missing"
) )
for name in actual_input_field_names:
# Для суффикса "минут"
value_suffix_loc = self.input_fields_locators.get(name). \
locator(SettingsFormLocators.SETTINGS_FORM_INPUT_VALUE_SUFFIX)
value_suffix = value_suffix_loc.text_content().strip()
assert value_suffix == "минут", f"Incorrect value suffix for field {name}"
def check_vertical_scrolling(self) -> bool: def check_vertical_scrolling(self) -> bool:
"""Проверка возможности вертикального скроллинга формы настроек времени жизни сессии. """Проверка возможности вертикального скроллинга формы настроек времени жизни сессии.
""" """
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 +274,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 +297,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:
"""Проверяет соответствие содержимого полей формы данным из БД. """Проверяет соответствие содержимого полей формы данным из БД.
@ -277,4 +327,4 @@ class SessionSettingsTab(BasePage):
field_value = self.settings_form.get_content_item("collector_setting").get_input_value().strip() field_value = self.settings_form.get_content_item("collector_setting").get_input_value().strip()
else: else:
assert False, f"Got unsupported field name {key}" assert False, f"Got unsupported field name {key}"
assert field_value==str(value),f"{key} field value {field_value} is not equal value {value} from data base" assert field_value == str(value), f"{key} field value {field_value} is not equal value {value} from data base"

View File

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

View File

@ -8,7 +8,8 @@ import re
from playwright.sync_api import Page from playwright.sync_api import Page
from locators.table_locators import TableLocators from locators.table_locators import TableLocators
from components_derived.modal_edit_user import EditUserModalWindow from components_derived.modal_edit_user import EditUserModalWindow
from components_derived.modal_add_user import AddUserModalWindow from components_derived.modal_add_local_user import AddLocalUserModalWindow
from components_derived.modal_add_AD_user import AddADUserModalWindow
from data.roles_dict import roles_dict from data.roles_dict import roles_dict
from components.toolbar_component import ToolbarComponent from components.toolbar_component import ToolbarComponent
from components.table_component import TableComponent from components.table_component import TableComponent
@ -32,18 +33,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 = {}
@ -62,8 +62,10 @@ class UsersTab(BasePage):
AssertionError: Если тип окна не поддерживается. AssertionError: Если тип окна не поддерживается.
""" """
if window_type == "add_user": if window_type == "add_local_user":
self.modal_windows["add_user"] = AddUserModalWindow(self.page) self.modal_windows["add_local_user"] = AddLocalUserModalWindow(self.page)
elif window_type == "add_AD_user":
self.modal_windows["add_AD_user"] = AddADUserModalWindow(self.page)
elif window_type == "edit_user": elif window_type == "edit_user":
self.modal_windows[title] = EditUserModalWindow(self.page, title) self.modal_windows[title] = EditUserModalWindow(self.page, title)
else: else:
@ -83,7 +85,14 @@ class UsersTab(BasePage):
или если текст alert не соответствует ожидаемому. или если текст alert не соответствует ожидаемому.
""" """
add_user_window = self.get_modal_window("add_user") add_user_window = self.get_modal_window("add_local_user")
# skip as unsupported
# auth_type = user_data.get("auth_type")
# if auth_type == "active_directory":
# add_user_window.check_active_directory_checkbox()
# self.add_modal_window("add_AD_user", "")
# add_user_window = self.get_modal_window("add_AD_user")
add_user_window.new_user(user_data) add_user_window.new_user(user_data)
@ -101,15 +110,25 @@ class UsersTab(BasePage):
return is_added return is_added
def close_add_AD_user_window(self) -> None:
"""Закрывает окно добавления пользователя."""
self.close_modal_window("add_AD_user")
def close_add_AD_user_window_by_toolbar_button(self) -> None:
"""Закрывает окно добавления пользователя через тулбар."""
self.close_modal_window_by_toolbar_button("add_AD_user")
def close_add_user_window(self) -> None: def close_add_user_window(self) -> None:
"""Закрывает окно добавления пользователя.""" """Закрывает окно добавления пользователя."""
self.close_modal_window("add_user") self.close_modal_window("add_local_user")
def close_add_user_window_by_toolbar_button(self) -> None: def close_add_user_window_by_toolbar_button(self) -> None:
"""Закрывает окно добавления пользователя через тулбар.""" """Закрывает окно добавления пользователя через тулбар."""
self.close_modal_window_by_toolbar_button("add_user") self.close_modal_window_by_toolbar_button("add_local_user")
def close_edit_user_window(self, title: str) -> None: def close_edit_user_window(self, title: str) -> None:
"""Закрывает окно редактирования пользователя. """Закрывает окно редактирования пользователя.
@ -248,18 +267,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:
"""Открывает окно добавления пользователя. """Открывает окно добавления пользователя.
@ -275,8 +282,8 @@ class UsersTab(BasePage):
self.toolbar.click_button("add_user") self.toolbar.click_button("add_user")
self.page.wait_for_timeout(700) self.page.wait_for_timeout(700)
self.add_modal_window("add_user", "") self.add_modal_window("add_local_user", "")
self.get_modal_window("add_user").check_by_window_title() self.get_modal_window("add_local_user").check_by_window_title()
def open_edit_user_page_by_index(self, row_index: int) -> tuple: def open_edit_user_page_by_index(self, row_index: int) -> tuple:
"""Открывает окно редактирования по индексу строки. """Открывает окно редактирования по индексу строки.
@ -311,7 +318,7 @@ class UsersTab(BasePage):
if user_name == val: if user_name == val:
user_name = key user_name = key
role = table_content[row_index][3] role = table_content[row_index][2]
self.page.locator(TableLocators.TABLE_WORK_AREA).locator( self.page.locator(TableLocators.TABLE_WORK_AREA).locator(
"//tbody/tr").nth(row_index).click() "//tbody/tr").nth(row_index).click()
@ -359,11 +366,38 @@ class UsersTab(BasePage):
return new_password return new_password
def transform_to_add_AD_user_window(self):
"""Трансформирует модальное окно добавления локального пользователя
в окно добавления пользователя Active Directory с помощью нажатия
чек-бокса Active Directory.
"""
self.get_modal_window("add_local_user").check_active_directory_checkbox()
modal_window = self.modal_windows.get("add_AD_user")
if modal_window is None:
self.add_modal_window("add_AD_user", "")
def transform_to_add_user_window(self):
"""Трансформирует модальное окно добавления пользователя Active Directory
в окно добавления локального пользователя с помощью снятия отметки с
чек-бокса Active Directory.
"""
self.get_modal_window("add_AD_user").uncheck_active_directory_checkbox()
modal_window = self.modal_windows.get("add_local_user")
if modal_window is None:
self.add_modal_window("add_local_user", "")
# Проверки: # Проверки:
def check_add_AD_user_window_content(self) -> None:
"""Проверяет содержимое окна добавления пользователя через Active Directory."""
self.get_modal_window("add_AD_user").check_content()
def check_add_user_window_content(self) -> None: def check_add_user_window_content(self) -> None:
"""Проверяет содержимое окна добавления локального пользователя.""" """Проверяет содержимое окна добавления локального пользователя."""
self.get_modal_window("add_user").check_content() self.get_modal_window("add_local_user").check_content()
def check_edit_user_window_content(self, user_name: str, role: str) -> None: def check_edit_user_window_content(self, user_name: str, role: str) -> None:
"""Проверяет содержимое окна редактирования. """Проверяет содержимое окна редактирования.
@ -387,9 +421,8 @@ class UsersTab(BasePage):
""" """
self.page.wait_for_timeout(2000) self.page.wait_for_timeout(2000)
expected_headers = ['Имя пользователя', 'Идентификатор', 'Тип авторизации', expected_headers = ['Имя пользователя', 'Тип авторизации', 'Роль',
'Роль', 'E-mail', 'Номер для СМС'] 'E-mail', 'Номер для СМС']
table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA)
if len(table_content) == 0: if len(table_content) == 0:
@ -408,21 +441,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:
"""Проверяет наличие тулбара. """Проверяет наличие тулбара.
@ -521,11 +539,6 @@ class UsersTab(BasePage):
# НЕ преобразуем имя пользователя - оставляем как есть из БД # НЕ преобразуем имя пользователя - оставляем как есть из БД
user_info.append(user_name) user_info.append(user_name)
if item["id"] is not None:
user_info.append(str(item["id"]))
else:
user_info.append("")
if item["type_auth"] is not None: if item["type_auth"] is not None:
user_info.append(item["type_auth"]) user_info.append(item["type_auth"])
else: else:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,491 @@
"""Тест создания дочернего элемента 'Стойка'."""
import pytest
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData
from components_derived.frames.create_child_element_frame import CreateChildElementFrame
from pages.location_page import LocationPage
from pages.login_page import LoginPage
from pages.main_page import MainPage
logger = get_logger("CREATE_RACK_ELEMENT_TEST")
# @pytest.mark.smoke
class TestCreateRackElement:
"""Тест создания дочернего элемента типа 'Стойка'.
Тесты покрывают следующие сценарии:
1. test_create_rack_content: Проверяет содержимое формы создания стойки
2. test_create_rack_child_element: Проверяет создание дочернего элемента типа 'Стойка'
3. test_create_rack_with_duplicate_name: Проверяет создание стойки с дублирующимся именем
4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки
"""
# Инициализируем атрибуты
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.wait_for_timeout(2000)
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(2000)
self.main_page.click_main_navigation_panel_item("test-zone")
self.main_page.wait_for_timeout(2000)
# Создаем экземпляр страницы локации
self.location_page = LocationPage(browser)
@pytest.mark.develop
def test_create_rack_content(self, browser: Page) -> None:
"""Тест создания дочернего элемента типа 'Стойка'."""
# Проверяем что кнопка "Создать" доступна
self.location_page.should_be_toolbar_buttons()
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Проверяем заголовок формы создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Проверяем что после выбора 'Стойка' появляются специфичные поля
rack_maker.check_rack_fields_presence()
logger.info("Rack-specific fields are displayed correctly")
create_child_frame.should_be_toolbar_buttons()
def test_create_rack_child_element(self, browser: Page) -> None:
"""Тест создания дочернего элемента типа 'Стойка'."""
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных стойки
rack_data = RackData(
name="Test-Rack-01",
height="42",
depth="1000",
serial="TEST123456",
inventory="INV-001",
comment="Тестовая стойка для автоматизации",
cable_entry="Сверху",
state="В эксплуатации"
)
# Заполняем данные стойки
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку "Добавить"
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000)
logger.info("Test for creating 'Rack' child element completed successfully")
def test_create_rack_with_duplicate_name(self, browser: Page) -> None:
"""
Тест создания стойки с уже существующим именем.
Проверяет, что система корректно обрабатывает попытку создания
стойки с именем, которое уже используется.
"""
logger.info("Starting test for creating rack with duplicate name")
rack_name = "Test-Rack-01"
# Проверяем, существует ли уже стойка с таким именем
if not self._check_rack_existance(browser, rack_name):
logger.info(f"Rack with name '{rack_name}' not found. Creating first rack.")
self._create_rack(browser, rack_name)
logger.info(f"First rack with name '{rack_name}' created successfully")
else:
logger.info(f"Rack with name '{rack_name}' already exists, proceeding to create second one")
# Создаем вторую стойку с тем же именем
logger.info(f"Attempting to create second rack with name '{rack_name}'")
# Переходим обратно к созданию новой стойки
self.main_page.click_main_navigation_panel_item("test-zone")
self.main_page.wait_for_timeout(2000)
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных для второй стойки
rack_data = RackData(
name=rack_name,
height="42",
depth="1000"
)
# Пытаемся создать вторую стойку с тем же именем
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000)
# Проверяем наличие alert-окна с сообщением о дублирующемся имени
expected_alert_text = f"Имя {rack_name} уже используется"
create_child_frame.alert.check_alert_presence(expected_alert_text)
# Проверяем, что остались на странице создания (стойка не создана)
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Закрываем alert-окно с помощью кнопки закрытия
create_child_frame.wait_for_timeout(2000)
create_child_frame.alert.close_alert_by_text(expected_alert_text)
logger.info("System prevented creating rack with duplicate name")
def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data):
"""Выполняет один тест валидации обязательных полей.
Args:
create_child_frame: Фрейм создания дочернего элемента
rack_maker: Объект для работы со стойкой
test_data: Словарь с данными теста
"""
# Распаковываем данные теста
name_value = test_data["name"]
height_value = test_data["height"]
depth_value = test_data["depth"]
expected_alert_height = test_data["expected_alert_height"]
expected_alert_depth = test_data["expected_alert_depth"]
# Функция для проверки заполненности combobox поля
def is_field_filled(field_name: str) -> bool:
"""Проверяет, заполнено ли combobox поле."""
# Получаем локатор поля
field_locator = create_child_frame._get_field_locator(field_name)
# Находим элемент поля
field_element = create_child_frame.page.locator(field_locator).first
if not field_element.is_visible():
logger.info(f"Field '{field_name}' not visible")
return False
# Проверяем наличие кнопки закрытия (крестика) - признак заполненного поля
close_button = field_element.locator(
".v-select__selections" # Или другой локатор для кнопки закрытия
)
# Если есть кнопка закрытия, поле заполнено
has_close_button = close_button.count() > 0 and close_button.is_visible()
# Также можно проверить по тексту в поле
field_text = field_element.text_content() or ""
has_text = bool(field_text.strip())
logger.info(f"Field '{field_name}' - has close button: {has_close_button}, has text: {has_text}")
return has_close_button or has_text
# Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено
logger.info("Checking field: Depth (mm)")
if is_field_filled("Глубина (мм)"):
logger.info("Field 'Depth (mm)' is filled, performing clearing")
create_child_frame.clear_combobox_field("Глубина (мм)")
logger.info("Clearing completed for 'Depth (mm)'")
else:
logger.info("Field 'Depth (mm)' is already empty, skipping clearing")
# Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено
logger.info("Checking field: Height in units")
if is_field_filled("Высота в юнитах"):
logger.info("Field 'Height in units' is filled, performing clearing")
create_child_frame.clear_combobox_field("Высота в юнитах")
logger.info("Clearing completed for 'Height in units'")
else:
logger.info("Field 'Height in units' is already empty, skipping clearing")
# Создаем объект данных стойки
rack_data = RackData(
name=name_value,
height=height_value,
depth=depth_value
)
# Заполняем данные стойки
logger.info(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'")
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
logger.info("Submitting form for validation")
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(3000)
# Проверяем валидацию полей
logger.info("Checking validation results")
if height_value:
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
logger.info("Height field validation passed")
else:
create_child_frame.check_field_error_highlighted("Высота в юнитах")
logger.info("Height field validation failed as expected")
if depth_value:
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.info("Depth field validation passed")
else:
create_child_frame.check_field_error_highlighted("Глубина (мм)")
logger.info("Depth field validation failed as expected")
# Обрабатываем alert-окна
if not height_value:
logger.info("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.info("Height alert handled")
if not depth_value:
logger.info("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.info("Depth alert handled")
# Проверяем, что остались на странице создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.info("Test completed successfully")
def test_required_fields_validation(self, browser: Page) -> None:
"""
Тест проверки обязательных полей при создании стойки.
Проверяет, что система корректно валидирует обязательные поля:
- Поле 'Высота в юнитах' должно быть заполнено
- Поле 'Глубина (мм)' должно быть заполнено
"""
# Текст сообщения alert-окна
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Тестовые данные
test_cases = [
{
"name": "Test 1: Creating rack with default field values",
"data": {
"name": "",
"height": "",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 2: Required fields are not filled",
"data": {
"name": "",
"height": "",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 3: Only 'Height in units' field is filled",
"data": {
"name": "",
"height": "42",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 4: Only 'Depth (mm)' field is filled",
"data": {
"name": "",
"height": "",
"depth": "1000",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
}
]
# Выполняем тестовые случаи
for test_case in test_cases:
logger.info(test_case["name"])
self._perform_required_fields_test(
create_child_frame, rack_maker, test_case["data"]
)
logger.info("System prevented creating rack with invalid required fields")
# 5. Тест: Заполняем все обязательные поля
logger.info("Test 5: All required fields are filled")
# Генерируем уникальное имя для финального теста
final_rack_name = "Test-Rack-Required-Final"
# Создаем объект данных стойки
rack_data = RackData(
name=final_rack_name,
height="42",
depth="1000"
)
# Заполняем все обязательные поля
rack_maker.fill_rack_data(rack_data)
# Проверяем, что ни одно поле не подсвечено цветом ошибки
create_child_frame.check_field_error_not_highlighted("Имя")
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.info("No required fields are highlighted with error color - all fields filled correctly")
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(3000)
# Проверяем, что НЕТ alert-окон для всех обязательных полей
create_child_frame.alert.check_alert_absence(expected_alert_text_height, 1000)
create_child_frame.alert.check_alert_absence(expected_alert_text_depth, 1000)
logger.info("No alert windows for required fields appeared - all fields filled correctly")
# Проверяем, что ушли со страницы создания
try:
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.warning("Rack creation may not have completed successfully")
except AssertionError:
logger.info("Creation page closed - rack successfully created")
logger.info("Required fields validation test completed successfully")
def _check_rack_existance(self, browser: Page, rack_name: str) -> bool:
"""Проверяет существование стойки."""
logger.info(f"Checking existence of rack with name '{rack_name}'")
# Обновляем навигационную панель
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(3000)
nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента
element = browser.locator(nav_panel_locator).get_by_text(rack_name).first
if element.is_visible():
logger.info(f"Rack with name '{rack_name}' found")
return True
logger.info(f"Rack with name '{rack_name}' not found")
return False
def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку."""
logger.info(f"Creating rack with name '{rack_name}'")
# Переходим обратно к созданию новой стойки
self.main_page.click_main_navigation_panel_item("test-zone")
self.main_page.wait_for_timeout(2000)
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных стойки
rack_data = RackData(
name=rack_name,
height="42",
depth="1000"
)
# Заполняем данные стойки
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000)

View File

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

View File

@ -1,547 +0,0 @@
"""Модуль тестов редактирования стойки в модуле Объекты.
Содержит тесты для проверки функциональности
редактирования стойки оборудования.
"""
import os
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, EditRackData
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_EDIT_TESTS")
logger.setLevel("INFO")
class TestRackEdit:
"""Набор тестов для редактирования стойки в модуле Объекты.
Проверяет функциональность редактирования различных вкладок стойки:
1. Общая информация
2. Изображение
3. Правила доступа
"""
# Имя тестовой стойки
RACK_NAME = "Test-Rack-Edit"
# Инициализируем атрибуты
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):
"""Фикстура для очистки созданной стойки после ВСЕХ тестов класса.
Выполняется один раз после завершения всех тестов класса TestRackEdit.
Удаляет созданную стойку.
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_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.click_edit_button()
rack_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, self.RACK_NAME)
# Создаем тестовые данные для заполнения всех полей
rack_edit_data = EditRackData(
# Основные поля
name=self.RACK_NAME,
serial="SN123456789",
inventory="INV987654321",
comment="Тестовый комментарий для стойки (обновленный)",
allocated_power="5500",
# Combobox поля
cable_entry="сверху",
state="Введен в эксплуатацию",
owner="",
service_org="",
project="",
# Checkbox поля
ventilation_panel=True,
)
# Заполняем все поля формы
results = rack_edit.fill_rack_data(rack_edit_data)
logger.debug(f"Fill results: {results}")
# Сохраняем изменения
rack_edit.click_done_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")
# Вход в режим редактирования
rack_page.click_edit_button()
# Проверяем поля, пропуская недоступные
verification_results = rack_edit.verify_all_filled_fields(
rack_edit_data,
skip_fields=["Владелец", "Обслуживающая организация", "Проект/Титул"]
)
logger.debug(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()
logger.debug("General info tab test completed successfully")
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:
"""Тест вкладки 'Изображение' стойки."""
logger.debug(f"Starting image tab test for rack: {self.RACK_NAME}")
rack_page = RackPage(browser)
# Переходим в режим редактирования
rack_page.click_edit_button()
rack_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, self.RACK_NAME)
# Переключаемся на вкладку "Изображение"
rack_edit.switch_to_tab(EditRackMaker.TAB_IMAGE)
# Проверяем вкладку
assert rack_edit.is_tab_active(EditRackMaker.TAB_IMAGE), "Image tab should be active"
# Загружаем изображение если есть
test_image_path = os.path.join(os.path.dirname(__file__), "test_edit_rack_image.jpg")
if os.path.exists(test_image_path):
logger.debug(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)
rack_page.wait_for_timeout(2000)
logger.debug("Test image uploaded")
else:
logger.warning(f"Test image not found at: {test_image_path}")
# Сохраняем
rack_edit.click_done_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.debug("Image tab test completed successfully")
def test_rack_access_rules(self, browser: Page) -> None:
"""Тест заполнения полей правил доступа.
В каждое поле добавляются ВСЕ пользователи из списка custom_users.
"""
logger.debug(f"Starting access rules test for rack: {self.RACK_NAME}")
rack_page = RackPage(browser)
# Переходим в режим редактирования
rack_page.click_edit_button()
rack_page.wait_for_timeout(1000)
# Создаем экземпляр EditRackMaker
rack_edit = EditRackMaker(browser, self.RACK_NAME)
# Переключаемся на вкладку "Настройки"
rack_edit.switch_to_tab(EditRackMaker.TAB_SETTINGS)
# Проверяем, что вкладка активна
assert rack_edit.is_tab_active(EditRackMaker.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 = [
"admin",
"manager",
"operator",
"sec",
"collector"
]
# Заполняем поля
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.debug(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()
# Проверяем уведомление об успешном обновлении
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.wait_for_timeout(1000)
rack_edit = EditRackMaker(browser, self.RACK_NAME)
rack_edit.switch_to_tab(EditRackMaker.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()
logger.debug("Access rules test completed successfully")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,119 @@
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
import pytest
from playwright.sync_api import Page
from pages.rack_page import RackPage
from pages.login_page import LoginPage
from pages.main_page import MainPage
# @pytest.mark.smoke
class TestRackTab:
"""Набор тестов для вкладки 'Стойка' в модуле Объекты.
Проверяет корректность отображения, функциональность элементов интерфейса
и переключение между вкладками стойки оборудования.
Тесты покрывают следующие функциональные области:
1. test_rack_tab_content - Базовая структура и содержимое вкладки стойки
2. test_rack_tab_switching - Функциональность переключения между вкладками стойки
"""
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Выполняет:
1. Авторизацию в системе
2. Переход к стойке оборудования через панель навигации:
- Объекты Физические устройства с опросом Здание ЦОД 4 Стойка КСПД
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
# Авторизация в системе
lp = LoginPage(browser)
lp.do_login()
# Мы на главной странице
mp = MainPage(browser)
mp.should_be_navigation_panel()
mp.wait_for_timeout(3000)
# Переходим к Объектам
mp.click_main_navigation_panel_item("Объекты")
mp.wait_for_timeout(3000)
mp.click_subpanel_item("test-zone")
mp.wait_for_timeout(3000)
# Переходим к Стойка КСПД с указанием родителя
mp.click_subpanel_item("Test-Rack-01", parent="test-zone")
mp.wait_for_timeout(3000)
@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',
'Test-Rack-01'
]
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(2000)
def test_rack_tab_switching(self, browser: Page) -> None:
"""Тест переключения между вкладками стойки оборудования.
Проверяет функциональность переключения на все доступные вкладки:
1. Общая информация
2. Обслуживание
3. События
4. Сервисы
Проверяет:
1. Наличие и доступность всех вкладок
2. Корректность активации вкладок после переключения
3. Отсутствие ошибок при последовательном переключении
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
rt = RackPage(browser)
# Проверяем переключение между всеми вкладками стойки
rt.check_tab_switching()
# Переход в режим редактирования
rt.should_be_toolbar_buttons()
rt.wait_for_timeout(2000)

View File

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

View File

@ -1,406 +0,0 @@
"""Модуль тестов контейнера для отображения событий вкладки Действия.
Содержит тесты для проверки функциональности
контейнера для отображения событий вкладки Действия.
"""
import pytest
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
# @pytest.mark.smoke
class TestActionsEventsContainer:
"""Класс тестов для проверки контейнера для отображения событий вкладки Действия.
Атрибуты:
browser: Фикстура для работы с браузером.
"""
# @pytest.mark.develop
def test_actions_events_tab_content(self, browser: Page) -> None:
"""Проверяет содержимое контейнера для отображения событий вкладки Действия.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.check_content()
def test_events_tables_row_highlighting(self, browser: Page):
"""Проверяет выделение строк в таблицах событий: Реальное время и Архив.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.click_real_time_button()
# Получение количества строк в таблице Реальное время
rows_count = actions_events_container.get_events_table_rows_count()
if rows_count != 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)
if rows_count > 3:
actions_events_container.check_events_table_row_highlighting(int(rows_count / 2))
actions_events_container.click_archive_button()
# Получение количества строк в таблице Архив
rows_count = actions_events_container.get_events_table_rows_count()
if rows_count != 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)
if rows_count > 3:
actions_events_container.check_events_table_row_highlighting(int(rows_count / 2))
# @pytest.mark.develop
def test_events_table_scrolling(self, browser: Page):
"""Проверяет возможность скроллинга таблицы событий на примере таблицы Архив.
Args:
browser: Экземпляр страницы Playwright.
"""
rows_to_start_scrolling = 20
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
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()
# Проверка, что панель с таблицей открыта
assert events_panel_position != "bottom", "Panel with actions events should be opened"
is_scrollable = actions_events_container.check_events_table_verticall_scrolling()
# Убеждаемся, что панель открыта наполовину и проверяем скроллинг
assert events_panel_position == "center",\
"Panel with actions events should be located on the main page center"
assert is_scrollable, "Actions events table should be scrollable"
# Скроллинг вниз
actions_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
actions_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
actions_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
actions_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 actions events should be located on the main page top"
is_scrollable = actions_events_container.check_events_table_verticall_scrolling()
assert is_scrollable, "Actions events table should be scrollable in the full window"
# Скроллинг вниз
actions_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
actions_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
actions_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
actions_events_container.check_events_table_first_row_visibility()
else:
print("Not enough data to check vertical scrolling")
# @pytest.mark.develop
def test_real_time_task_view(self, browser: Page):
"""Проверяет содержимое окна вывода информации о задаче.
Args:
browser: Экземпляр страницы Playwright.
"""
index = 0
expected_task_headers = ['СООБЩЕНИЕ','СТАТУС', 'ОПИСАНИЕ']
expected_task_stages = []
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.click_real_time_button()
task_view_window = actions_events_container.click_events_table_row(index)
task_view_window.check_content()
title, task_id = task_view_window.get_window_title().split(" ")
assert title == "Task", "Window title should start with the word 'Task'"
stages_table_content = task_view_window.get_stages_table_content()
task_view_window.check_stages_table_headers(stages_table_content[0], expected_task_headers)
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},
"count": 40}
response = mp.send_post_api_request("e-nms/task_manager/showTaskWithFilter", payload)
if response.status == 200:
response_body = mp.get_response_body(response)
if response_body:
for item in response_body["data"]["data"]:
if item["id"] == task_id:
for stage in item["progress"]:
expected_task_stages.append(stage)
break
assert len(expected_task_stages) > 0, f"No expected task stages found for task with id {task_id}"
assert len (expected_task_stages) == len(stages_table_content), "Incorrect actual task stages list"
for i in range(len(expected_task_stages)):
expected_message = expected_task_stages[i]["message"]
actual_message = stages_table_content[i][0]
assert expected_message == actual_message, \
f"Expected message {expected_message} is not equal actual message {actual_message} for {i} row"
expected_status = expected_task_stages[i]["status"]
if expected_status == "ok":
expected_status = "Норма"
actual_status = stages_table_content[i][1]
assert expected_status == actual_status, \
f"Expected message {expected_status} is not equal actual message {actual_status} for {i} row"
task_view_window.close()
# TO-DO: Вернуть тесты после прояснения как должна отображаться информация в таблицах Реальное время и Архив
# def test_events_table_column_sorting(self, browser: Page):
# """Проверяет сортировку колонки 'Время' в таблице событий.
# Args:
# browser: Экземпляр страницы Playwright.
# """
# lp = LoginPage(browser)
# lp.do_login()
# mp = MainPage(browser)
# mp.should_be_event_panel()
# actions_events_container = mp.click_events_panel_actions_tab()
# index = 0
# state = actions_events_container.get_arrow_button_state(index)
# assert state == "down", "Arrow button should be down"
# actions_events_container.click_event_table_header_arrow(index)
# browser.wait_for_timeout(500)
# state = actions_events_container.get_arrow_button_state(index)
# assert state == "up", "Arrow button should be up"
# is_descending_order = actions_events_container.check_events_table_column_descending_order(index,
# convert2timestamp=True)
# assert not is_descending_order, "Column data should be in ascending order"
# actions_events_container.click_event_table_header_arrow(index)
# browser.wait_for_timeout(500)
# state = actions_events_container.get_arrow_button_state(index)
# assert state == "down", "Arrow button should be down"
# is_descending_order = actions_events_container.check_events_table_column_descending_order(index,
# convert2timestamp=True)
# assert is_descending_order, "Column data should be in descending order"
# @pytest.mark.develop
def test_real_time_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий на примере вкладки 'Реальное время'.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.click_real_time_button()
actions_events_container.should_be_pagination_buttons()
# Проверка начального состояния
tested_pages = 1
pages = 1
payload = {"filter": {"page": 1},
"count": 40}
response = mp.send_post_api_request("e-nms/task_manager/showTaskWithFilter", 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 = actions_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"
if tested_pages == 1:
actions_events_container.should_be_all_disabled()
else:
actions_events_container.should_be_initial_state()
# Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед
actions_events_container.click_last_page()
browser.wait_for_timeout(2000)
actions_events_container.should_be_final_state()
last_number = actions_events_container.get_current_data_set_number()
actions_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
if last_number == 2:
actions_events_container.should_be_initial_state()
else:
actions_events_container.should_be_all_enabled()
counter = last_number - 1
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()
browser.wait_for_timeout(4000)
actions_events_container.should_be_final_state()
current_number = actions_events_container.get_current_data_set_number()
assert current_number == last_number, \
f"Expected page number {last_number} is not equal actual {current_number}"
# Переход на первую страницу, переход на следующую страницу, возврат на первую страницу
actions_events_container.click_first_page()
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, f"Expected page number 1 is not equal actual {current_number}"
actions_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
if tested_pages == 2:
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 == 2, f"Expected page number 2 is not equal actual {current_number}"
actions_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
actions_events_container.should_be_initial_state()
current_number = actions_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:
actions_events_container.click_chevron_right()
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages:
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}"
# загрузка страниц от конца к началу
# to_do: проверка, что происходит обновление содержимого таблицы
while counter > 2:
actions_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
actions_events_container.should_be_all_enabled()
counter -= 1
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_left()
browser.wait_for_timeout(2000)
actions_events_container.should_be_initial_state()
current_number = actions_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"

View File

@ -1,314 +0,0 @@
"""Модуль тестов контейнера для отображения событий аудита.
Содержит тесты для проверки функциональности
контейнера для отображения событий аудита.
"""
# import pytest
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
# @pytest.mark.smoke
class TestAuditEventsContainer:
"""Класс тестов для проверки контейнера для отображения событий аудита.
Атрибуты:
browser: Фикстура для работы с браузером.
"""
# @pytest.mark.develop
def test_events_tab_content(self, browser: Page) -> None:
"""Проверяет содержимое контейнера для отображения событий аудита.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
audit_events_container = mp.click_events_panel_audit_tab()
audit_events_container.check_content()
def test_events_table_row_highlighting(self, browser: Page):
"""Проверяет выделение строк в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
audit_events_container = mp.click_events_panel_audit_tab()
# Получение количества строк в таблице
rows_count = audit_events_container.get_events_table_rows_count()
if rows_count != 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)
if rows_count > 3:
audit_events_container.check_events_table_row_highlighting(int(rows_count / 2))
def test_events_table_scrolling(self, browser: Page):
"""Проверяет возможность скроллинга таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
audit_events_container = mp.click_events_panel_audit_tab()
events_panel_position = mp.get_events_panel_position()
# Проверка, что панель с таблицей открыта
assert events_panel_position != "bottom", "Panel with system log events should be opened"
is_scrollable = audit_events_container.check_events_table_verticall_scrolling()
# Убеждаемся, что панель открыта наполовину и проверяем скроллинг
assert events_panel_position == "center",\
"Panel with system log events should be located on the main page center"
assert is_scrollable, "System log events table should be scrollable"
# Скроллинг вниз
audit_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
audit_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
audit_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
audit_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 system log events should be located on the main page top"
is_scrollable = audit_events_container.check_events_table_verticall_scrolling()
assert is_scrollable, "System log events table should be scrollable in the full window"
# Скроллинг вниз
audit_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
audit_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
audit_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
audit_events_container.check_events_table_first_row_visibility()
def test_events_table_column_sorting(self, browser: Page):
"""Проверяет сортировку колонки 'Время' в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
audit_events_container = mp.click_events_panel_audit_tab()
index = 0
state = audit_events_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
audit_events_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = audit_events_container.get_arrow_button_state(index)
assert state == "up", "Arrow button should be up"
is_descending_order = audit_events_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert not is_descending_order, "Column data should be in ascending order"
audit_events_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = audit_events_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
is_descending_order = audit_events_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert is_descending_order, "Column data should be in descending order"
def test_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
audit_events_container = mp.click_events_panel_audit_tab()
audit_events_container.should_be_pagination_buttons()
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "default",
"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)
if response_body:
pages = response_body["data"]["pages"]
if pages > 5:
tested_pages = 5
else:
tested_pages = pages
current_number = audit_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"
if tested_pages == 1:
audit_events_container.should_be_all_disabled()
else:
audit_events_container.should_be_initial_state()
# Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед
audit_events_container.click_last_page()
browser.wait_for_timeout(2000)
audit_events_container.should_be_final_state()
last_number = audit_events_container.get_current_data_set_number()
audit_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
if last_number == 2:
audit_events_container.should_be_initial_state()
else:
audit_events_container.should_be_all_enabled()
counter = last_number - 1
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}"
audit_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
audit_events_container.should_be_final_state()
current_number = audit_events_container.get_current_data_set_number()
assert current_number == last_number, \
f"Expected page number {last_number} is not equal actual {current_number}"
# Переход на первую страницу, переход на следующую страницу, возврат на первую страницу
audit_events_container.click_first_page()
browser.wait_for_timeout(2000)
audit_events_container.should_be_initial_state()
current_number = audit_events_container.get_current_data_set_number()
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
audit_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
if tested_pages == 2:
audit_events_container.should_be_final_state()
else:
audit_events_container.should_be_all_enabled()
current_number = audit_events_container.get_current_data_set_number()
assert current_number == 2, f"Expected page number 2 is not equal actual {current_number}"
audit_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
audit_events_container.should_be_initial_state()
current_number = audit_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:
audit_events_container.click_chevron_right()
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages:
audit_events_container.should_be_final_state()
else:
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}"
# загрузка страниц от конца к началу
# to_do: проверка, что происходит обновление содержимого таблицы
while counter > 2:
audit_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
audit_events_container.should_be_all_enabled()
counter -= 1
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}"
# Проверка возврата к начальному состоянию
audit_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
audit_events_container.should_be_initial_state()
current_number = audit_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"

View File

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

View File

@ -1,312 +0,0 @@
"""Модуль тестов контейнера для отображения событий системного журнала.
Содержит тесты для проверки функциональности
контейнера для отображения событий системного журнала.
"""
import pytest
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
# @pytest.mark.smoke
class TestEventsTabContainer:
"""Класс тестов для проверки контейнера для отображения событий вкладки События панели событий.
Атрибуты:
browser: Фикстура для работы с браузером.
"""
# @pytest.mark.develop
def test_events_tab_content(self, browser: Page) -> None:
"""Проверяет содержимое контейнера для отображения событий вкладки События панели событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
events_tab_container = mp.click_events_panel_events_tab()
events_tab_container.check_content()
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_row_highlighting(self, browser: Page):
"""Проверяет выделение строк в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
events_tab_container = mp.click_events_panel_events_tab()
# Получение количества строк в таблице
rows_count = events_tab_container.get_events_table_rows_count()
if rows_count != 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)
if rows_count > 3:
events_tab_container.check_events_table_row_highlighting(int(rows_count / 2))
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_scrolling(self, browser: Page):
"""Проверяет возможность скроллинга таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
events_tab_container = mp.click_events_panel_events_tab()
events_panel_position = mp.get_events_panel_position()
# Проверка, что панель с таблицей открыта
assert events_panel_position != "bottom", "Panel with system log events should be opened"
is_scrollable = events_tab_container.check_events_table_verticall_scrolling()
# Убеждаемся, что панель открыта наполовину и проверяем скроллинг
assert events_panel_position == "center",\
"Panel with system log events should be located on the main page center"
assert is_scrollable, "System log events table should be scrollable"
# Скроллинг вниз
events_tab_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
events_tab_container.check_events_table_last_row_visibility()
# Скроллинг вверх
events_tab_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
events_tab_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 system log events should be located on the main page top"
is_scrollable = events_tab_container.check_events_table_verticall_scrolling()
assert is_scrollable, "System log events table should be scrollable in the full window"
# Скроллинг вниз
events_tab_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
events_tab_container.check_events_table_last_row_visibility()
# Скроллинг вверх
events_tab_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
events_tab_container.check_events_table_first_row_visibility()
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_column_sorting(self, browser: Page):
"""Проверяет сортировку колонки 'Время' в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
events_tab_container = mp.click_events_panel_events_tab()
index = 0
state = events_tab_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
events_tab_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = events_tab_container.get_arrow_button_state(index)
assert state == "up", "Arrow button should be up"
is_descending_order = events_tab_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert not is_descending_order, "Column data should be in ascending order"
events_tab_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = events_tab_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
is_descending_order = events_tab_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert is_descending_order, "Column data should be in descending order"
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
events_tab_container = mp.click_events_panel_events_tab()
events_tab_container.should_be_pagination_buttons()
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "syslogs",
"filter": {"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)
if response_body:
pages = response_body["data"]["pages"]
if pages > 5:
tested_pages = 5
else:
tested_pages = pages
current_number = events_tab_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"
if tested_pages == 1:
events_tab_container.should_be_all_disabled()
else:
events_tab_container.should_be_initial_state()
# Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед
events_tab_container.click_last_page()
browser.wait_for_timeout(2000)
events_tab_container.should_be_final_state()
last_number = events_tab_container.get_current_data_set_number()
events_tab_container.click_chevron_left()
browser.wait_for_timeout(4000)
if last_number == 2:
events_tab_container.should_be_initial_state()
else:
events_tab_container.should_be_all_enabled()
counter = last_number - 1
current_number = events_tab_container.get_current_data_set_number()
assert current_number == counter, f"Expected page number {counter} is not equal actual {current_number}"
events_tab_container.click_chevron_right()
browser.wait_for_timeout(4000)
events_tab_container.should_be_final_state()
current_number = events_tab_container.get_current_data_set_number()
assert current_number == last_number, \
f"Expected page number {last_number} is not equal actual {current_number}"
# Переход на первую страницу, переход на следующую страницу, возврат на первую страницу
events_tab_container.click_first_page()
browser.wait_for_timeout(2000)
events_tab_container.should_be_initial_state()
current_number = events_tab_container.get_current_data_set_number()
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
events_tab_container.click_chevron_right()
browser.wait_for_timeout(4000)
if tested_pages == 2:
events_tab_container.should_be_final_state()
else:
events_tab_container.should_be_all_enabled()
current_number = events_tab_container.get_current_data_set_number()
assert current_number == 2, f"Expected page number 2 is not equal actual {current_number}"
events_tab_container.click_chevron_left()
browser.wait_for_timeout(4000)
events_tab_container.should_be_initial_state()
current_number = events_tab_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:
events_tab_container.click_chevron_right()
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages:
events_tab_container.should_be_final_state()
else:
events_tab_container.should_be_all_enabled()
current_number = events_tab_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:
events_tab_container.click_chevron_left()
browser.wait_for_timeout(2000)
events_tab_container.should_be_all_enabled()
counter -= 1
current_number = events_tab_container.get_current_data_set_number()
assert current_number == counter,\
f"Expected page number {counter} is not equal actual {current_number}"
# Проверка возврата к начальному состоянию
events_tab_container.click_chevron_left()
browser.wait_for_timeout(2000)
events_tab_container.should_be_initial_state()
current_number = events_tab_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"

View File

@ -1,318 +0,0 @@
"""Модуль тестов контейнера для отображения событий обслуживания.
Содержит тесты для проверки функциональности
контейнера для отображения событий обслуживания.
"""
import pytest
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
# @pytest.mark.smoke
class TestMaintenanceEventsContainer:
"""Класс тестов для проверки контейнера для отображения событий обслуживания.
Атрибуты:
browser: Фикстура для работы с браузером.
"""
# @pytest.mark.develop
def test_events_tab_content(self, browser: Page) -> None:
"""Проверяет содержимое контейнера для отображения событий обслуживания.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
maintenance_events_container = mp.click_events_panel_maintenance_tab()
maintenance_events_container.check_content()
def test_events_table_row_highlighting(self, browser: Page):
"""Проверяет выделение строк в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
maintenance_events_container = mp.click_events_panel_maintenance_tab()
# Получение количества строк в таблице
rows_count = maintenance_events_container.get_events_table_rows_count()
if rows_count != 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)
if rows_count > 3:
maintenance_events_container.check_events_table_row_highlighting(int(rows_count / 2))
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_scrolling(self, browser: Page):
"""Проверяет возможность скроллинга таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
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()
# Проверка, что панель с таблицей открыта
assert events_panel_position != "bottom", "Panel with system log events should be opened"
is_scrollable = maintenance_events_container.check_events_table_verticall_scrolling()
# Убеждаемся, что панель открыта наполовину и проверяем скроллинг
assert events_panel_position == "center",\
"Panel with system log events should be located on the main page center"
assert is_scrollable, "System log events table should be scrollable"
# Скроллинг вниз
maintenance_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
maintenance_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
maintenance_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
maintenance_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 system log events should be located on the main page top"
is_scrollable = maintenance_events_container.check_events_table_verticall_scrolling()
assert is_scrollable, "System log events table should be scrollable in the full window"
# Скроллинг вниз
maintenance_events_container.scroll_events_table_down()
browser.wait_for_timeout(1000)
# Проверка видимости последней строки после прокрутки
maintenance_events_container.check_events_table_last_row_visibility()
# Скроллинг вверх
maintenance_events_container.scroll_events_table_up()
browser.wait_for_timeout(1000)
# Проверка видимости первой строки после прокрутки
maintenance_events_container.check_events_table_first_row_visibility()
# @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_column_sorting(self, browser: Page):
"""Проверяет сортировку колонки 'Время' в таблице событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
maintenance_events_container = mp.click_events_panel_maintenance_tab()
index = 0
state = maintenance_events_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
maintenance_events_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = maintenance_events_container.get_arrow_button_state(index)
assert state == "up", "Arrow button should be up"
is_descending_order = maintenance_events_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert not is_descending_order, "Column data should be in ascending order"
maintenance_events_container.click_event_table_header_arrow(index)
browser.wait_for_timeout(500)
state = maintenance_events_container.get_arrow_button_state(index)
assert state == "down", "Arrow button should be down"
is_descending_order = maintenance_events_container.check_events_table_column_descending_order(index,
convert2timestamp=True)
assert is_descending_order, "Column data should be in descending order"
# @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
def test_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий.
Args:
browser: Экземпляр страницы Playwright.
"""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_event_panel()
maintenance_events_container = mp.click_events_panel_maintenance_tab()
maintenance_events_container.should_be_pagination_buttons()
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"id": [ "/physical"],
"notNull": True,
"data": {"links": {"flatten": True}},
"reducer": "events"
}
response = mp.send_post_api_request("e-cmdb/api/query", payload)
if response.status == 200:
response_body = mp.get_response_body(response)
if response_body:
pages = int(len(response_body)/40)
if (len(response_body) % 40) > 0:
pages = pages + 1
# print(f"pages = {pages}")
if pages > 5:
tested_pages = 5
else:
tested_pages = pages
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"
if tested_pages == 1:
maintenance_events_container.should_be_all_disabled()
else:
maintenance_events_container.should_be_initial_state()
# Переход на самую последнюю страницу, возврат на страницу назад, потом на страницу вперед
maintenance_events_container.click_last_page()
browser.wait_for_timeout(2000)
maintenance_events_container.should_be_final_state()
last_number = maintenance_events_container.get_current_data_set_number()
maintenance_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
if last_number == 2:
maintenance_events_container.should_be_initial_state()
else:
maintenance_events_container.should_be_all_enabled()
counter = last_number - 1
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == counter, f"Expected page number {counter} is not equal actual {current_number}"
maintenance_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
maintenance_events_container.should_be_final_state()
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == last_number, \
f"Expected page number {last_number} is not equal actual {current_number}"
# Переход на первую страницу, переход на следующую страницу, возврат на первую страницу
maintenance_events_container.click_first_page()
browser.wait_for_timeout(2000)
maintenance_events_container.should_be_initial_state()
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == 1, f"Expected page number 1 is not equal actual {current_number}"
maintenance_events_container.click_chevron_right()
browser.wait_for_timeout(4000)
if tested_pages == 2:
maintenance_events_container.should_be_final_state()
else:
maintenance_events_container.should_be_all_enabled()
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == 2, f"Expected page number 2 is not equal actual {current_number}"
maintenance_events_container.click_chevron_left()
browser.wait_for_timeout(4000)
maintenance_events_container.should_be_initial_state()
current_number = maintenance_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:
maintenance_events_container.click_chevron_right()
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages and counter == pages:
maintenance_events_container.should_be_final_state()
else:
maintenance_events_container.should_be_all_enabled()
current_number = maintenance_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:
maintenance_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
maintenance_events_container.should_be_all_enabled()
counter -= 1
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == counter,\
f"Expected page number {counter} is not equal actual {current_number}"
# Проверка возврата к начальному состоянию
maintenance_events_container.click_chevron_left()
browser.wait_for_timeout(2000)
maintenance_events_container.should_be_initial_state()
current_number = maintenance_events_container.get_current_data_set_number()
assert current_number == 1, "The first page should be number one"

View File

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

View File

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

View File

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

View File

@ -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,19 +91,16 @@ 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:
"""Тест проверки возможности увеличения/уменьшения значения выбранного поля формы с помощью """Тест проверки возможности увеличения/уменьшения значения выбранного поля формы с помощью стрелочек Вверх/Вниз.
стрелочек Вверх/Вниз.
""" """
field_name = 'operator' field_name = 'operator'

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