Compare commits

..

52 Commits

Author SHA1 Message Date
nsubbot e3804bd9c9 Актуализация тестов после перехода на версию приложения 1.40 2026-04-28 13:34:05 +03:00
Radislav a07cb43b80 Merge branch 'main' of http://192.168.2.61/AlexL/e-nms_qa_automation 2026-04-24 08:11:47 +03:00
Radislav 06e680675c fix: исправлен выбор пользователей в правилах доступа стойки
- Добавлен скроллинг в выпадающем списке
- Исправлено частичное совпадение имен пользователей
- Добавлена проверка на уже выбранных пользователей
2026-04-24 08:08:22 +03:00
nsubbot 18f7873145 Актуализация тестов панели событий в соответствии с последними изменениями UI 2026-04-16 14:06:05 +03:00
nsubbot e926b04a14 Актуализация тестов работы с пользователями в соответствии с последними изменениями UI 2026-04-15 09:45:48 +03:00
nsubbot 57d8a0466d Актуализация тестов панели 'Настройки' в соответствии с последними изменениями UI 2026-04-13 09:58:02 +03:00
nsubbot cab4f18f55 Актуализация тестов компоненент ввода даты в панелях фильтрации в соответствии с последними изменениями UI 2026-04-07 15:55:21 +03:00
nsubbot bfb4082a2d Актуализация тестов тестов скроллинга в соответствии с последними изменениями UI 2026-04-07 13:17:16 +03:00
nsubbot db50e80c51 Актуализация тестов после перехода на версию 1.39 2026-04-07 10:12:09 +03:00
nsubbot 82a28dda72 Добавлены тесты для работы с обязательными полями при пересоздании сертификатов 2026-04-03 10:51:30 +03:00
nsubbot 085d8c4ec7 Добавлены тесты для вкладки 'Сертификаты' 2026-04-02 14:23:46 +03:00
nsubbot 384ee4e15e Актуализация тестов после перехода на версию 1.38 2026-03-31 11:49:44 +03:00
nsubbot 036f86efad Актуализированы тесты панели событий 2026-03-23 15:20:25 +03:00
Radislav e1e166b878 Добавлены тесты поля Имя 2026-03-20 11:21:47 +03:00
Radislav 713cfd6126 Изменены тексты Alert окон 2026-03-13 09:42:49 +03:00
Radislav f075024386 refactor: унификация работы с формами создания и редактирования стоек
- Добавлен базовый класс BaseRackForm с общей логикой для работы с формами
2026-03-13 08:23:28 +03:00
Radislav b024fac0d8 refactor: переименовать InteractiveDropdownList в CheckboxGroupComponent
- Переименование и перенос компонента в папку components
- Обновление импортов
- Расширение функционала компонента
2026-03-12 15:45:17 +03:00
Radislav 0295852986 refactor: унификация работы с формами создания и редактирования стоек
- Добавлен базовый класс BaseRackForm с общей логикой для работы с формами
- Вынесена общая функциональность по заполнению полей, очистке и проверке ошибок
- Упрощены классы CreateRackForm и EditRackForm за счет наследования от BaseRackForm
- Обновлены зависимые компоненты (create_element_frame, edit_rack_maker)
- Исправлены тесты создания стойки с учетом новой архитектуры
2026-03-11 14:14:15 +03:00
Radislav 4fff4835f1 refactor: реорганизация структуры проекта
- Изменены (test_edit_rack.py, test_management_rack.py)
2026-03-06 11:54:03 +03:00
Radislav ca7c69c423 refactor: реорганизация структуры проекта
- Добавлены forms/ (create_rack_form.py, edit_rack_form.py)
- Добавлены makers/ (edit_rack_maker.py)
- Добавлены frames/ (create_element_frame.py)
- Добавлен тест test_create_rack.py
- Удалены устаревшие файлы из components_derived
- Обновлены alert_component.py и rack_locators.py
2026-03-06 11:41:06 +03:00
nsubbot 0509d5bee3 Добавлены тесты вкладки 'Резервное копирование' 2026-03-06 09:08:55 +03:00
nsubbot afb611dae9 Минорные исправления тестов после перехода на версию 1.33 2026-03-02 10:46:47 +03:00
Radislav 5f518f0aa7 Переименован test_image.jpg в test_edit_rack_image.jpg 2026-02-27 08:34:25 +03:00
Radislav dd87041fd0 Добавлена функциональность редактирования и управления стойками 2026-02-27 08:25:00 +03:00
Radislav 681b0ff99d тесты: обновление тестов для управления стойками 2026-02-25 13:43:03 +03:00
Radislav 04bf299194 Актуализация тестов после перехода на версию 1.33 2026-02-25 13:21:07 +03:00
nsubbot abf1a89bc3 Актуализация тестов после перехода на версию 1.33 2026-02-25 09:07:32 +03:00
Radislav c38cf601cb Добавлен тест редактирования Стойки 2026-02-20 13:12:29 +03:00
Radislav b5c1ee5d23 Добавлен тест редактирования Стойки 2026-02-20 11:45:39 +03:00
nsubbot 4ad79b108b Добавлена проверка индикатора статуса в панели навигации 2026-02-19 16:43:40 +03:00
nsubbot b8c3b55ebd Добавлен тест проверяющий невозможность отправки push-уведомления при незаполненных полях формы 2026-02-18 13:46:45 +03:00
nsubbot ba6bc49859 Восстановлен тест пагинации для вкладки Действия/Реальное время панели событий 2026-02-18 11:14:11 +03:00
nsubbot 6d36491d79 Кнопка расширения/сжатия рабочей области страницы добавлена в панель навигации 2026-02-17 14:28:01 +03:00
nsubbot 7612138c50 Актуализация тестов после перехода на версию 1.31 2026-02-11 18:57:26 +03:00
nsubbot f8f85300d3 Внесение изменений в локаторы и названия тулбаров после изменений в UI версии 1.31 2026-02-10 14:35:24 +03:00
nsubbot ce9ff3e27d Добавление в локаторы data-testid для элементов вкладки 'Настройки/Лицензии', 'Настройки/Пользователи', карточки текущего пользователя 2026-02-05 14:14:16 +03:00
nsubbot 1ad9ceb256 Добавление в локаторы data-testid для элементов вкладки 'Настройки/Обслуживание и диагностика/Сеансы/Настройки' 2026-02-04 15:06:20 +03:00
nsubbot 5960b27e0b Добавление в локаторы data-testid для элементов вкладки 'Настройки/Уведомления' 2026-02-04 14:59:07 +03:00
nsubbot 415924ec98 Добавлены тесты для вкладки 'Настройки/Аутентификация' 2026-02-03 10:03:28 +03:00
nsubbot 4e39172bc7 Актуализация тестов после перехода на версию 1.29 2026-01-30 14:01:39 +03:00
Radislav 3a09e0d602 fix(confirm_locators): обновлены локаторы для работы с диалогами подтверждения TITLE, TEXT 2026-01-28 14:57:24 +03:00
Radislav b46cd7870c feat(confirm_component): добавлена поддержка кастомных локаторов кнопок
- Добавлены опциональные параметры cancel_button_locator и allow_button_locator в конструктор
- Текстовые параметры cancel_button_text и allow_button_text опциональными с пустыми строками по умолчанию
- Добавлен приоритет локаторам над текстом кнопок при инициализации
- Сохранена обратная совместимость с существующим кодом
2026-01-28 14:52:11 +03:00
nsubbot 46a882d2c1 Добавлено модальное окно посылки тестовой СМС 2026-01-28 11:19:51 +03:00
nsubbot 29d8aa8354 Перевод локаторов элементов окон работы с пользователями на использование testid 2026-01-27 14:02:37 +03:00
nsubbot 891502d3ca Актуализация тестов после перехода на версию 1.28 2026-01-23 14:01:34 +03:00
nsubbot 18228df8c7 Первая версия тестов вкладок 'Уведомления/СМС' и 'Уведомления/E-mail' 2026-01-22 14:59:35 +03:00
nsubbot 004bb2b4d7 Добавлен тест панели событий для пользователя с ролью 'Специалист ИБ' 2026-01-21 09:58:50 +03:00
nsubbot 9da4097e8d Исправлена ошибка подсветки выбранной строки таблицы сервисов вызванная перекрытием элементов 2026-01-20 13:16:50 +03:00
Radislav e8f42aa480 refactor(test_element_rack): оптимизация тестов стойки оборудования 2026-01-19 13:59:46 +03:00
Radislav b270b45cbd style: исправлено предупреждения Pylint и улучшить код 2026-01-19 08:38:22 +03:00
nsubbot bd9768dc4e Актуализация тестов после перехода на версию 1.27.0 2026-01-16 14:27:37 +03:00
nsubbot 2ae53e3e2e Временный фикс проблемы перехода на https версию приложения 2026-01-15 14:02:21 +03:00
109 changed files with 9924 additions and 3721 deletions

3
.env.12 Normal file
View File

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

View File

@ -123,12 +123,13 @@ 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) -> None: def check_alert_presence(self, text: str, timeout: int = 30000) -> None:
"""Проверяет наличие alert-окна с заданным текстом. """Проверяет наличие alert-окна с заданным текстом.
Args: Args:
text: Текст для проверки. Если пустая строка - проверяет только text: Текст для проверки. Если пустая строка - проверяет только
наличие окна. наличие окна.
timeout: Время ожидания появления alert в миллисекундах
Raises: Raises:
AssertionError: Если alert-окно не найдено. AssertionError: Если alert-окно не найдено.
@ -136,12 +137,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(), msg expect(self.page.get_by_role(AlertLocators.ALERT_ROLE)).to_be_visible(timeout=timeout), 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(), msg ).filter(has_text=text)).to_be_visible(timeout=timeout), 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

@ -51,14 +51,14 @@ class BaseComponent:
fields_locators = {} fields_locators = {}
layouts = container_locator.locator("div.layout") layouts = container_locator.locator("div.layout > div.flex").locator("..")
for i in range(layouts.count()): for i in range(layouts.count()):
layout = layouts.nth(i) layout = layouts.nth(i)
flex_containers = layout.locator("div.flex") flex_containers = layout.locator("div.flex")
# Обрабатываем пары контейнеров # Обрабатываем пары контейнеров
for j in range(0, flex_containers.count() - 1): for j in range(0, flex_containers.count() - 1, 2):
label_container = flex_containers.nth(j) label_container = flex_containers.nth(j)
input_container = flex_containers.nth(j + 1) input_container = flex_containers.nth(j + 1)
@ -72,7 +72,9 @@ class BaseComponent:
"input, textarea, select" "input, textarea, select"
).count() > 0 ).count() > 0
if has_input: not_found = fields_locators.get(label_text) is None
if has_input and not_found:
fields_locators[label_text] = input_container fields_locators[label_text] = input_container
return fields_locators return fields_locators

View File

@ -0,0 +1,186 @@
"""Модуль компонента группы чек-боксов.
Содержит класс 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,31 +17,48 @@ 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) day_button_locator = days_table_locator.locator("//td").get_by_role("button", name=day, exact=True)
visible = day_button_locator.is_visible() visible = day_button_locator.is_visible()
if visible: if visible:
day_button_locator.click() day_button_locator.click()
@ -189,7 +189,7 @@ class DatePickerComponent(BaseComponent):
assert actual_month_year == expected_month_year, \ assert actual_month_year == expected_month_year, \
f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body" f"Expected value {expected_month_year} is not equal actual value {actual_month_year} on date picker body"
expected_day = str(expected_date.strftime("%d")) expected_day = str(expected_date.strftime("%d")).lstrip('0')
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,7 +82,12 @@ class DropdownList(BaseComponent):
loc = self.get_locator(locator) loc = self.get_locator(locator)
texts = loc.all_inner_texts() texts = loc.all_inner_texts()
return texts[0].splitlines() if len(texts) == 1 and texts[0].find("\n") != -1:
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

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

View File

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

View File

@ -1,72 +0,0 @@
"""Модуль 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,6 +3,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.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")
@ -20,6 +21,14 @@ 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:
"""Кликает по элементу с указанным текстом. """Кликает по элементу с указанным текстом.
@ -40,7 +49,20 @@ class NavigationPanelComponent(BaseComponent):
item_name: Текст элемента для клика. item_name: Текст элемента для клика.
""" """
def find_and_click_item(page, root_locator, item_name: str, parent: None|str) -> Locator|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 = 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()
@ -109,7 +131,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 = find_and_click_item( found_loc = self._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:
@ -133,17 +155,6 @@ 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]:
"""Возвращает тексты всех элементов по указанному локатору. """Возвращает тексты всех элементов по указанному локатору.
@ -224,13 +235,30 @@ 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) -> None: def check_item_visibility(self, locator: str | Locator, item_name: str, parent = None) -> None:
"""Проверяет видимость элемента с указанным текстом. """Проверяет видимость элемента с указанным текстом.
Args: Args:
locator: Локатор элемента или строка с CSS/XPath. locator: Локатор элемента или строка с CSS/XPath.
item_name: Текст элемента для проверки. item_name: Текст элемента для проверки.
parent: Текст родительского элемента (необязательный параметр)
Note: Note:
Временная обработка для элементов с текстом 'Шаблоны'. Временная обработка для элементов с текстом 'Шаблоны'.
@ -238,17 +266,13 @@ 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 item_name == "Шаблоны_1": if parent:
loc = loc.get_by_text("Шаблоны").first parent_loc = f"//div[contains(@class, 'v-treeview-node') and contains(.,'{parent}')]"
elif item_name == "Шаблоны_2": loc = loc.locator(parent_loc)
loc = loc.get_by_text("Шаблоны").nth(1) item_loc = loc.get_by_text(item_name).first
else:
loc = loc.get_by_text(item_name) self.check_visibility(item_loc, msg)
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:
""" """
@ -268,3 +292,57 @@ 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,43 +327,29 @@ 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, def check_mui_table_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
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()
bounding_box = row.evaluate("el => el.getBoundingClientRect()") self.page.wait_for_timeout(1000)
# center_x = (bounding_box["x"] + bounding_box["width"] / 2 + offset_x) * scale_x bounding_box = row.bounding_box()
# center_y = (bounding_box["y"] + bounding_box["height"] / 2 + offset_y) * scale_y assert bounding_box, "Requested row is not visible"
center_x = (bounding_box["x"] + bounding_box["width"] / 2) * scale_x + offset_x center_x = bounding_box["x"] + bounding_box["width"] / 4
center_y = (bounding_box["y"] + bounding_box["height"] / 2) * scale_y + offset_y center_y = bounding_box["y"] + bounding_box["height"] / 2
self.page.mouse.move(math.ceil(center_x), math.ceil(center_y), steps=5) self.page.mouse.move(math.ceil(center_x), math.ceil(center_y), steps=5)
self.page.wait_for_timeout(1000) self.page.wait_for_timeout(1000)

View File

@ -240,7 +240,7 @@ class ToolbarComponent(BaseComponent):
message (str): Сообщение об ошибке если тулбар не виден message (str): Сообщение об ошибке если тулбар не виден
""" """
locator = self.get_locator(locator).filter(has_text=self.title) locator = self.get_locator(locator).filter(has_text=self.title).first
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

@ -0,0 +1,39 @@
"""Модуль компонента тулбара (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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -153,7 +153,7 @@ class DateInput(BaseComponent):
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 == "00:00", \ assert current_time_value == "", \
"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:
@ -181,6 +181,7 @@ class DateInput(BaseComponent):
result = False result = False
inner_text = self.switch_mode_button.get_text(0).strip() inner_text = self.switch_mode_button.get_text(0).strip()
print(inner_text)
if inner_text == "keyboard": if inner_text == "keyboard":
result = True result = True
return result return result

View File

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

View File

@ -0,0 +1,93 @@
"""Модуль контейнера для импорта сертификата во вкладке 'Сертификаты'.
Содержит класс для работы с формой для импорта
сертификата во вкладке 'Сертификаты' через 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

@ -1,78 +0,0 @@
"""Модуль 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

@ -35,7 +35,6 @@ class AddUserModalWindow(ModalWindowComponent):
super().__init__(page) super().__init__(page)
# Локаторы элементов формы # Локаторы элементов формы
text_field_locator = f"//{ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}"
input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA
# Настройка заголовка и кнопки закрытия тулбара # Настройка заголовка и кнопки закрытия тулбара
@ -49,16 +48,17 @@ 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_fields_locators(page) elements_locators = self.get_input_fields_locators(page.locator(input_form_locator))
# print(elements_locators)
# Поле Тип авторизации # Поле Тип авторизации
loc = elements_locators.get("Тип авторизации") auth_type_loc = elements_locators.get("Тип авторизации")
if loc: if auth_type_loc:
auth_type_selector = SelectionBarComponent(page, loc.get_by_role("combobox")) auth_type_selector = SelectionBarComponent(page, auth_type_loc.get_by_role("combobox").first)
self.add_content_item("auth_type_selector", auth_type_selector) self.add_content_item("auth_type_selector", auth_type_selector)
# Поле Имя # Поле Имя
loc = elements_locators.get("Имя").locator(text_field_locator) loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
name_input = TextInput(page, loc, "name_input") name_input = TextInput(page, loc, "name_input")
self.add_content_item("name_input", name_input) self.add_content_item("name_input", name_input)
@ -76,34 +76,34 @@ class AddUserModalWindow(ModalWindowComponent):
# Чекбокс "Блокировка" # Чекбокс "Блокировка"
checkbox_blocking = Checkbox( checkbox_blocking = Checkbox(
page, page,
label_blocking_locator.locator("../..").get_by_role("checkbox"), page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED),
"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") role_loc = elements_locators.get("Роль").get_by_role("combobox").first
role_input = TextInput(page, role_loc, "role_input") role_input = TextInput(page, role_loc, "role_input")
self.add_content_item("role_input", role_input) self.add_content_item("role_input", role_input)
self.add_content_item("roles_list", DropdownList(page)) self.add_content_item("roles_list", DropdownList(page))
# Поле Пароль # Поле Пароль
loc = elements_locators.get("Пароль").locator(text_field_locator) loc = elements_locators.get("Пароль").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_PASSWORD)
password_input = TextInput(page, loc, "password_input") password_input = TextInput(page, loc, "password_input")
self.add_content_item("password_input", password_input) self.add_content_item("password_input", password_input)
# Поле Комментарий # Поле Комментарий
loc = elements_locators.get("Комментарий").locator(text_field_locator) loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT)
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(text_field_locator) loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL)
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(text_field_locator) loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS)
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)
@ -120,7 +120,7 @@ class AddUserModalWindow(ModalWindowComponent):
# Чекбокс "Подписка на Push-уведомления" # Чекбокс "Подписка на Push-уведомления"
checkbox_push = Checkbox( checkbox_push = Checkbox(
page, page,
label_push_locator.locator("../..").get_by_role("checkbox"), page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE),
"push_notification" "push_notification"
) )
self.add_content_item("push_notification_checkbox", checkbox_push) self.add_content_item("push_notification_checkbox", checkbox_push)
@ -192,7 +192,8 @@ class AddUserModalWindow(ModalWindowComponent):
if auth_type == "LDAP": if auth_type == "LDAP":
menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM) menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM)
elements_locators = self._get_fields_locators(self.page) 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_loc = elements_locators.get("Группа").get_by_role("combobox")
@ -223,7 +224,8 @@ class AddUserModalWindow(ModalWindowComponent):
search_button.click() search_button.click()
# Если в группе есть пользователи, открывается новое поле, заново вычисляем локаторы # Если в группе есть пользователи, открывается новое поле, заново вычисляем локаторы
elements_locators = self._get_fields_locators(self.page) 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("Пользователи AD")
# users_ad_loc = elements_locators.get("Пользователи LDAP") # users_ad_loc = elements_locators.get("Пользователи LDAP")
assert users_ad_loc, f"Selected group {group_name} is empty. Use another group." assert users_ad_loc, f"Selected group {group_name} is empty. Use another group."
@ -323,52 +325,40 @@ class AddUserModalWindow(ModalWindowComponent):
def locators_recalculation(self, is_active_directory=False) -> None: def locators_recalculation(self, is_active_directory=False) -> None:
"""Пересчет локаторов полей ввода""" """Пересчет локаторов полей ввода"""
text_field_locator = f"//{ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA}" elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
elements_locators = self._get_fields_locators(self.page) new_loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
new_loc = elements_locators.get("Имя").locator(text_field_locator)
self.get_content_item("name_input").update_locator(new_loc) self.get_content_item("name_input").update_locator(new_loc)
if not is_active_directory: if not is_active_directory:
new_loc = elements_locators.get("Пароль").locator(text_field_locator) new_loc = elements_locators.get("Пароль").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_PASSWORD)
self.get_content_item("password_input").update_locator(new_loc) self.get_content_item("password_input").update_locator(new_loc)
new_loc = elements_locators.get("Роль").get_by_role("combobox") new_loc = elements_locators.get("Роль").get_by_role("combobox").first
self.get_content_item("role_input").update_locator(new_loc) self.get_content_item("role_input").update_locator(new_loc)
new_loc = elements_locators.get("Комментарий").locator(text_field_locator) new_loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT)
self.get_content_item("commentary_input").update_locator(new_loc) self.get_content_item("commentary_input").update_locator(new_loc)
new_loc = elements_locators.get("E-mail").locator(text_field_locator) 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) self.get_content_item("email_input").update_locator(new_loc)
new_loc = elements_locators.get("Номер для СМС").locator(text_field_locator) new_loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS)
self.get_content_item("phone_input").update_locator(new_loc) self.get_content_item("phone_input").update_locator(new_loc)
def _get_fields_locators(self, page) -> dict:
fields_locators = {}
elements = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA). \
locator("div.v-text-field__slot > input").all()
for el in elements:
val = el.input_value().strip()
if val:
fields_locators[val] = el.locator("../ancestor::div[5]")
return fields_locators
# Проверки: # Проверки:
def check_content(self): def check_content(self):
"""Проверяет наличие и корректность всех элементов формы создания локального пользователя. """Проверяет наличие и корректность всех элементов формы создания локального пользователя.
Форма для создания keycloack пользователя имеет тот же набор полей. Форма для создания keycloack пользователя имеет тот же набор полей.
""" """
expected_auth_types = ['local', 'LDAP', 'keycloack'] expected_auth_types = ['local', 'LDAP', 'keycloak']
expected_roles = ['$collector', 'Администратор', expected_roles = ['$collector', 'Администратор',
'Специалист информационной безопасности', 'Специалист информационной безопасности',
'Контактное лицо', 'Оператор'] 'Контактное лицо', 'Оператор']
menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM) menu_locator = self.page.locator(ModalWindowLocators.MENU_ACTIVE_INPUT_FORM)
items_locator = menu_locator.locator(ModalWindowLocators.MENU_ACTIVE_ITEMS)
self.check_by_window_title() self.check_by_window_title()
@ -397,7 +387,7 @@ class AddUserModalWindow(ModalWindowComponent):
continue continue
assert current_auth_type == 'local', "Default Auth Type value should be 'local'" assert current_auth_type == 'local', "Default Auth Type value should be 'local'"
actual_auth_types = item.get_available_options(items_locator) actual_auth_types = item.get_available_options()
assert actual_auth_types == expected_auth_types, \ assert actual_auth_types == expected_auth_types, \
f"Actual auth types {actual_auth_types} are not equal expected values {expected_auth_types}." f"Actual auth types {actual_auth_types} are not equal expected values {expected_auth_types}."
elif name == "role_input": elif name == "role_input":
@ -419,8 +409,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"
) )
@ -443,7 +433,8 @@ class AddUserModalWindow(ModalWindowComponent):
if auth_type_selector: if auth_type_selector:
self.select_auth_type("LDAP") self.select_auth_type("LDAP")
elements_locators = self._get_fields_locators(self.page) 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_loc = elements_locators.get("Группа").get_by_role("combobox")

View File

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

View File

@ -34,8 +34,6 @@ 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
# Настройка заголовка и кнопки закрытия # Настройка заголовка и кнопки закрытия
@ -50,31 +48,31 @@ 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_fields_locators(page) elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Поле Имя # Поле Имя
loc = elements_locators.get("Имя").locator(text_field_locator) loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
name_input = TextInput(page, loc, "name_input") name_input = TextInput(page, loc, "name_input")
self.add_content_item("name_input", name_input) self.add_content_item("name_input", name_input)
# Поле Роль # Поле Роль
role_loc = self.page.locator(input_form_locator).get_by_role("combobox") role_loc = elements_locators.get("Роль").get_by_role("combobox").first
role_input = TextInput(page, role_loc, "role_input") role_input = TextInput(page, role_loc, "role_input")
self.add_content_item("role_input", role_input) self.add_content_item("role_input", role_input)
self.add_content_item("roles_list", DropdownList(page)) self.add_content_item("roles_list", DropdownList(page))
# Поле Комментарий # Поле Комментарий
loc = elements_locators.get("Комментарий").locator(text_field_locator) loc = elements_locators.get("Комментарий").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_COMMENT)
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(text_field_locator) loc = elements_locators.get("E-mail").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_EMAIL)
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(text_field_locator) loc = elements_locators.get("Номер для СМС").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_SMS)
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)
@ -93,7 +91,7 @@ class EditUserModalWindow(ModalWindowComponent):
# Чекбокс "Блокировка" # Чекбокс "Блокировка"
checkbox_blocking = Checkbox( checkbox_blocking = Checkbox(
page, page,
label_blocking_locator.locator("../..").get_by_role("checkbox"), page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED),
"blocking" "blocking"
) )
self.add_content_item("blocking_checkbox", checkbox_blocking) self.add_content_item("blocking_checkbox", checkbox_blocking)
@ -111,7 +109,7 @@ class EditUserModalWindow(ModalWindowComponent):
# Чекбокс "Подписка на Push-уведомления" # Чекбокс "Подписка на Push-уведомления"
checkbox_push = Checkbox( checkbox_push = Checkbox(
page, page,
label_push_locator.locator("../..").get_by_role("checkbox"), page.locator(input_form_locator).locator(ModalWindowLocators.INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE),
"push_notification" "push_notification"
) )
self.add_content_item("push_notification_checkbox", checkbox_push) self.add_content_item("push_notification_checkbox", checkbox_push)
@ -214,16 +212,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() checkbox.check(force=True)
else: else:
checkbox.uncheck() checkbox.uncheck(force=True)
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() checkbox.check(force=True)
else: else:
checkbox.uncheck() checkbox.uncheck(force=True)
save_button = self.get_button_by_name("save") save_button = self.get_button_by_name("save")
save_button.click() save_button.click()
@ -241,16 +239,6 @@ class EditUserModalWindow(ModalWindowComponent):
reset_password_button = self.get_button_by_name("reset_password") reset_password_button = self.get_button_by_name("reset_password")
reset_password_button.click() reset_password_button.click()
def _get_fields_locators(self, page) -> dict:
fields_locators = {}
elements = page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA). \
locator("div.v-text-field__slot > input").all()
for el in elements:
val = el.input_value().strip()
if val:
fields_locators[val] = el.locator("../ancestor::div[5]")
return fields_locators
# Проверки: # Проверки:
def check_content(self, user_name, role): def check_content(self, user_name, role):
"""Проверяет наличие и корректность элементов окна. """Проверяет наличие и корректность элементов окна.

View File

@ -0,0 +1,121 @@
"""Модуль 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

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

View File

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

View File

@ -0,0 +1,172 @@
"""Модуль контейнера для пересоздания сертификата во вкладке 'Сертификаты'.
Содержит класс для работы с формой для пересоздания
сертификата во вкладке 'Сертификаты' через 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,6 +44,7 @@ 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:
@ -54,6 +55,10 @@ 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]:
@ -86,8 +91,13 @@ 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)
return title_locator.text_content() if title_locator.count() > 0:
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]:
"""Возвращает список выбранных значений""" """Возвращает список выбранных значений"""
@ -95,6 +105,11 @@ 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()
@ -152,7 +167,16 @@ class SelectionBarComponent(BaseComponent):
self.selection_bar_locator.click(force=True) self.selection_bar_locator.click(force=True)
# Ждем появления выпадающего списка # Ждем появления выпадающего списка
self.wait_for_timeout(1500) if self.page.locator(SelectionBarLocators.LIST_ACTIVE).count() == 0:
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:
"""Выбор значения из списка""" """Выбор значения из списка"""
@ -161,7 +185,6 @@ 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:
"""Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена). """Проверяет, что поле подсвечено цветом ошибки (валидация не пройдена).
@ -213,3 +236,40 @@ 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,
"Session settings form toolbar is missing") "Settings form toolbar is missing")

View File

@ -15,7 +15,6 @@ from components_derived.modal_change_password import ChangePasswordModalWindow
logger = get_logger("USER_CARD") logger = get_logger("USER_CARD")
class UserCard(BaseComponent): class UserCard(BaseComponent):
"""Компонент карточка. """Компонент карточка.
@ -36,37 +35,37 @@ class UserCard(BaseComponent):
# Обновленные локаторы согласно новой структуре карточки # Обновленные локаторы согласно новой структуре карточки
self.current_user_name = Text( self.current_user_name = Text(
page, page,
card_locator.locator("xpath=/div/div[1]"), # Изменено с div[2] на div[1] card_locator.locator("xpath=/div[@class='v-card__text']/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/div[2]"), # Изменено с div[3] на div[2] card_locator.locator("xpath=/div[@class='v-card__text']/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/div[3]"), # Изменено с div[4] на div[3] card_locator.locator("xpath=/div[@class='v-card__text']/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/div[4]"), # Изменено с div[5] на div[4] card_locator.locator("xpath=/div[@class='v-card__text']/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,
page.get_by_role("button", name="Выйти"), card_locator.locator(UserCardLocators.BUTTON_LOGOUT),
"logout button" "logout button"
) )
self.change_password_button = Button( self.change_password_button = Button(
page, page,
page.get_by_role("button", name="Изменить пароль"), card_locator.locator(UserCardLocators.BUTTON_CHANGE_PASSWORD),
"change password button" "change password button"
) )
self.close_button = Button( self.close_button = Button(
page, page,
page.get_by_role("button", name="Закрыть"), card_locator.locator(UserCardLocators.BUTTON_CLOSE),
"close button" "close button"
) )
@ -148,6 +147,6 @@ class UserCard(BaseComponent):
Raises: Raises:
AssertionError: Если карточка пользователя все еще открыта. AssertionError: Если карточка пользователя все еще открыта.
""" """
card_locator = self.page.locator(UserCardLocators.CARD_USER).locator("xpath=../..") card_locator = self.page.locator(UserCardLocators.CARD_USER).locator("..")
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

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

View File

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

View File

@ -31,4 +31,13 @@ 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() self.locator.hover(force=True)
# Получение элемента подсказки # Получение элемента подсказки
tooltip = self.page.locator(ButtonLocators.TOOLTIP) tooltip = self.page.locator(ButtonLocators.TOOLTIP)
@ -82,3 +82,8 @@ 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()

469
forms/base_rack_form.py Normal file
View File

@ -0,0 +1,469 @@
"""Базовый модуль для работы с формами стойки."""
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

77
forms/create_rack_form.py Normal file
View File

@ -0,0 +1,77 @@
# 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

96
forms/edit_rack_form.py Normal file
View File

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

View File

@ -0,0 +1,48 @@
"""Модуль 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,6 +13,7 @@ class ButtonLocators:
- Кнопка удаления сессии - Кнопка удаления сессии
""" """
BUTTON_LICENSE_UPDATE = "//div[@class='scrollarea__footer']//button" BUTTON_LICENSE_UPDATE = "//button[@data-testid='LICENSE__btn__setLicense']"
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

@ -0,0 +1,63 @@
"""Модуль 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 = "//div[@class='v-card__title']/h3" TITLE = f"{CONFIRM}//div[contains(@class, 'v-card__title')]"
BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']" BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']"
TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']" TEXT = f"{CONFIRM}//div[contains(@class, 'v-card__text')]"

View File

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

View File

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

View File

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

View File

@ -24,10 +24,27 @@ 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']"
TEXT_FIELD_INPUT_FORM_USER_DATA = "div[2]/div/div/div/div/input" INPUT_FORM_USER_DATA_FIELD_NAME = "//input[@data-testid='USER_CARD__text-field__name']"
# TEXT_FIELD_INPUT_FORM_USER_DATA = "xpath=div[2]/div/div/div/div/input" INPUT_FORM_USER_DATA_FIELD_ROLE = "//input[@data-testid='USER_CARD__select__role']"
INPUT_FORM_USER_DATA_FIELD_PASSWORD = "//input[@data-testid='USER_CARD__text-field__password']"
INPUT_FORM_USER_DATA_FIELD_COMMENT = "//input[@data-testid='USER_CARD__text-field__comment']"
INPUT_FORM_USER_DATA_FIELD_EMAIL = "//input[@data-testid='USER_CARD__text-field__email']"
INPUT_FORM_USER_DATA_FIELD_SMS = "//input[@data-testid='USER_CARD__text-field__sms_phone']"
INPUT_FORM_USER_DATA_CHECKBOX_BLOCKED = "//input[@data-testid='USER_CARD__checkbox__blocked']"
INPUT_FORM_USER_DATA_CHECKBOX_PUSH_ACTIVE = "//input[@data-testid='USER_CARD__checkbox__push_active']"
# TEXT_FIELD_INPUT_FORM_USER_DATA = "div[2]/div/div/div/div/input"
MENU_ACTIVE_INPUT_FORM = "//div[contains(@class, 'menuable__content__active')]" MENU_ACTIVE_INPUT_FORM = "//div[contains(@class, 'menuable__content__active')]"
MENU_ACTIVE_ITEMS = "//div[@role='list']//div[@role='listitem']" 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']" 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,3 +29,6 @@ class NavigationPanelLocators:
NODE_ROOT = "//div[contains(@class,'v-treeview-node__root')]" NODE_ROOT = "//div[contains(@class,'v-treeview-node__root')]"
NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]" NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]"
TOGGLE_BUTTON = "//i[contains(@class,'v-treeview-node__toggle')]" TOGGLE_BUTTON = "//i[contains(@class,'v-treeview-node__toggle')]"
BUTTON_EXPAND_WORKAREA = "//button[@data-testid='BASELINE__btn__leftBarMini']"
BUTTON_REDUCE_WORKAREA = "//button[@data-testid='BASELINE__btn__!leftBarMini']"

View File

@ -28,41 +28,60 @@ class RackLocators:
ACTIVE_TAB = ("//div[@data-testid='CABINET_SHOW__tabs']" ACTIVE_TAB = ("//div[@data-testid='CABINET_SHOW__tabs']"
"//a[contains(@class, 'v-tabs__item--active')]") "//a[contains(@class, 'v-tabs__item--active')]")
# Контейнер формы создания/редактирования стойки # ================ ЛОКАТОРЫ ДЛЯ ФОРМЫ СОЗДАНИЯ СТОЙКИ ===================
FORM_INPUT_CONTAINER = "//div[contains(@class, 'flex xs6 pa-0')]"
# Локаторы полей формы создания стойки # Контейнер формы создания стойки
RACK_NAME_FIELD = ("//div[contains(@class, 'container')]" CREATE_RACK_FORM_CONTAINER = "//div[contains(@class, 'flex xs6 pa-0')]"
"//label[text()='Имя']/following-sibling::input")
RACK_HEIGHT_FIELD = ("//div[contains(@class, 'container')]"
"//div[contains(@class, 'v-input__slot') and "
".//label[text()='Высота в юнитах']]")
RACK_DEPTH_FIELD = ("//div[contains(@class, 'container')]"
"//div[contains(@class, 'v-input__slot') and "
".//label[text()='Глубина (мм)']]")
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') 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()='Проект/Титул']]")
# Локаторы для выпадающего меню # Text
CREATE_RACK_FORM_FIELD_NAME = "[data-testid='create-location-bar__text-field__name']"
CREATE_RACK_FORM_FIELD_COMMENT = "[data-testid='create-location-bar__text-field__comment']"
CREATE_RACK_FORM_FIELD_SERIAL = "[data-testid='create-location-bar__text-field__serial_number']"
CREATE_RACK_FORM_FIELD_INVENTORY = "[data-testid='create-location-bar__text-field__inventory_number']"
# Сombobox
CREATE_RACK_FORM_SELECT_USIZE = "[data-testid='create-location-bar__select__usize']"
CREATE_RACK_FORM_SELECT_DEPTH = "[data-testid='create-location-bar__select__depth']"
CREATE_RACK_FORM_SELECT_CABLE_INPUT = "[data-testid='create-location-bar__select__cable_input']"
CREATE_RACK_FORM_SELECT_CONDITION_TYPE = "[data-testid='create-location-bar__select__condition_type']"
CREATE_RACK_FORM_SELECT_OWNER = "[data-testid='create-location-bar__select__owner']"
CREATE_RACK_FORM_SELECT_SERVICE_PROVIDER = "[data-testid='create-location-bar__select__service_provider']"
CREATE_RACK_FORM_SELECT_PROJECT = "[data-testid='create-location-bar__select__project']"
# ================ ЛОКАТОРЫ ДЛЯ ФОРМЫ РЕДАКТИРОВАНИЯ СТОЙКИ ===================
# Форма редактирования стойки в модальном окне
EDIT_RACK_FORM = "[data-testid='cabinet-bar__cabinet-form']"
# 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_LIST = 'div.menuable__content__active div[role="list"]'
DROPDOWN_ITEM_BY_TEXT = ('div.menuable__content__active ' DROPDOWN_ITEM_BY_TEXT = ('div.menuable__content__active '
'div[role="listitem"]:has(span:has-text("{}"))') 'div[role="listitem"]:has(span:has-text("{}"))')
@ -126,3 +145,34 @@ class RackLocators:
# Кнопки подтверждения удаления # Кнопки подтверждения удаления
CONFIRM_REMOVE_YES_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__yes']" CONFIRM_REMOVE_YES_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__yes']"
CONFIRM_REMOVE_NO_BUTTON = "[data-testid='cabinet-bar__card_confirmation__btn__no']" 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,8 +19,9 @@ class SelectionBarLocators:
PARAMETERS_SELECTED = "div.v-select__selections" PARAMETERS_SELECTED = "div.v-select__selections"
# Локаторы для элементов выпадающего списка # Локаторы для элементов выпадающего списка
LIST_ACTIVE = "//div[contains(@class, 'menuable__content__active')]"
LISTBOX = "//div[@role='list']" LISTBOX = "//div[@role='list']"
LIST_ITEMS = "//div[@role='list']//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,7 +18,7 @@ class SettingsFormLocators:
SETTTINGS_FORM_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]" SETTTINGS_FORM_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]"
SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]" SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]"
SETTINGS_FORM_INPUT_FORM_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/following-sibling::div" SETTINGS_FORM_INPUT_FORM_CONTAINER = "//nav[contains(@class, 'active v-toolbar')]/../following-sibling::div"
SETTINGS_FORM_INPUT_FIELD = "div.v-text-field__slot > input" SETTINGS_FORM_INPUT_FIELD = "div.v-text-field__slot > input"
SETTINGS_FORM_INPUT_VALUE_SUFFIX = ".v-text-field__suffix" SETTINGS_FORM_INPUT_VALUE_SUFFIX = ".v-text-field__suffix"
@ -26,3 +26,5 @@ class SettingsFormLocators:
DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]" DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]"
SELECTED_VALUES = "//div[@class='v-select__selections']" SELECTED_VALUES = "//div[@class='v-select__selections']"
CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear" CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear"
PUSH_NOTIFICATIONS_BUTTON_SUBMIT = "//button[@data-testid='PUSH_NOTIFICATIONS__btn__submit']"

View File

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

1168
makers/edit_rack_maker.py Normal file

File diff suppressed because it is too large Load Diff

1048
pages/backup_settings_tab.py Normal file

File diff suppressed because it is too large Load Diff

178
pages/certificates_tab.py Normal file
View File

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

View File

@ -1,355 +0,0 @@
"""Модуль страницы создания дочернего элемента.
Содержит класс для работы с формой создания дочернего элемента.
"""
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

@ -1,678 +0,0 @@
"""Модуль страницы создания дочернего элемента.
Содержит класс для работы с формой создания дочернего элемента.
"""
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

@ -0,0 +1,484 @@
"""Модуль вкладки настройки 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

@ -0,0 +1,211 @@
"""Модуль вкладки настройки 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')

346
pages/ldap_settings_tab.py Normal file
View File

@ -0,0 +1,346 @@
"""Модуль вкладки настройки 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).first loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER)
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).first loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER)
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).first loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER)
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

@ -103,6 +103,16 @@ 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:
"""Выполняет нажатие кнопки пользователя.""" """Выполняет нажатие кнопки пользователя."""
@ -195,7 +205,7 @@ class MainPage(BasePage):
item_name item_name
) )
def check_navigation_panel_item_visibility(self, item_name: str) -> None: def check_navigation_panel_item_visibility(self, item_name: str, parent=None) -> None:
"""Проверяет видимость элемента в панели навигации. """Проверяет видимость элемента в панели навигации.
Args: Args:
@ -204,9 +214,28 @@ class MainPage(BasePage):
self.navigation_panel.check_item_visibility( self.navigation_panel.check_item_visibility(
NavigationPanelLocators.PANEL_MAIN, NavigationPanelLocators.PANEL_MAIN,
item_name item_name, parent
) )
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:
"""Проверяет возможность вертикальной прокрутки панели. """Проверяет возможность вертикальной прокрутки панели.
@ -233,3 +262,13 @@ 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,14 +4,12 @@
Позволяет проверять состояние и взаимодействовать с элементами вкладки. Позволяет проверять состояние и взаимодействовать с элементами вкладки.
""" """
import re
from playwright.sync_api import Page from playwright.sync_api import Page
from locators.settings_form_locators import SettingsFormLocators from locators.settings_form_locators import SettingsFormLocators
from elements.text_input_element import TextInput from elements.text_input_element import TextInput
from components.toolbar_component import ToolbarComponent
from components.alert_component import AlertComponent from components.alert_component import AlertComponent
from components_derived.settings_form_component import SettingsFormComponent from components_derived.settings_form_component import SettingsFormComponent
from components_derived.interactive_dropdown_list import InteractiveDropdownList from components.checkbox_group_component import CheckboxGroupComponent # Изменен импорт
from pages.base_page import BasePage from pages.base_page import BasePage
@ -29,35 +27,28 @@ 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("Общие") self.settings_form.add_toolbar_title("Push уведомления")
container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER) container_locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator) self.input_fields_locators = self.settings_form.get_input_fields_locators(container_locator)
print(self.input_fields_locators)
loc = self.input_fields_locators.get("Сообщение") loc = self.input_fields_locators.get("Сообщение")
loc_message_input = loc.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FIELD).first loc_message_input = loc.locator("//input[@data-testid='PUSH_NOTIFICATIONS__text-field__message']")
message_setting_input = TextInput(page, loc_message_input, "message_setting_input") message_setting_input = TextInput(page, loc_message_input, "message_setting_input")
self.settings_form.add_content_item("message_setting_input", message_setting_input) self.settings_form.add_content_item("message_setting_input", message_setting_input)
loc = self.input_fields_locators.get("Пользователи") loc = self.input_fields_locators.get("Пользователи")
users_setting_input = TextInput(page, users_setting_input = TextInput(page,
loc.get_by_role("combobox"), loc.get_by_role("combobox"),
"users_setting_input") "users_setting_input")
# users_setting_input = TextInput(page,
# page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\
# get_by_role("combobox"),
# "users_setting_input")
self.settings_form.add_content_item("users_setting_input", users_setting_input) self.settings_form.add_content_item("users_setting_input", users_setting_input)
self.settings_form.add_content_item("users_list", InteractiveDropdownList(page)) # Используем новый компонент CheckboxGroupComponent
self.settings_form.add_content_item("users_checkbox_group", CheckboxGroupComponent(page))
self.settings_form.add_tooltip_button(page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ self.settings_form.add_tooltip_button(page.locator(SettingsFormLocators.PUSH_NOTIFICATIONS_BUTTON_SUBMIT),
get_by_role("button", name='Отправить'),
"submit_button") "submit_button")
self.alert = AlertComponent(page) self.alert = AlertComponent(page)
@ -68,7 +59,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.SETTTINGS_FORM_SCROLL_CONTAINER).\ clear_selection_button = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_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()
@ -95,7 +86,7 @@ class PushNotificationsSettingsTab(BasePage):
str : Текущее значение поля настроек 'Пользователи'. str : Текущее значение поля настроек 'Пользователи'.
""" """
users_setting_field_loc = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ users_setting_field_loc = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_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()
@ -113,10 +104,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_list = self.settings_form.get_content_item("users_list") users_checkbox_group = self.settings_form.get_content_item("users_checkbox_group")
for user in users: for user in users:
users_list.deselect_item_with_text(user) users_checkbox_group.uncheck_by_text(user)
# Закрываем выпадающий список (кликаем вне его) # Закрываем выпадающий список (кликаем вне его)
self.page.mouse.click(10, 10) self.page.mouse.click(10, 10)
@ -127,10 +118,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_list = self.settings_form.get_content_item("users_list") users_checkbox_group = self.settings_form.get_content_item("users_checkbox_group")
for user in users: for user in users:
users_list.select_item_with_text(user) users_checkbox_group.check_by_text(user)
# Закрываем выпадающий список (кликаем вне его) # Закрываем выпадающий список (кликаем вне его)
self.page.mouse.click(10, 10) self.page.mouse.click(10, 10)
@ -141,19 +132,20 @@ class PushNotificationsSettingsTab(BasePage):
expected_input_field_names = ["Сообщение", "Пользователи"] 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() actual_input_field_names = self.input_fields_locators.keys()
assert set(actual_input_field_names) == set(expected_input_field_names), \ assert set(actual_input_field_names) == set(expected_input_field_names), \
f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}" f"Misscomparison input field names: Expected {expected_input_field_names}, Actual {actual_input_field_names}"
for name in self.settings_form.content_items.keys(): for name in self.settings_form.content_items.keys():
if name == "users_list": if name == "users_checkbox_group":
self.settings_form.get_content_item("users_setting_input").click() self.settings_form.get_content_item("users_setting_input").click()
users_list = self.settings_form.get_content_item(name) users_checkbox_group = self.settings_form.get_content_item(name)
selected_users = users_list.get_selected_items(SettingsFormLocators.DROPDOWN_LIST) selected_users = users_checkbox_group.get_checked_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)
@ -161,19 +153,6 @@ 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:
"""Проверяет наличие тулбара формы редактирования настроек. """Проверяет наличие тулбара формы редактирования настроек.
@ -187,7 +166,7 @@ class PushNotificationsSettingsTab(BasePage):
"""Проверяет наличие сообщения об успешной отправке push-уведомления. """Проверяет наличие сообщения об успешной отправке push-уведомления.
Raises: Raises:
AssertionError: Если тулбар отсутствует. AssertionError: Если alert отсутствует.
""" """
alert_type = self.alert.get_alert_type() alert_type = self.alert.get_alert_type()
@ -195,3 +174,13 @@ 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

@ -48,103 +48,19 @@ class RackPage(BasePage):
show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON) show_button_locator = self.page.locator(RackLocators.SHOW_RACK_BUTTON)
self.show_button = TooltipButton(page, show_button_locator, "show_rack") self.show_button = TooltipButton(page, show_button_locator, "show_rack")
# Кнопка "Переместить"
replace_button_locator = self.page.locator(RackLocators.TOOLBAR_REPLACE_BUTTON)
self.replace_button = TooltipButton(page, replace_button_locator, "replace")
# Кнопка "Сохранить"
done_button_locator = self.page.locator(RackLocators.TOOLBAR_DONE_BUTTON)
self.done_button = TooltipButton(page, done_button_locator, "done")
# Кнопка "Отменить"
close_button_locator = self.page.locator(RackLocators.TOOLBAR_CLOSE_BUTTON)
self.close_button = TooltipButton(page, close_button_locator, "close")
# Кнопка "Удалить"
remove_button_locator = self.page.locator(RackLocators.TOOLBAR_REMOVE_BUTTON)
self.remove_button = TooltipButton(page, remove_button_locator, "remove")
self.toolbar = ToolbarComponent(page, "") self.toolbar = ToolbarComponent(page, "")
self.toolbar.add_tooltip_button(locator_button, "edit") self.toolbar.add_tooltip_button(locator_button, "edit")
self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack") self.toolbar.add_tooltip_button(hide_button_locator, "hide_rack")
self.toolbar.add_tooltip_button(show_button_locator, "show_rack") self.toolbar.add_tooltip_button(show_button_locator, "show_rack")
self.toolbar.add_tooltip_button(replace_button_locator, "replace")
self.toolbar.add_tooltip_button(done_button_locator, "done")
self.toolbar.add_tooltip_button(close_button_locator, "close")
self.toolbar.add_tooltip_button(remove_button_locator, "remove")
# Действия # Действия
def click_remove_button(self) -> None: def click_edit_button(self) -> None:
""" """ Кликает на кнопку 'Изменить'."""
Кликает на кнопку 'Удалить' и обрабатывает диалог подтверждения.
"""
logger.debug("Clicking on 'Remove' button...")
# Проверяем видимость кнопки self.toolbar.get_button_by_name("edit").click()
self.toolbar.check_button_visibility("remove")
# Проверяем тултип кнопки (может быть "Удалить" или "Remove")
try:
self.toolbar.check_button_tooltip("remove", "Удалить")
except AssertionError:
try:
self.toolbar.check_button_tooltip("remove", "Remove")
except AssertionError:
logger.debug("Could not verify tooltip text for remove button")
# Кликаем на кнопку удаления
self.toolbar.get_button_by_name("remove").click()
self.wait_for_timeout(1000) self.wait_for_timeout(1000)
# Ожидаем появления диалога подтверждения
self._handle_remove_confirmation_dialog()
def confirm_remove_dialog(self, confirm: bool = True) -> None:
"""
Подтверждает или отклоняет удаление в диалоговом окне.
Args:
confirm (bool): Если True - подтвердить удаление, если False - отменить
"""
logger.debug(f"Confirming remove dialog with: {'Да' if confirm else 'Нет'}")
# Ждем немного перед поиском диалога
self.wait_for_timeout(1500)
# Ищем активный диалог
dialog = self.page.locator("div.v-dialog--active")
# Проверяем, что диалог найден и содержит нужный текст
assert dialog.count() > 0, "No active dialog found"
# Проверяем текст диалога
dialog_text = dialog.first.text_content()
logger.debug("Dialog text: %s", dialog_text)
# Должен содержать "Запрос подтверждения" и "Удалить"
assert "Запрос подтверждения" in dialog_text, "Not a confirmation dialog"
# Ищем кнопку по data-testid
if confirm:
button = self.page.locator(RackLocators.CONFIRM_REMOVE_YES_BUTTON)
else:
button = self.page.locator(RackLocators.CONFIRM_REMOVE_NO_BUTTON)
# Проверяем, что кнопка найдена
assert button.count() > 0, "Button not found with selector"
# Кликаем на кнопку
button_text = button.first.text_content()
logger.debug("Clicking button with text: %s", button_text)
button.first.click()
self.wait_for_timeout(2000)
# Проверяем, что диалог закрылся
self.wait_for_timeout(1000)
logger.debug("Remove confirmation completed")
def get_available_tabs(self) -> list[str]: def get_available_tabs(self) -> list[str]:
""" """
Возвращает список доступных вкладок. Возвращает список доступных вкладок.
@ -534,20 +450,8 @@ class RackPage(BasePage):
self.toolbar.check_button_tooltip("edit", "Изменить") self.toolbar.check_button_tooltip("edit", "Изменить")
# Кликаем на кнопку "Изменить" для проверки функциональности # Кликаем на кнопку "Изменить" для проверки функциональности
self.toolbar.get_button_by_name("edit").click() #self.toolbar.get_button_by_name("edit").click()
# Проверяем новые кнопки тулбара
self.toolbar.check_button_visibility("replace")
self.toolbar.check_button_tooltip("replace", "Переместить")
self.toolbar.check_button_visibility("done")
self.toolbar.check_button_tooltip("done", "Сохранить")
self.toolbar.check_button_visibility("close")
self.toolbar.check_button_tooltip("close", "Отменить")
self.toolbar.check_button_visibility("remove")
self.toolbar.check_button_tooltip("remove", "Удалить")
def should_have_hide_rack_button(self) -> None: def should_have_hide_rack_button(self) -> None:
""" """
@ -690,11 +594,6 @@ class RackPage(BasePage):
logger.debug("%s check completed successfully", side_name) logger.debug("%s check completed successfully", side_name)
def _handle_remove_confirmation_dialog(self) -> None:
"""Обрабатывает диалог подтверждения удаления."""
logger.debug("Handling remove confirmation dialog...")
self.confirm_remove_dialog(confirm=True)
def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None: def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None:
""" """
Ожидает активации вкладки. Ожидает активации вкладки.

View File

@ -1,466 +0,0 @@
"""Модуль тестов вкладки 'Стойка'.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
from playwright.sync_api import Page, expect
from elements.tooltip_button_element import TooltipButton
from components.toolbar_component import ToolbarComponent
from pages.base_page import BasePage
from locators.rack_locators import RackLocators
from tools.logger import get_logger
logger = get_logger("RACK_TAB")
# Специфичные локаторы оставленые в основном коде
PANEL_HEADER = "//span[text()='Объекты']/following-sibling::i"
TOOLBAR_CONTENT = "//div[@class='v-toolbar__content']"
EDIT_BUTTON_ANCESTOR_DIV3 = "xpath=/ancestor::div[3]//button"
PANEL_HEADER_ANCESTOR_DIV2 = "xpath=/ancestor::div[2]"
class RackTab(BasePage):
"""Класс для работы с вкладкой стойки оборудования."""
def __init__(self, page: Page) -> None:
"""
Инициализирует объект вкладки стойки.
Args:
page: Экземпляр страницы Playwright
"""
super().__init__(page)
locator_button = self.page.locator(PANEL_HEADER).\
locator(EDIT_BUTTON_ANCESTOR_DIV3).nth(0)
self.edit_button = TooltipButton(page, locator_button, "edit")
self.toolbar = ToolbarComponent(page, "")
self.toolbar.add_tooltip_button(locator_button, "edit")
def wait_for_rack_loading(self, timeout: int = 15000) -> None:
"""
Ожидает загрузки интерфейса стойки.
Args:
timeout: Время ожидания в миллисекундах (по умолчанию 15000)
Raises:
TimeoutError: Если загрузка не завершилась в указанное время
"""
logger.info("Ожидание загрузки интерфейса стойки...")
# Ждем появления основного контейнера
main_container = self.page.locator(RackLocators.MAIN_CONTAINER)
expect(main_container).to_be_visible(timeout=timeout)
# Ждем появления юнитов
units = self.page.locator(RackLocators.ALL_UNITS)
expect(units).to_have_count(20, timeout=timeout)
logger.info("Интерфейс стойки загружен")
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 switch_to_tab(self, tab_name: str) -> None:
"""
Переключается на указанную вкладку.
Args:
tab_name: Название вкладки для переключения
Raises:
AssertionError: Если вкладка не найдена или недоступна
"""
logger.info(f"Переключение на вкладку '{tab_name}'...")
tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name))
if tab.count() == 0:
raise AssertionError(f"Вкладка '{tab_name}' не найдена")
# Проверяем активность ДО клика
if self.is_tab_active(tab_name):
logger.info(f"Вкладка '{tab_name}' уже активна")
return
# Находим первую видимую вкладку с нужным именем
target_tab = None
for i in range(tab.count()):
element = tab.nth(i)
if element.is_visible() and element.is_enabled():
target_tab = element
break
if not target_tab:
raise AssertionError(f"Не найдена видимая/доступная вкладка '{tab_name}'")
# Кликаем на вкладку
logger.info(f"Клик на вкладку '{tab_name}'...")
target_tab.click()
# Ждем изменения активной вкладки
self._wait_for_tab_activation(tab_name)
# Ждем загрузки контента
self.page.wait_for_timeout(500)
def switch_to_general_info_tab(self) -> None:
"""Переключается на вкладку 'Общая информация'."""
self.switch_to_tab("Общая информация")
def switch_to_maintenance_tab(self) -> None:
"""Переключается на вкладку 'Обслуживание'."""
self.switch_to_tab("Обслуживание")
def switch_to_events_tab(self) -> None:
"""Переключается на вкладку 'События'."""
self.switch_to_tab("События")
def switch_to_services_tab(self) -> None:
"""Переключается на вкладку 'Сервисы'."""
self.switch_to_tab("Сервисы")
def is_tab_active(self, tab_name: str) -> bool:
"""
Проверяет, активна ли указанная вкладка.
Args:
tab_name: Название вкладки для проверки
Returns:
bool: True если вкладка активна, False в противном случае
"""
# Метод 1: Проверяем по активному классу и тексту, метод быстый, если надо универсальный оставояем метод 2 - медленный
active_tab = self.page.locator(RackLocators.ACTIVE_TAB)
if active_tab.count() > 0 and active_tab.first.is_visible():
active_text = active_tab.first.text_content()
if active_text and active_text.strip() == tab_name:
logger.info(f"Вкладка '{tab_name}' активна (через класс активной вкладки)")
return True
# Метод 2: Проверяем по классам у конкретной вкладки
tab = self.page.locator(RackLocators.TAB_BY_NAME.format(tab_name))
if tab.count() > 0:
for i in range(tab.count()):
element = tab.nth(i)
if element.is_visible() and element.is_enabled():
element_class = element.get_attribute("class") or ""
is_active = any(
active_class in element_class
for active_class in RackLocators.ACTIVE_TAB_CLASSES
)
if is_active:
logger.info(f"Вкладка '{tab_name}' активна (классы: {element_class})")
return True
logger.info(f"Вкладка '{tab_name}' не активна")
return False
def get_available_tabs(self) -> list[str]:
"""
Возвращает список доступных вкладок используя DOM структуру.
Returns:
list[str]: Список названий доступных вкладок
"""
tabs = []
# Используем локатор для верхних вкладок
tab_elements = self.page.locator(RackLocators.ALL_TABS)
# Ждем появления элементов
tab_elements.first.wait_for(state="visible", timeout=5000)
total_count = tab_elements.count()
logger.info(f"Всего найдено элементов верхних вкладок: {total_count}")
for i in range(total_count):
element = tab_elements.nth(i)
# Проверяем видимость и доступность элемента
if element.is_visible() and element.is_enabled():
tab_text = element.text_content()
if tab_text:
tab_text = tab_text.strip()
if tab_text and tab_text not in tabs:
tabs.append(tab_text)
logger.info(f"Найдена верхняя вкладка: '{tab_text}'")
logger.info(f"Найдены доступные верхние вкладки: {tabs}")
return tabs
def _wait_for_tab_activation(self, tab_name: str, timeout: int = 5000) -> None:
"""
Ожидает активации вкладки.
Args:
tab_name: Название вкладки для ожидания
timeout: Время ожидания в миллисекундах
Raises:
AssertionError: Если вкладка не активирована в течение таймаута
"""
logger.info(f"Ожидание активации вкладки '{tab_name}'...")
start_time = self.page.evaluate("Date.now()")
while self.page.evaluate("Date.now()") - start_time < timeout:
if self.is_tab_active(tab_name):
logger.info(f"Вкладка '{tab_name}' успешно активирована")
return
self.page.wait_for_timeout(100)
raise AssertionError(f"Вкладка '{tab_name}' не активирована в течение {timeout}мс")
def should_be_toolbar_buttons(self) -> None:
"""
Проверяет наличие и функциональность кнопок тулбара.
Raises:
AssertionError: Если кнопки недоступны или подсказки неверны.
"""
logger.info("Проверка кнопок панели инструментов...")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_tooltip("edit", "Изменить")
self.toolbar.get_button_by_name("edit").click()
def should_be_rack_sides_displayed(self) -> None:
"""
Проверка отображения и структуры сторон стойки.
Raises:
AssertionError: Если стороны стойки не отображаются корректно
"""
logger.info("Проверка отображения и структуры сторон стойки...")
# Ожидаем загрузки
self.wait_for_rack_loading()
# БАЗОВАЯ ПРОВЕРКА: обе стороны отображаются
logger.info("--- Базовая проверка отображения сторон ---")
front_side_section = self.page.locator(RackLocators.FRONT_SIDE_SECTION).first
expect(front_side_section).to_be_visible(timeout=10000)
logger.info("Секция лицевой стороны найдена")
back_side_section = self.page.locator(RackLocators.BACK_SIDE_SECTION).first
expect(back_side_section).to_be_visible(timeout=10000)
logger.info("Секция обратной стороны найдена")
# Проверяем заголовки
front_side_title = front_side_section.locator(RackLocators.FRONT_SIDE_TITLE)
expect(front_side_title).to_be_visible(timeout=5000), "Заголовок 'Лицевая сторона' не отображается"
logger.info("Заголовок 'Лицевая сторона' отображается")
back_side_title = back_side_section.locator(RackLocators.BACK_SIDE_TITLE)
expect(back_side_title).to_be_visible(timeout=5000), "Заголовок 'Обратная сторона' не отображается"
logger.info("Заголовок 'Обратная сторона' отображается")
# Проверяем позиции юнитов
unit_positions = self.page.locator(RackLocators.UNIT_POSITIONS)
total_positions = unit_positions.count()
logger.info(f"Всего позиций юнитов: {total_positions}")
assert total_positions > 0, "Не найдено позиций юнитов"
# Детальная проверка лицевой стороны
logger.info("--- Детальная проверка лицевой стороны ---")
self._check_front_side_details(front_side_section)
# Детальная проверка обратной стороны
logger.info("--- Детальная проверка обратной стороны ---")
self._check_back_side_details(back_side_section)
logger.info("Все проверки сторон стойки пройдены успешно")
def _check_front_side_details(self, front_side_section) -> None:
"""
Проверка структуры лицевой стороны стойки.
Args:
front_side_section: Локатор секции лицевой стороны
Raises:
AssertionError: Если структура лицевой стороны некорректна
"""
# Проверяем юниты в секции лицевой стороны
front_side_units = front_side_section.locator(RackLocators.FRONT_SIDE_UNITS)
unit_count = front_side_units.count()
logger.info(f"Найдено юнитов на лицевой стороне: {unit_count}")
assert unit_count >= 1, f"Не найдено юнитов на лицевой стороне. Ожидалось минимум 1, найдено {unit_count}"
# Проверяем наличие устройств на лицевой стороне
front_side_devices = front_side_section.locator(RackLocators.FRONT_SIDE_DEVICES)
device_count = front_side_devices.count()
logger.info(f"Найдено физических устройств на лицевой стороне: {device_count}")
if device_count > 0:
for i in range(device_count):
device = front_side_devices.nth(i)
device_title = device.get_attribute("title")
device_classes = device.get_attribute("class") or ""
logger.info(f" Устройство {i}: title='{device_title}', classes='{device_classes}'")
def _check_back_side_details(self, back_side_section) -> None:
"""
Проверка структуры обратной стороны стойки.
Args:
back_side_section: Локатор секции обратной стороны
Raises:
AssertionError: Если структура обратной стороны некорректна
"""
# Проверяем юниты в секции обратной стороны
back_side_units = back_side_section.locator(RackLocators.BACK_SIDE_UNITS)
unit_count = back_side_units.count()
logger.info(f"Найдено юнитов на обратной стороне: {unit_count}")
assert unit_count >= 1, f"Не найдено юнитов на обратной стороне. Ожидалось минимум 1, найдено {unit_count}"
# Проверяем наличие устройств на обратной стороне
back_side_devices = back_side_section.locator(RackLocators.BACK_SIDE_DEVICES)
device_count = back_side_devices.count()
logger.info(f"Найдено физических устройств на обратной стороне: {device_count}")
if device_count > 0:
for i in range(device_count):
device = back_side_devices.nth(i)
device_title = device.get_attribute("title")
device_classes = device.get_attribute("class") or ""
logger.info(f" Устройство {i}: title='{device_title}', classes='{device_classes}'")
def should_be_header_panel(self, expected_toolbar_title_items: list[str]) -> None:
"""
Проверяет наличие и корректность заголовка панели.
Args:
expected_toolbar_title_items: Ожидаемые элементы заголовка
Raises:
AssertionError: Если заголовок панели не соответствует ожиданиям
"""
panel_header_locator = self.page.locator(PANEL_HEADER)
expect(panel_header_locator).to_be_visible(), "Panel header 'Объекты'"
if panel_header_locator.inner_text() != 'chevron_right':
assert False, "No separator 'chevron_right' after header 'Объекты'"
actual_toolbar_title_items = self.get_toolbar_title()
self.check_lists_equals(actual_toolbar_title_items,
expected_toolbar_title_items,
f"Miscomparison actual {actual_toolbar_title_items} and expected {expected_toolbar_title_items}")
self.toolbar.check_button_visibility("edit")
def check_tab_switching(self) -> None:
"""
Проверяет переключение между вкладками стойки в соответствии с локаторами.
Raises:
AssertionError: Если переключение на одну или более вкладок не удалось
"""
logger.info("Тестирование функциональности переключения вкладок стойки...")
# Вкладки
defined_tabs = [
"Общая информация",
"Обслуживание",
"События",
"Сервисы"
]
logger.info(f"Тестируемые определенные вкладки: {defined_tabs}")
successful_switches = 0
failed_switches = []
# Тестируем переключение на каждую определенную вкладку
for tab_name in defined_tabs:
logger.info(f"Тестирование переключения на вкладку '{tab_name}'...")
# Проверяем существование локатора для этой вкладки
tab_locator = RackLocators.TAB_BY_NAME.format(tab_name)
tab_elements = self.page.locator(tab_locator)
# Проверяем наличие элементов через count()
if tab_elements.count() == 0:
logger.warning(f"Вкладка '{tab_name}' не найдена на странице")
failed_switches.append(f"Вкладка '{tab_name}' не найдена")
continue
# Находим видимую и доступную вкладку
target_tab = None
for i in range(tab_elements.count()):
element = tab_elements.nth(i)
# Проверки видимости и доступности
if element.is_visible() and element.is_enabled():
target_tab = element
break
if not target_tab:
logger.warning(f"Не найдена видимая/доступная вкладка '{tab_name}'")
failed_switches.append(f"Вкладка '{tab_name}' не видима/не доступна")
continue
# Переключаемся на вкладку
logger.info(f"Переключение на вкладку '{tab_name}'...")
# Проверяем активность ДО клика
if self.is_tab_active(tab_name):
logger.info(f"Вкладка '{tab_name}' уже активна")
successful_switches += 1
continue
# Кликаем на вкладку с таймаутом
target_tab.click(timeout=5000)
# Ждем изменения активной вкладки
self._wait_for_tab_activation(tab_name)
# Проверяем, что вкладка активна
if not self.is_tab_active(tab_name):
logger.warning(f"Вкладка '{tab_name}' не активна после переключения")
failed_switches.append(f"Вкладка '{tab_name}' не активна после клика")
continue
logger.info(f"Успешно переключено на вкладку '{tab_name}'")
successful_switches += 1
# Небольшая пауза между переключениями для стабильности
self.page.wait_for_timeout(1000)
# Формируем итоговый отчет
logger.info("=== РЕЗУЛЬТАТЫ ПЕРЕКЛЮЧЕНИЯ ВКЛАДОК ===")
logger.info(f"Успешных переключений: {successful_switches}/{len(defined_tabs)}")
if failed_switches:
logger.info("Неудачные переключения:")
for failure in failed_switches:
logger.info(f" - {failure}")
# Требуем успешного переключения на все определенные вкладки
if successful_switches < len(defined_tabs):
raise AssertionError(
f"Тест переключения вкладок не пройден. "
f"Только {successful_switches} из {len(defined_tabs)} определенных вкладок переключены успешно. "
f"Ошибки: {', '.join(failed_switches)}"
)
logger.info(f"Все {successful_switches} определенных вкладок успешно переключены!")

View File

@ -1,4 +1,4 @@
"""Модуль вкладки 'Статус обслуживания'. """Модуль вкладки 'Статус компонентов'.
Содержит класс ServiceStatusTab для работы с таблицей сервисов. Содержит класс ServiceStatusTab для работы с таблицей сервисов.
Позволяет проверять состояние и взаимодействовать с элементами вкладки. Позволяет проверять состояние и взаимодействовать с элементами вкладки.
@ -9,12 +9,11 @@ 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):
"""Класс для работы с вкладкой 'Статус обслуживания'. """Класс для работы с вкладкой 'Статус компонентов'.
Предоставляет методы для взаимодействия с таблицей сервисов и проверки Предоставляет методы для взаимодействия с таблицей сервисов и проверки
её состояния. её состояния.
@ -24,7 +23,7 @@ class ServiceStatusTab(BasePage):
""" """
def __init__(self, page: Page) -> None: def __init__(self, page: Page) -> None:
"""Инициализирует компоненты вкладки 'Статус обслуживания'.""" """Инициализирует компоненты вкладки 'Статус компонентов'."""
super().__init__(page) super().__init__(page)
@ -41,7 +40,6 @@ class ServiceStatusTab(BasePage):
self.update_button_locator, self.update_button_locator,
"update_button") "update_button")
self.expand_work_area_button = ExpandButton(page)
self.services_table = TableComponent(page) self.services_table = TableComponent(page)
# Действия: # Действия:
@ -128,35 +126,11 @@ class ServiceStatusTab(BasePage):
return self.services_table.get_rows_count(self.table_locator) return self.services_table.get_rows_count(self.table_locator)
def expand_tab(self) -> None: def get_workarea_widht(self) -> float:
"""Расширяет рабочую область складки.""" """Возвращает текущую ширину рабочей области вкладки."""
iframe_container_bounding_box = self.iframe_container_locator.\ iframe_container_bounding_box = self.iframe_container_locator.evaluate("el => el.getBoundingClientRect()")
evaluate("el => el.getBoundingClientRect()") return iframe_container_bounding_box["width"]
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:
"""Прокручивает содержимое вкладки вверх.""" """Прокручивает содержимое вкладки вверх."""
@ -278,16 +252,11 @@ 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:
"""Проверяет наличие заголовка вкладки. """Проверяет наличие заголовка вкладки.
@ -312,15 +281,6 @@ class ServiceStatusTab(BasePage):
"Service statuses table is missing" "Service statuses table is missing"
) )
def should_be_expand_work_area_button(self) -> None:
"""Проверяет наличие кнопки расширения/сжатия рабочей области вкладки.
Raises:
AssertionError: Если кнопка отсутствует.
"""
self.expand_work_area_button.should_be_button()
def should_be_update_button(self) -> None: def should_be_update_button(self) -> None:
"""Проверяет наличие кнопки 'Обновить'. """Проверяет наличие кнопки 'Обновить'.

View File

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

View File

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

View File

@ -32,17 +32,18 @@ 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, "Пользователи")
self.toolbar.add_tooltip_button(locator_button_1, "edit") toolbar_button_edit = self.page.get_by_role("navigation").filter(has_text=re.compile("Пользователи")). \
self.toolbar.add_tooltip_button(locator_button_1, "add_user") locator("//button[@data-testid='USERS__btn__edit']")
self.toolbar.add_tooltip_button(locator_button_2, "close") self.toolbar.add_tooltip_button(toolbar_button_edit, "edit")
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 = {}
@ -247,6 +248,18 @@ 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:
"""Открывает окно добавления пользователя. """Открывает окно добавления пользователя.
@ -298,7 +311,7 @@ class UsersTab(BasePage):
if user_name == val: if user_name == val:
user_name = key user_name = key
role = table_content[row_index][2] role = table_content[row_index][3]
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()
@ -374,8 +387,9 @@ 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:
@ -394,6 +408,21 @@ 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:
"""Проверяет наличие тулбара. """Проверяет наличие тулбара.
@ -492,6 +521,11 @@ 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': 420}" test_json_container.py pytest -s -v --s="{'width': 300, 'height': 600}" 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("Шаблоны_2") mp.check_navigation_panel_item_visibility("Шаблоны")
mp.wait_for_timeout(2000) mp.wait_for_timeout(2000)

View File

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

View File

@ -32,7 +32,7 @@ class TestSessionSettingsForm:
mp.click_subpanel_item("Настройки", parent="Сеансы") mp.click_subpanel_item("Настройки", parent="Сеансы")
def test_scrolling(self, browser: Page) -> None: def test_scrolling(self, browser: Page) -> None:
"""Проверяет прокрутку таблицы статусов сервисов. """Проверяет прокрутку формы редактирования настроек.
Args: Args:
browser: Экземпляр страницы Playwright. browser: Экземпляр страницы Playwright.
@ -55,5 +55,5 @@ class TestSessionSettingsForm:
sst.wait_for_timeout(3000) sst.wait_for_timeout(3000)
sst.scroll_up() sst.scroll_up()
sst.should_be_form_toolbar() sst.get_field_by_name('administrator').check_visibility("Text 'Администратор' should be visible")
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_local_user") modal_window = ut.get_modal_window("add_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,7 +61,8 @@ put
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
if date_input.is_text_input_mode(): # Temporarily due to error in UI
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)
@ -95,7 +96,8 @@ put
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
if date_input.is_text_input_mode(): # Temporarily due to error in UI
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:
@ -169,7 +171,8 @@ put
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
if date_input.is_text_input_mode(): # Temporarily due to error in UI
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)
@ -208,7 +211,8 @@ put
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
if date_input.is_text_input_mode(): # Temporarily due to error in UI
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, проверяем результат
@ -245,26 +249,27 @@ put
date_input.click_switch_mode_button() date_input.click_switch_mode_button()
browser.wait_for_timeout(500) browser.wait_for_timeout(500)
if date_input.is_text_input_mode(): # Temporarily due to error in UI
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.time_date("1:01") date_input.input_time("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 nor equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'"
try: try:
date_input.input_date("o2.01") date_input.input_date("o2.01.2024")
except AssertionError as e: except AssertionError as e:
actual_err = f"{e}" actual_err = f"{e}"
expected_err = "Incorrect hours value o2 for selection" expected_err = "Incorrect day value o2 for selection"
assert expected_err == actual_err, \ assert expected_err == actual_err, \
f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'"
try: try:
date_input.input_time("01:1") date_input.input_time("01:1")
@ -272,14 +277,14 @@ put
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 nor equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is not equal actual error message: '{actual_err}'"
try: try:
date_input.input_date("01:o1") date_input.input_date("01.o1.2024")
except AssertionError as e: except AssertionError as e:
actual_err = f"{e}" actual_err = f"{e}"
expected_err = "Incorrect minutes value o1 for selection" expected_err = "Incorrect month value o1 for selection"
assert expected_err == actual_err, \ assert expected_err == actual_err, \
f"Expected error message: '{expected_err}' is nor equal actual error message: '{actual_err}'" f"Expected error message: '{expected_err}' is not 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,6 +179,9 @@ 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

@ -0,0 +1,115 @@
"""Модуль тестов вкладки настройки 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

@ -0,0 +1,143 @@
"""Модуль тестов вкладки настройки 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

@ -1,621 +0,0 @@
"""Тест создания дочернего элемента 'Стойка'."""
import pytest
from playwright.sync_api import Page
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from locators.rack_locators import RackLocators
from components_derived.accounting_objects.rack_maker import RackObjectMaker, RackData
from components_derived.frames.create_child_element_frame import CreateChildElementFrame
from pages.location_page import LocationPage
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.rack_page import RackPage
logger = get_logger("CREATE_RACK_ELEMENT_TEST")
logger.setLevel("INFO")
# @pytest.mark.smoke
class TestCreateRackElement:
"""Тест создания дочернего элемента типа 'Стойка'.
Тесты покрывают следующие сценарии:
1. test_create_rack_content: Проверяет содержимое формы создания стойки
2. test_create_rack_child_element: Проверяет создание дочернего элемента типа 'Стойка'
3. test_create_rack_with_duplicate_name: Проверяет создание стойки с дублирующимся именем
4. test_required_fields_validation: Проверяет валидацию обязательных полей при создании стойки
"""
# Инициализируем атрибуты
main_page: MainPage = None
location_page: LocationPage = None
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser: Page) -> None:
"""Фикстура для подготовки тестового окружения.
Args:
browser (Page): Экземпляр страницы Playwright для взаимодействия с UI
"""
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Мы на главной странице
self.main_page = MainPage(browser)
self.main_page.should_be_navigation_panel()
# Переходим к Объектам
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.wait_for_timeout(1000)
self.main_page.click_main_navigation_panel_item("test-zone")
# Создаем экземпляр страницы локации
self.location_page = LocationPage(browser)
@pytest.fixture
def cleanup_racks(self, browser: Page):
"""Фикстура для очистки созданных стоек."""
# Список для хранения созданных в тесте стоек
created_racks = []
yield created_racks
# После завершения теста удаляем созданные стойки
if created_racks:
logger.debug(f"Cleaning up racks: {created_racks}")
self.main_page.wait_for_timeout(500)
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(1000)
# Удаляем каждую стойку если она существует
for rack_name in created_racks:
# Проверяем существование стойки
if self._check_rack_existance(browser, rack_name):
logger.debug(f"Deleting rack '{rack_name}'...")
# Переходим на страницу стойки для удаления
self.main_page.click_subpanel_item(rack_name, parent="test-zone")
self.main_page.wait_for_timeout(1000)
# Удаляем стойку
self._delete_rack_from_context_menu(browser, rack_name)
# Проверяем удаление
self.main_page.click_subpanel_item("test-zone")
self.main_page.wait_for_timeout(500)
# Дополнительная проверка удаления
rack_still_exists = self._check_rack_existance(browser, rack_name)
if rack_still_exists:
logger.error(f"Rack '{rack_name}' still exists after deletion attempt")
logger.debug("Racks cleanup completed")
else:
logger.debug("No racks to cleanup")
def _create_rack(self, browser: Page, rack_name: str) -> None:
"""Создает стойку.
Args:
browser: Страница Playwright
rack_name: Имя стойки для создания
"""
logger.debug(f"Creating rack with name '{rack_name}'")
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных стойки
rack_data = RackData(
name=rack_name,
height="42",
depth="1000"
)
# Заполняем данные стойки
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
create_child_frame.click_add_button()
def _delete_rack_from_context_menu(self, browser: Page, rack_name: str) -> None:
"""Удаляет стойку через контекстное меню.
Args:
browser: Страница Playwright
rack_name: Имя стойки для удаления
"""
logger.debug(f"Deleting rack '{rack_name}' from context menu...")
# 1. Находим элемент стойки в навигационной панели
rack_element = browser.locator(NavigationPanelLocators.TREEVIEW).get_by_text(rack_name, exact=True).first
# Прокручиваем до элемента если нужно
rack_element.scroll_into_view_if_needed()
self.main_page.wait_for_timeout(500)
# 2. Проверяем и нажимаем кнопку "Изменить"
logger.debug("Step 1: Clicking 'Edit' button...")
rack_page = RackPage(browser)
# Проверяем видимость кнопки
rack_page.toolbar.check_button_visibility("edit")
# Проверяем тултип кнопки
rack_page.toolbar.check_button_tooltip("edit", "Изменить")
# Кликаем на кнопку "Изменить"
rack_page.toolbar.get_button_by_name("edit").click()
logger.debug("Edit button clicked, waiting for edit form...")
# 3. Используем метод click_remove_button, который обрабатывает весь процесс удаления
# включая диалог подтверждения
logger.debug("Clicking remove button...")
rack_page.click_remove_button()
# 4. Проверяем уведомление об успешном удалении - требуется создать разработчику (заведена задача)
# Создаем экземпляр фрейма для доступа к alert компоненту
# create_child_frame = CreateChildElementFrame(browser)
# Проверяем наличие любого alert-окна (не обязательно точного текста)
# create_child_frame.alert.check_alert_presence("")
# Получаем текст alert, чтобы убедиться что удаление прошло успешно
# alert_text = create_child_frame.alert.get_text()
# logger.debug(f"Alert text after deletion: {alert_text}")
# Проверяем что в тексте есть указание на успешное удаление
# assert "удален" in alert_text.lower() or "успешно" in alert_text.lower()
# Закрываем alert
# create_child_frame.alert.close_alert()
logger.debug("Rack deletion completed")
def _perform_required_fields_test(self, create_child_frame, rack_maker, test_data):
"""Выполняет один тест валидации обязательных полей.
Args:
create_child_frame: Фрейм создания дочернего элемента
rack_maker: Объект для работы со стойкой
test_data: Словарь с данными теста
"""
# Распаковываем данные теста
name_value = test_data["name"]
height_value = test_data["height"]
depth_value = test_data["depth"]
expected_alert_height = test_data["expected_alert_height"]
expected_alert_depth = test_data["expected_alert_depth"]
# Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
logger.debug(f"Available fields:\
{list(create_child_frame.get_input_fields_locators(container_locator).keys())}")
# Проверяем и очищаем поле "Глубина (мм)" только если оно заполнено
logger.debug("Checking field: Глубина (мм)")
if create_child_frame.is_field_filled("Глубина (мм)", container_locator):
logger.debug("Field 'Глубина (мм)' is filled, performing clearing")
create_child_frame.clear_combobox_field("Глубина (мм)")
logger.debug("Clearing completed for 'Глубина (мм)'")
else:
logger.debug("Field 'Глубина (мм)' is already empty, skipping clearing")
# Проверяем и очищаем поле "Высота в юнитах" только если оно заполнено
logger.debug("Checking field: Высота в юнитах")
if create_child_frame.is_field_filled("Высота в юнитах", container_locator):
logger.debug("Field 'Высота в юнитах' is filled, performing clearing")
create_child_frame.clear_combobox_field("Высота в юнитах")
logger.debug("Clearing completed for 'Высота в юнитах'")
else:
logger.debug("Field 'Высота в юнитах' is already empty, skipping clearing")
# Создаем объект данных стойки
rack_data = RackData(
name=name_value,
height=height_value,
depth=depth_value
)
# Заполняем данные стойки
logger.debug(f"Setting test data - Name: '{name_value}', Height: '{height_value}', Depth: '{depth_value}'")
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
logger.debug("Submitting form for validation")
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(500)
# Проверяем валидацию полей
logger.debug("Checking validation results")
# Обрабатываем alert-окна
if not height_value:
logger.debug("Expecting height validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_height)
create_child_frame.alert.close_alert_by_text(expected_alert_height)
logger.debug("Height alert handled")
if not depth_value:
logger.debug("Expecting depth validation alert")
create_child_frame.alert.check_alert_presence(expected_alert_depth)
create_child_frame.alert.close_alert_by_text(expected_alert_depth)
logger.debug("Depth alert handled")
# Проверяем подсветку обязательных полей
if height_value:
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
logger.debug("Height field validation passed")
else:
create_child_frame.check_field_error_highlighted("Высота в юнитах")
logger.debug("Height field validation failed as expected")
if depth_value:
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.debug("Depth field validation passed")
else:
create_child_frame.check_field_error_highlighted("Глубина (мм)")
logger.debug("Depth field validation failed as expected")
# Проверяем, что остались на странице создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
logger.debug("Test completed successfully")
def test_create_rack_child_element(self, browser: Page, cleanup_racks) -> None:
"""Тест создания дочернего элемента типа 'Стойка'."""
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных стойки
rack_data = RackData(
name="Test-Rack-01",
height="42",
depth="1000",
serial="TEST123456",
inventory="INV-001",
comment="Тестовая стойка для автоматизации",
state="Введен в эксплуатацию",
cable_entry="сверху"
)
# Сохраняем имя стойки в переменную
rack_name = rack_data.name
cleanup_racks.append(rack_name)
# Заполняем данные стойки
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку "Добавить"
create_child_frame.click_add_button()
# 1. Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача)
# Проверяем наличие alert-окна
# create_child_frame.alert.check_alert_presence("")
# Получаем текст alert
# alert_text = create_child_frame.alert.get_text()
# logger.debug(f"Alert text after creation: {alert_text}")
# Проверяем, что в тексте есть указание на успешное создание
# assert "создан" in alert_text.lower() or "успешно" in alert_text.lower()
# assert final_rack_name in alert_text
# Закрываем alert
# create_child_frame.alert.close_alert()
# 2. Проверяем, что стойка создана и отображается
logger.debug(f"Verifying that rack '{rack_name}' was created...")
# Обновляем навигационную панель
self.main_page.click_main_navigation_panel_item("test-zone")
# Проверяем существование стойки в навигационной панели
rack_exists = self._check_rack_existance(browser, rack_name)
assert rack_exists, f"Rack '{rack_name}' should be visible in navigation panel after creation"
logger.debug(f"Rack '{rack_name}' is visible in navigation panel")
logger.debug("Test for creating 'Rack' child element completed successfully")
def test_create_rack_content(self, browser: Page) -> None:
"""Тест проверки содержимого формы создания стойки."""
# Проверяем что кнопка "Создать" доступна
self.location_page.should_be_toolbar_buttons()
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Проверяем заголовок формы создания
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Проверяем что после выбора 'Стойка' появляются специфичные поля
rack_maker.check_rack_fields_presence()
logger.debug("Rack-specific fields are displayed correctly")
create_child_frame.should_be_toolbar_buttons()
def test_create_rack_with_duplicate_name(self, browser: Page, cleanup_racks) -> None:
"""Тест создания стойки с уже существующим именем.
Проверяет, что система корректно обрабатывает попытку создания
стойки с именем, которое уже используется.
"""
logger.debug("Starting test for creating rack with duplicate name")
rack_name = "Test-Rack-Duplicate"
# Проверяем, существует ли уже стойка с таким именем
if not self._check_rack_existance(browser, rack_name):
logger.debug(f"Rack with name '{rack_name}' not found. Creating first rack.")
self._create_rack(browser, rack_name)
logger.debug(f"First rack with name '{rack_name}' created successfully")
# Добавляем стойку в список для очистки
cleanup_racks.append(rack_name)
else:
logger.debug(f"Rack with name '{rack_name}' already exists, proceeding to create second one")
# Создаем вторую стойку с тем же именем
logger.debug(f"Attempting to create second rack with name '{rack_name}'")
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Создаем объект данных для второй стойки
rack_data = RackData(
name=rack_name,
height="42",
depth="450"
)
# Пытаемся создать вторую стойку с тем же именем
rack_maker.fill_rack_data(rack_data)
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(2000)
# Проверяем наличие alert-окна с сообщением о дублирующемся имени
expected_alert_text = f"Имя {rack_name} уже используется"
create_child_frame.alert.check_alert_presence(expected_alert_text)
# Проверяем, что остались на странице создания (стойка не создана)
create_child_frame.check_toolbar_title('Создать дочерний элемент в')
# Закрываем alert-окно с помощью кнопки закрытия
create_child_frame.wait_for_timeout(2000)
create_child_frame.alert.close_alert_by_text(expected_alert_text)
logger.debug("System prevented creating rack with duplicate name")
def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None:
"""Тест проверки обязательных полей при создании стойки.
Проверяет, что система корректно валидирует обязательные поля:
- Поле 'Высота в юнитах' должно быть заполнено
- Поле 'Глубина (мм)' должно быть заполнено
"""
# Текст сообщения alert-окна
expected_alert_text_height = "поле Высота в юнитах должно быть заполнено"
expected_alert_text_depth = "поле Глубина (мм) должно быть заполнено"
# Нажимаем кнопку "Создать" на тулбаре
self.location_page.click_create_button()
# Создаем фрейм создания дочернего элемента
create_child_frame = CreateChildElementFrame(browser)
# Нажимаем на плашку "Класс объекта учета"
create_child_frame.open_object_class_combobox()
# Из выпадающего меню выбираем пункт "Стойка"
create_child_frame.select_object_class("Стойка")
# Открывается набор плашек для задания параметров стойки
rack_maker = RackObjectMaker(browser)
# Тестовые данные
test_cases = [
{
"name": "Test 1: Required fields are not filled",
"data": {
"name": "Test-Rack-Required-01",
"height": "",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 2: Only 'Height in units' field is filled",
"data": {
"name": "Test-Rack-Required-02",
"height": "42",
"depth": "",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
},
{
"name": "Test 3: Only 'Depth (mm)' field is filled",
"data": {
"name": "Test-Rack-Required-03",
"height": "",
"depth": "1000",
"expected_alert_height": expected_alert_text_height,
"expected_alert_depth": expected_alert_text_depth
}
}
]
# Выполняем тестовые случаи
for test_case in test_cases:
logger.debug(test_case["name"])
self._perform_required_fields_test(
create_child_frame, rack_maker, test_case["data"])
logger.debug("System prevented creating rack with invalid required fields")
# 4. Тест: Заполняем все обязательные поля
logger.debug("Test 4: All required fields are filled")
# Генерируем уникальное имя для финального теста
final_rack_name = "Test-Rack-Required-04"
cleanup_racks.append(final_rack_name)
# **ВАЖНО: Очищаем поля перед заполнением**
logger.debug("Clearing fields before filling...")
# Получаем контейнер формы
container_locator = create_child_frame.page.locator(RackLocators.FORM_INPUT_CONTAINER).nth(1)
fields_locators = create_child_frame.get_input_fields_locators(container_locator)
# Очищаем поле "Высота в юнитах" если оно заполнено
if "Высота в юнитах" in fields_locators:
if create_child_frame.is_field_filled("Высота в юнитах", container_locator):
logger.debug("Clearing 'Высота в юнитах' field...")
create_child_frame.clear_combobox_field("Высота в юнитах")
create_child_frame.wait_for_timeout(500)
# Очищаем поле "Глубина (мм)" если оно заполнено
if "Глубина (мм)" in fields_locators:
if create_child_frame.is_field_filled("Глубина (мм)", container_locator):
logger.debug("Clearing 'Глубина (мм)' field...")
create_child_frame.clear_combobox_field("Глубина (мм)")
create_child_frame.wait_for_timeout(500)
# Очищаем поле "Имя" если оно заполнено
if "Имя" in fields_locators:
if create_child_frame.is_field_filled("Имя", container_locator):
logger.debug("Clearing 'Имя' field...")
# Специальная обработка для текстового поля
field_container = fields_locators["Имя"]
input_field = field_container.locator("input").first
if input_field.count() > 0:
input_field.click()
input_field.press("Control+A")
input_field.press("Backspace")
create_child_frame.wait_for_timeout(500)
# Создаем объект данных стойки
rack_data = RackData(
name=final_rack_name,
height="42",
depth="1000"
)
# Заполняем все обязательные поля
rack_maker.fill_rack_data(rack_data)
# Проверяем, что ни одно поле не подсвечено цветом ошибки
create_child_frame.check_field_error_not_highlighted("Имя")
create_child_frame.check_field_error_not_highlighted("Высота в юнитах")
create_child_frame.check_field_error_not_highlighted("Глубина (мм)")
logger.debug("No required fields are highlighted with error color - all fields filled correctly")
# Нажимаем кнопку создания
create_child_frame.click_add_button()
create_child_frame.wait_for_timeout(500)
# Проверяем уведомление об успешном создании стойки - требуется создать разработчику (заведена задача)
# Проверяем наличие alert-окна
# create_child_frame.alert.check_alert_presence("")
# Получаем текст alert
# alert_text = create_child_frame.alert.get_text()
# logger.debug(f"Alert text after creation: {alert_text}")
# Проверяем что в тексте есть указание на успешное создание
# assert "создан" in alert_text.lower() or "успешно" in alert_text.lower()
# assert final_rack_name in alert_text
# Закрываем alert
# create_child_frame.alert.close_alert()
logger.debug("Required fields validation test completed successfully")
# Вспомогательные методы проверки
def _check_rack_existance(self, browser: Page, rack_name: str) -> bool:
"""Проверяет существование стойки.
Args:
browser: Страница Playwright
rack_name: Имя стойки для проверки
Returns:
bool: True если стойка существует, False в противном случае
"""
logger.debug(f"Checking existence of rack with name '{rack_name}'")
# Обновляем навигационную панель
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.click_main_navigation_panel_item("Объекты")
self.main_page.click_subpanel_item("test-zone")
nav_panel_locator = NavigationPanelLocators.TREEVIEW
# Проверяем видимость элемента
element = browser.locator(nav_panel_locator).get_by_text(rack_name, exact=True).first
if element.is_visible():
logger.debug(f"Rack with name '{rack_name}' found")
return True
logger.debug(f"Rack with name '{rack_name}' not found")
return False

View File

@ -0,0 +1,481 @@
"""Тест создания дочернего элемента 'Стойка'."""
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

@ -0,0 +1,547 @@
"""Модуль тестов редактирования стойки в модуле Объекты.
Содержит тесты для проверки функциональности
редактирования стойки оборудования.
"""
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.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -1,119 +0,0 @@
"""Модуль тестов вкладки 'Стойка' в модуле Объекты.
Содержит тесты для проверки функциональности
работы со стойкой оборудования.
"""
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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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