Compare commits

...

14 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
56 changed files with 1977 additions and 265 deletions

3
.env.12 Normal file
View File

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

View File

@ -31,6 +31,37 @@ class CheckboxGroupComponent(BaseComponent):
def get_checkbox_locator(self, text: str, container_locator: Locator | None = None) -> Locator: 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: Args:
text (str): Текст элемента для выбора. text (str): Текст элемента для выбора.
container_locator (Locator | None): Локатор контейнера с чек-боксами. container_locator (Locator | 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()

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

@ -252,12 +252,13 @@ class NavigationPanelComponent(BaseComponent):
assert False, "Workarea already reduced" 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:
Временная обработка для элементов с текстом 'Шаблоны'. Временная обработка для элементов с текстом 'Шаблоны'.
@ -265,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:
""" """

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

@ -114,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

@ -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")
@ -97,6 +100,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")

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")

View File

@ -66,9 +66,19 @@ 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()

View File

@ -61,9 +61,21 @@ 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()

View File

@ -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

@ -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

@ -50,7 +50,6 @@ class EditUserModalWindow(ModalWindowComponent):
# Добавление полей формы # Добавление полей формы
elements_locators = self.get_input_fields_locators( elements_locators = self.get_input_fields_locators(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA)) self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Поле Имя # Поле Имя
loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME) loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
name_input = TextInput(page, loc, "name_input") name_input = TextInput(page, loc, "name_input")
@ -213,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()

View File

@ -106,3 +106,16 @@ class SendTestEmailModalWindow(ModalWindowComponent):
self.alert.check_alert_presence('\nТестовое сообщение\nотправлено\n') self.alert.check_alert_presence('\nТестовое сообщение\nотправлено\n')
self.alert.check_alert_absence('\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

@ -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

@ -55,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]:
@ -87,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]:
"""Возвращает список выбранных значений""" """Возвращает список выбранных значений"""
@ -96,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()
@ -153,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:
"""Выбор значения из списка""" """Выбор значения из списка"""

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

@ -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

@ -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

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

View File

@ -646,28 +646,52 @@ class EditRackMaker(ModalWindowComponent):
username: str, username: str,
field_label: str field_label: str
) -> Tuple[bool, Optional[str]]: ) -> Tuple[bool, Optional[str]]:
"""Добавляет пользователя из выпадающего списка. """Добавляет пользователя из выпадающего списка."""
Args:
dropdown_menu: Выпадающее меню.
username: Имя пользователя.
field_label: Название поля.
Returns:
Кортеж (добавлен ли пользователь, сообщение об ошибке или None).
"""
try: try:
user_item = dropdown_menu.locator(f"[role='listitem']:has-text('{username}')").first # Получаем все элементы списка
if user_item.count() == 0: listitem_locator = dropdown_menu.get_by_role("listitem")
user_item = dropdown_menu.locator(f"div:has-text('{username}')").first
if user_item.count() > 0: # Проверяем, что элементы есть
user_item.click() if listitem_locator.count() == 0:
return False, f"No list items found in dropdown for {field_label}"
# Прокручиваем к последнему элементу
listitem_locator.last.scroll_into_view_if_needed()
# Ждем, пока последний элемент станет видимым
listitem_locator.last.wait_for(state="visible")
# Ищем элемент с точным совпадением текста
all_items = listitem_locator.all()
target_item = None
for item in all_items:
# Ищем span с текстом внутри элемента
span = item.locator("span").first
if span.inner_text().strip() == username:
target_item = item
break
if target_item is None:
return False, f"User '{username}' not found in dropdown for {field_label}"
# Проверяем, не выбран ли уже этот пользователь
class_attribute = target_item.get_attribute("class") or ""
# Если элемент уже выбран (есть класс v-list__tile--active)
if "v-list__tile--active" in class_attribute:
logger.debug(f"User '{username}' is already selected in {field_label}, skipping")
return True, None # Считаем как успех, т.к. пользователь уже есть
# Прокручиваем к найденному элементу
target_item.scroll_into_view_if_needed()
target_item.wait_for(state="visible")
# Кликаем
target_item.click()
self.wait_for_timeout(500) self.wait_for_timeout(500)
return True, None return True, None
return False, f"User '{username}' not found in dropdown for {field_label}"
except Exception as e: except Exception as e:
return False, f"Failed to add user '{username}' to {field_label}: {str(e)}" return False, f"Failed to add user '{username}' to {field_label}: {str(e)}"

View File

@ -16,7 +16,6 @@ from components_derived.settings_form_component import SettingsFormComponent
from components_derived.selection_bar_component import SelectionBarComponent from components_derived.selection_bar_component import SelectionBarComponent
from pages.base_page import BasePage from pages.base_page import BasePage
class BackupSettingsTab(BasePage): class BackupSettingsTab(BasePage):
"""Класс для работы с вкладкой настройки резервного копирования. """Класс для работы с вкладкой настройки резервного копирования.
@ -82,6 +81,12 @@ class BackupSettingsTab(BasePage):
self.toolbar.check_button_visibility("save") self.toolbar.check_button_visibility("save")
self.toolbar.get_button_by_name("save").click() self.toolbar.get_button_by_name("save").click()
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обновлены')
self.alert.check_alert_absence('Параметры успешно\nобновлены')
def clear_inventory_dump_selection(self) -> None: def clear_inventory_dump_selection(self) -> None:
"""Удаление ранее выбранного имени дампа""" """Удаление ранее выбранного имени дампа"""
@ -94,7 +99,6 @@ class BackupSettingsTab(BasePage):
dump_selector = self.streaming_data_settings.get_content_item("streaming_data_dump_selector") dump_selector = self.streaming_data_settings.get_content_item("streaming_data_dump_selector")
dump_selector.clear_selections() dump_selector.clear_selections()
def create_inventory_copy(self) -> None: def create_inventory_copy(self) -> None:
"""Создать резервную копию Системы.""" """Создать резервную копию Системы."""
@ -147,6 +151,33 @@ class BackupSettingsTab(BasePage):
download.save_as(str(path_to_download) + "/" + download.suggested_filename) download.save_as(str(path_to_download) + "/" + download.suggested_filename)
def download_streaming_data_copy(self, dump: str, path_to_download: str) -> None:
"""Скачать резервную копию Потоковых данных."""
wait_download_timeout = 3*60000
is_set = self.page.evaluate("window.__PLAYWRIGHT__")
assert is_set, "'window.__PLAYWRIGHT__' should be set to True"
assert path_to_download.exists(), f"{path_to_download} does not exist"
dump_selector = self.streaming_data_settings.get_content_item("streaming_data_dump_selector")
if dump_selector:
dump_selector.open_values_list()
dump_selector.select_value(dump)
button_download_copy = self.streaming_data_settings.get_content_item("streaming_data_button_download_copy")
button_download_copy.check_visibility("Streaming data button to download copy is missing")
with self.page.expect_download(timeout=wait_download_timeout) as download_info:
button_download_copy.click()
download = download_info.value
download_error = download.failure()
assert not download_error, f"Download error: {download_error}"
download.save_as(str(path_to_download) + "/" + download.suggested_filename)
def get_inventory_scheduler_settings_values(self) -> dict: def get_inventory_scheduler_settings_values(self) -> dict:
"""Возвращает текущее значение полей настроек 'Инвентаризация/Параметры планировщика'. """Возвращает текущее значение полей настроек 'Инвентаризация/Параметры планировщика'.

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

@ -414,8 +414,8 @@ class EmailNotificationsSettingsTab(BasePage):
if name == "checkbox_activate": if name == "checkbox_activate":
is_activate_checked = item.is_checked() is_activate_checked = item.is_checked()
assert not is_activate_checked, ( assert is_activate_checked, (
"Checkbox 'Активировать' should not be checked by default" "Checkbox 'Активировать' should be checked"
) )
def _check_tls_settings_content(self): def _check_tls_settings_content(self):

View File

@ -296,6 +296,19 @@ class LDAPAuthSettingsTab(BasePage):
if name == "password_hidden_icon": if name == "password_hidden_icon":
is_hidden_state = item.is_password_hidden() is_hidden_state = item.is_password_hidden()
assert is_hidden_state, "Password hidden icon should be in hidden state" 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: def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара. """Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара.

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

@ -205,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:
@ -214,7 +214,7 @@ class MainPage(BasePage):
self.navigation_panel.check_item_visibility( self.navigation_panel.check_item_visibility(
NavigationPanelLocators.PANEL_MAIN, NavigationPanelLocators.PANEL_MAIN,
item_name item_name, parent
) )
def check_subpanel_item_state(self, item_name: str, parent=None) -> str|None: def check_subpanel_item_state(self, item_name: str, parent=None) -> str|None:

View File

@ -168,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()
@ -183,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)
# Проверки: # Проверки:
@ -227,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:

View File

@ -248,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:
"""Открывает окно добавления пользователя. """Открывает окно добавления пользователя.
@ -396,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:
"""Проверяет наличие тулбара. """Проверяет наличие тулбара.

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

@ -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

@ -347,6 +347,7 @@ class TestCreateRack:
logger.debug("System prevented creating rack with duplicate name") logger.debug("System prevented creating rack with duplicate name")
@pytest.mark.develop
def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None: def test_required_fields_validation(self, browser: Page, cleanup_racks) -> None:
"""Тест проверки обязательных полей при создании стойки.""" """Тест проверки обязательных полей при создании стойки."""
logger.debug("Starting required fields validation test") logger.debug("Starting required fields validation test")
@ -368,29 +369,31 @@ class TestCreateRack:
rack_form = CreateRackForm(browser) rack_form = CreateRackForm(browser)
# ========== Тест 1: Обязательные поля высота и глубина пустые ========== # ========== Тест 1: Обязательные поля имя, высота и глубина пустые ==========
logger.debug("Test 1: Both required fields (height, depth) are empty") logger.debug("Test 1: Both required fields (height, depth) are empty")
# Очищаем поля высоты и глубины перед заполнением # Очищаем поля имя, высоты и глубины перед заполнением
rack_form.clear_field("Имя")
rack_form.clear_field("Высота в юнитах") rack_form.clear_field("Высота в юнитах")
rack_form.clear_field("Глубина (мм)") rack_form.clear_field("Глубина (мм)")
test_data_1 = CreateRackData( test_data_1 = CreateRackData(
name=self.TEST_RACK_NAME, name="",
usize="", usize="",
depth="" depth=""
) )
rack_form.fill_rack_data(test_data_1) rack_form.fill_rack_data(test_data_1)
self.create_child_frame.click_add_button() self.create_child_frame.click_add_button()
self.create_child_frame.wait_for_timeout(500)
# Проверяем alert для высоты, глубины # Проверяем 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_height, timeout=7000)
self.alert.check_alert_presence(expected_alert_text_depth, timeout=7000) self.alert.check_alert_presence(expected_alert_text_depth, timeout=7000)
# Проверяем, закрылся ли автоматически alert для высоты, глубины # Проверяем, закрылся ли автоматически alert для имени, высоты, глубины
self.alert.check_alert_absence(expected_alert_text_height, timeout=7000) 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) self.alert.check_alert_absence(expected_alert_text_depth, timeout=500)
# Проверяем подсветку полей # Проверяем подсветку полей
@ -465,7 +468,6 @@ class TestCreateRack:
rack_form.fill_rack_data(test_data_4) rack_form.fill_rack_data(test_data_4)
self.create_child_frame.click_add_button() self.create_child_frame.click_add_button()
self.create_child_frame.wait_for_timeout(500)
# Проверяем alert для имени # Проверяем alert для имени
self.alert.check_alert_presence(expected_alert_text_name, timeout=7000) self.alert.check_alert_presence(expected_alert_text_name, timeout=7000)
@ -474,3 +476,6 @@ class TestCreateRack:
self.alert.check_alert_absence(expected_alert_text_name, timeout=7000) self.alert.check_alert_absence(expected_alert_text_name, timeout=7000)
logger.debug("Test 4 completed: System correctly validates empty name field") logger.debug("Test 4 completed: System correctly validates empty name field")
# ========== Тест 5: Поле "Имя" не более 35 знаков ==========
# разработчик должен ограничить длину имени - не более 35 знаков

View File

@ -304,7 +304,7 @@ class TestRackEdit:
rack_edit.click_done_button() rack_edit.click_done_button()
# Проверяем уведомление об успешном обновлении # Проверяем уведомление об успешном обновлении
expected_alert_text = "Элемент успешно обновлён" expected_alert_text = "Успешно обновлено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000) self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000) self.alert.check_alert_absence(expected_alert_text, timeout=7000)
@ -336,6 +336,54 @@ class TestRackEdit:
logger.debug("General info tab test completed successfully") 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: def test_rack_image_tab(self, browser: Page) -> None:
"""Тест вкладки 'Изображение' стойки.""" """Тест вкладки 'Изображение' стойки."""
@ -376,7 +424,7 @@ class TestRackEdit:
rack_edit.click_done_button() rack_edit.click_done_button()
# Проверяем уведомление об успешном обновлении # Проверяем уведомление об успешном обновлении
expected_alert_text = "Элемент успешно обновлён" expected_alert_text = "Успешно обновлено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000) self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000) self.alert.check_alert_absence(expected_alert_text, timeout=7000)
@ -466,7 +514,7 @@ class TestRackEdit:
rack_edit.click_done_button() rack_edit.click_done_button()
# Проверяем уведомление об успешном обновлении # Проверяем уведомление об успешном обновлении
expected_alert_text = "Элемент успешно обновлён" expected_alert_text = "Успешно обновлено"
self.alert.check_alert_presence(expected_alert_text, timeout=7000) self.alert.check_alert_presence(expected_alert_text, timeout=7000)
self.alert.check_alert_absence(expected_alert_text, timeout=7000) self.alert.check_alert_absence(expected_alert_text, timeout=7000)

View File

@ -53,6 +53,7 @@ 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: if rows_count > 1:
@ -64,6 +65,7 @@ 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: if rows_count > 1:
@ -71,7 +73,7 @@ class TestActionsEventsContainer:
if rows_count > 3: 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):
"""Проверяет возможность скроллинга таблицы событий на примере таблицы Архив. """Проверяет возможность скроллинга таблицы событий на примере таблицы Архив.
@ -79,6 +81,8 @@ class TestActionsEventsContainer:
browser: Экземпляр страницы Playwright. browser: Экземпляр страницы Playwright.
""" """
rows_to_start_scrolling = 20
lp = LoginPage(browser) lp = LoginPage(browser)
lp.do_login() lp.do_login()
@ -88,6 +92,8 @@ class TestActionsEventsContainer:
actions_events_container = mp.click_events_panel_actions_tab() actions_events_container = mp.click_events_panel_actions_tab()
actions_events_container.click_archive_button() 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()
# Проверка, что панель с таблицей открыта # Проверка, что панель с таблицей открыта
@ -140,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):
@ -174,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}
@ -246,7 +263,7 @@ class TestActionsEventsContainer:
# @pytest.mark.develop # @pytest.mark.develop
def test_real_time_events_table_pagination(self, browser: Page): def test_real_time_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий. """Проверяет возможность пагинации таблицы событий на примере вкладки 'Реальное время'.
Args: Args:
browser: Экземпляр страницы Playwright. browser: Экземпляр страницы Playwright.
@ -266,6 +283,7 @@ class TestActionsEventsContainer:
# Проверка начального состояния # Проверка начального состояния
tested_pages = 1 tested_pages = 1
pages = 1
payload = {"filter": {"page": 1}, payload = {"filter": {"page": 1},
"count": 40} "count": 40}
@ -357,7 +375,7 @@ class TestActionsEventsContainer:
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:
actions_events_container.should_be_final_state() actions_events_container.should_be_final_state()
else: else:
actions_events_container.should_be_all_enabled() actions_events_container.should_be_all_enabled()

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

@ -103,9 +103,12 @@ class TestAuditEventsContainerSecurity:
# Получение количества строк в таблице # Получение количества строк в таблице
rows_count = security_events_container.get_events_table_rows_count() rows_count = security_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк # Проверка выделения строк
security_events_container.check_events_table_row_highlighting(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) 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)) security_events_container.check_events_table_row_highlighting(int(rows_count / 2))
# Выход из системы текущего пользователя # Выход из системы текущего пользователя
@ -243,6 +246,7 @@ class TestAuditEventsContainerSecurity:
# Проверка начального состояния # Проверка начального состояния
tested_pages = 1 tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0} # to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "default", payload = {"table": "default",
@ -343,7 +347,7 @@ class TestAuditEventsContainerSecurity:
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:
security_events_container.should_be_final_state() security_events_container.should_be_final_state()
else: else:
security_events_container.should_be_all_enabled() security_events_container.should_be_all_enabled()

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

@ -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

@ -157,16 +157,14 @@ class TestEmailNotificationsSettingsTab:
send_test_email_window.close_by_toolbar_button() send_test_email_window.close_by_toolbar_button()
# @pytest.mark.develop # @pytest.mark.develop
# TO-DO: rewrite tescase after feature release def test_send_test_email_successful(self, browser: Page) -> None:
def test_send_test_email(self, browser: Page) -> None:
"""Тест модального окна для посылки тестового E-mail. """Тест модального окна для посылки тестового E-mail.
Проверяет: Проверяет:
Возможность посылки тестового E-mail. Возможность посылки тестового E-mail по существующему адресу.
""" """
# Адрес куда отправлять e-mail # Адрес куда отправлять e-mail
# Пока фейковый sent_address = "audiomine.platform@gmail.com"
fake_address = "test@grandpas_village.com"
# Инициализация вкладки # Инициализация вкладки
email_notification_settings_tab = EmailNotificationsSettingsTab(browser) email_notification_settings_tab = EmailNotificationsSettingsTab(browser)
@ -176,7 +174,7 @@ class TestEmailNotificationsSettingsTab:
send_test_email_window = email_notification_settings_tab.click_test_button() send_test_email_window = email_notification_settings_tab.click_test_button()
send_test_email_window.input_email(fake_address) send_test_email_window.input_email(sent_address)
with browser.expect_response("**/e-nms/email/testEmail") as response_info: with browser.expect_response("**/e-nms/email/testEmail") as response_info:
send_test_email_window.click_test_button() send_test_email_window.click_test_button()
@ -187,6 +185,39 @@ class TestEmailNotificationsSettingsTab:
send_test_email_window.close() 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: def _get_default_value(self, setting_name: str, default_settings: dict) -> str| None:
for setting in default_settings: for setting in default_settings:
if setting["name"] == setting_name: if setting["name"] == setting_name:

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 уведомлений.

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

@ -74,7 +74,7 @@ class TestSessionSettingsTab:
else: else:
print(f"Error request session setings data from API: {response.status_text}") print(f"Error request session setings data from API: {response.status_text}")
#@pytest.mark.develop # @pytest.mark.develop
def test_edit_session_settings(self, browser: Page) -> None: def test_edit_session_settings(self, browser: Page) -> None:
"""Тест проверки возможности редактирования выбранных полей формы настройки времени жизни сеансов. """Тест проверки возможности редактирования выбранных полей формы настройки времени жизни сеансов.
@ -91,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

@ -16,16 +16,9 @@ from pages.backup_settings_tab import BackupSettingsTab
# @pytest.mark.smoke # @pytest.mark.smoke
class TestBackupSettingsTab: class TestBackupSettingsTab:
"""Набор тестов для вкладки 'Сеансы/Настройки'. """Набор тестов для вкладки 'Обслуживание и диагностика/Резервное копирование'.
Проверяет корректность отображения и функциональность элементов вкладки настройки времени жизни сеансов. Проверяет корректность отображения и функциональность элементов вкладки 'Резервное копирование'.
Тесты покрывают следующие сценарии:
1. test_backup_settings_tab_content: Тест содержимого вкладки 'Резервное копирование'
2. test_edit_session_settings: Тест проверки возможности редактирования выбранных полей формы
настройки времени жизни сеансов.
3. test_edit_session_setting_by_arrow: Тест проверки возможности увеличения/уменьшения значения
выбранного поля формы с помощью стрелочек Вверх/Вниз.
""" """
@ -37,6 +30,9 @@ class TestBackupSettingsTab:
1. Авторизацию в системе 1. Авторизацию в системе
2. Переход на вкладку 'Резервное копирование' через панель навигации 2. Переход на вкладку 'Резервное копирование' через панель навигации
""" """
browser.add_init_script("window.__PLAYWRIGHT__ = true;")
# Авторизация в системе # Авторизация в системе
login_page = LoginPage(browser) login_page = LoginPage(browser)
login_page.do_login() login_page.do_login()
@ -50,7 +46,7 @@ class TestBackupSettingsTab:
main_page.click_subpanel_item("Обслуживание и диагностика") main_page.click_subpanel_item("Обслуживание и диагностика")
main_page.click_subpanel_item("Резервное копирование") main_page.click_subpanel_item("Резервное копирование")
# @pytest.mark.develop @pytest.mark.develop
def test_backup_settings_tab_content(self, browser: Page) -> None: def test_backup_settings_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки 'Резервное копирование'. """Тест содержимого вкладки 'Резервное копирование'.
@ -66,20 +62,15 @@ class TestBackupSettingsTab:
backup_settings_tab.check_content() backup_settings_tab.check_content()
# запрос текущих установок настройки 'Инвентаризация/Параметры планировщика' # запрос текущих установок настройки 'Инвентаризация/Параметры планировщика'
current_settings = {} expected_inventory_settings = {}
cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb") cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb")
if cur_settings_response.status == 200: if cur_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(cur_settings_response) response_body = backup_settings_tab.get_response_body(cur_settings_response)
if response_body: if response_body:
current_settings = response_body[0].copy() expected_inventory_settings = response_body[0].copy()
# Проверка соответствия для значений настройки 'Инвентаризация/Параметры планировщика' if len(expected_inventory_settings) == 0:
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"]
if len(current_settings) != 0 or len(inventory_auto_backup) != 0 or len(inventory_backup_limitation) != 0:
# запрос дефолтных значений настройки 'Инвентаризация/Параметры планировщика' # запрос дефолтных значений настройки 'Инвентаризация/Параметры планировщика'
default_settings = {} default_settings = {}
default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb/meta") default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb/meta")
@ -88,34 +79,42 @@ class TestBackupSettingsTab:
if response_body: if response_body:
default_settings = response_body["fields"].copy() 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)
expected = current_settings["auto_backup"] if len(current_settings) != 0 else None # Проверка соответствия для значений настройки 'Инвентаризация/Параметры планировщика'
if expected is None: inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
expected = self._get_default_value("auto_backup", default_settings) 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,\ assert inventory_auto_backup == expected,\
f"Actual value {inventory_auto_backup} \ f"Actual value {inventory_auto_backup} \
is not equal expected {expected} for field 'Время создания резервной копии'" is not equal expected {expected} for field 'Время создания резервной копии'"
else:
assert False, "No value setting for field 'Время создания резервной копии'"
expected = current_settings["backup_limitation"] if len(current_settings) != 0 else None if inventory_backup_limitation:
if expected is None: expected = expected_inventory_settings["backup_limitation"]
expected = self._get_default_value("backup_limitation", default_settings)
assert inventory_backup_limitation == expected,\ assert inventory_backup_limitation == expected,\
f"Actual value {inventory_backup_limitation} \ f"Actual value {inventory_backup_limitation} \
is not equal expected {expected} for field 'Количество резервных копий'" is not equal expected {expected} for field 'Количество резервных копий'"
else:
assert False, "No value setting for field 'Количество резервных копий'"
# запрос текущих установок настройки 'Потоковые данные' # запрос текущих установок настройки 'Потоковые данные'
current_sd_settings = {} expected_sd_settings = {}
cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata") cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata")
if cur_settings_response.status == 200: if cur_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(cur_settings_response) response_body = backup_settings_tab.get_response_body(cur_settings_response)
if response_body: if response_body:
current_sd_settings = response_body[0].copy() expected_sd_settings = response_body[0].copy()
# Проверка соответствия для значений настройки 'Потоковые данные/Параметры планировщика' if len(expected_sd_settings) == 0:
streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
sd_auto_backup = streaming_data_scheduler_settings["auto_backup"]
if len(current_sd_settings) != 0 or len(sd_auto_backup) != 0:
# запрос дефолтных значений настройки 'Потоковые данные' # запрос дефолтных значений настройки 'Потоковые данные'
default_sd_settings = {} default_sd_settings = {}
default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata/meta") default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata/meta")
@ -124,19 +123,62 @@ class TestBackupSettingsTab:
if response_body: if response_body:
default_sd_settings = response_body["fields"].copy() 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 = current_sd_settings["auto_backup"] if len(current_settings) != 0 else None expected_sd_settings["data_limitation_logs"] = self._get_default_value("data_limitation_logs",
if expected is None: default_sd_settings)
expected = self._get_default_value("auto_backup", 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,\ assert sd_auto_backup == expected,\
f"Actual value {sd_auto_backup} \ f"Actual value {sd_auto_backup} \
is not equal expected {expected} for field 'Время создания резервной копии'" 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 # @pytest.mark.develop
def test_backup_settings_tab_check_backup_copies_amount(self, browser: Page) -> None: def test_backup_settings_tab_check_backup_copies_amount(self, browser: Page) -> None:
"""Тест проверки количества резервных копий.""" """Тест проверки количества резервных копий."""
# TO-DO: Тест проверки правильности времени их создания # TO-DO: Тест проверки правильности времени их создания
# To-DO: Ограничение на количество копий для потоковых данных?
# Инициализация страницы сеансов # Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser) backup_settings_tab = BackupSettingsTab(browser)
@ -148,8 +190,9 @@ class TestBackupSettingsTab:
# получение списка резервных копий настройки 'Инвентаризация' # получение списка резервных копий настройки 'Инвентаризация'
dumps = backup_settings_tab.get_inventory_dumps_list() dumps = backup_settings_tab.get_inventory_dumps_list()
print(dumps)
assert inventory_backup_limitation == len(dumps), \ assert inventory_backup_limitation >= len(dumps), \
f"Required to store {inventory_backup_limitation} but {len(dumps)} stores" f"Required to store {inventory_backup_limitation} but {len(dumps)} stores"
# @pytest.mark.develop # @pytest.mark.develop
@ -217,10 +260,8 @@ class TestBackupSettingsTab:
assert is_disabled, "Streaming data button to restore copy should be disabled" assert is_disabled, "Streaming data button to restore copy should be disabled"
# @pytest.mark.develop # @pytest.mark.develop
def test_backup_settings_tab_check_download_copy(self, browser: Page) -> None: def test_backup_settings_tab_check_inventory_download_copy(self, browser: Page) -> None:
"""Тест проверки возможности загрузки резервной копии.""" """Тест проверки возможности загрузки резервной копии 'Инвентаризация'."""
# TO-DO: Тест проверки возможности загрузки резервной копии для потоковых данных
# Инициализация страницы сеансов # Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser) backup_settings_tab = BackupSettingsTab(browser)
@ -239,13 +280,42 @@ class TestBackupSettingsTab:
os.remove(downloaded) os.remove(downloaded)
@pytest.mark.develop #@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: def test_backup_settings_tab_set_inventory_scheduler_settings(self, browser: Page) -> None:
"""Тест проверки возможности изменения значения настроек 'Инвентаризация/Параметры планировщика'.""" """Тест проверки возможности изменения значения настроек 'Инвентаризация/Параметры планировщика'."""
# Инициализация страницы сеансов # Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser) 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.click_edit_button()
backup_settings_tab.input_inventory_backup_creation_time("0 0 22 * * 7") backup_settings_tab.input_inventory_backup_creation_time("0 0 22 * * 7")
@ -254,6 +324,9 @@ class TestBackupSettingsTab:
backup_settings_tab.decrease_inventory_backups_number() backup_settings_tab.decrease_inventory_backups_number()
backup_settings_tab.increase_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_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
inventory_auto_backup = inventory_scheduler_settings["auto_backup"] inventory_auto_backup = inventory_scheduler_settings["auto_backup"]
inventory_backup_limitation = inventory_scheduler_settings["backup_limitation"] inventory_backup_limitation = inventory_scheduler_settings["backup_limitation"]
@ -264,8 +337,11 @@ class TestBackupSettingsTab:
f"Actual value '{inventory_backup_limitation}' \ f"Actual value '{inventory_backup_limitation}' \
is not equal expected '6' for field 'Количество резервных копий'" is not equal expected '6' for field 'Количество резервных копий'"
# temporarily until fix 1280 # восстановление ранее сохраненных значений
backup_settings_tab.click_cancel_button() 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 # @pytest.mark.develop
def test_backup_settings_tab_set_streaming_data_settings(self, browser: Page) -> None: def test_backup_settings_tab_set_streaming_data_settings(self, browser: Page) -> None:
@ -274,60 +350,73 @@ class TestBackupSettingsTab:
# Инициализация страницы сеансов # Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser) 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.click_edit_button()
backup_settings_tab.input_audit_time_period("3", "месяц") backup_settings_tab.input_audit_time_period("4", "месяц")
backup_settings_tab.increase_audit_time_period() backup_settings_tab.increase_audit_time_period()
backup_settings_tab.decrease_audit_time_period() backup_settings_tab.decrease_audit_time_period()
backup_settings_tab.input_logs_time_period("3", "месяц") backup_settings_tab.input_logs_time_period("4", "месяц")
backup_settings_tab.increase_logs_time_period() backup_settings_tab.increase_logs_time_period()
backup_settings_tab.decrease_logs_time_period() backup_settings_tab.decrease_logs_time_period()
backup_settings_tab.input_metrics_time_period("3", "месяц") backup_settings_tab.input_metrics_time_period("4", "месяц")
backup_settings_tab.increase_metrics_time_period() backup_settings_tab.increase_metrics_time_period()
backup_settings_tab.decrease_metrics_time_period() backup_settings_tab.decrease_metrics_time_period()
backup_settings_tab.input_syslog_time_period("3", "месяц") backup_settings_tab.input_syslog_time_period("4", "месяц")
backup_settings_tab.increase_syslog_time_period() backup_settings_tab.increase_syslog_time_period()
backup_settings_tab.decrease_syslog_time_period() backup_settings_tab.decrease_syslog_time_period()
backup_settings_tab.input_tasks_time_period("3", "месяц") backup_settings_tab.input_tasks_time_period("4", "месяц")
backup_settings_tab.increase_tasks_time_period() backup_settings_tab.increase_tasks_time_period()
backup_settings_tab.decrease_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.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() streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
data_limitation_default = streaming_data_settings["data_limitation_default"] data_limitation_default = streaming_data_settings["data_limitation_default"]
assert data_limitation_default == "3", \ assert data_limitation_default == "4", \
f"Actual value '{data_limitation_default}' is not equal expected '3' for category 'Аудит'" f"Actual value '{data_limitation_default}' is not equal expected '4' for category 'Аудит'"
interval_limitation_default = streaming_data_settings["interval_limitation_default"] interval_limitation_default = streaming_data_settings["interval_limitation_default"]
assert interval_limitation_default == "месяц", \ assert interval_limitation_default == "МЕСЯЦ", \
f"Actual value '{interval_limitation_default}' is not equal expected 'месяц' for category 'Аудит'" f"Actual value '{interval_limitation_default}' is not equal expected 'месяц' for category 'Аудит'"
data_limitation_logs = streaming_data_settings["data_limitation_logs"] data_limitation_logs = streaming_data_settings["data_limitation_logs"]
assert data_limitation_logs == "3", \ assert data_limitation_logs == "4", \
f"Actual value '{data_limitation_logs}' is not equal expected '3' for category 'Логи'" f"Actual value '{data_limitation_logs}' is not equal expected '4' for category 'Логи'"
interval_limitation_logs = streaming_data_settings["interval_limitation_logs"] interval_limitation_logs = streaming_data_settings["interval_limitation_logs"]
assert interval_limitation_logs == "месяц", \ assert interval_limitation_logs == "МЕСЯЦ", \
f"Actual value '{interval_limitation_logs}' is not equal expected 'месяц' for category 'Логи'" f"Actual value '{interval_limitation_logs}' is not equal expected 'месяц' for category 'Логи'"
data_limitation_metrics = streaming_data_settings["data_limitation_metrics"] data_limitation_metrics = streaming_data_settings["data_limitation_metrics"]
assert data_limitation_metrics == "3", \ assert data_limitation_metrics == "4", \
f"Actual value '{data_limitation_metrics}' is not equal expected '3' for category 'Метрики'" f"Actual value '{data_limitation_metrics}' is not equal expected '4' for category 'Метрики'"
interval_limitation_metrics = streaming_data_settings["interval_limitation_metrics"] interval_limitation_metrics = streaming_data_settings["interval_limitation_metrics"]
assert interval_limitation_metrics == "месяц", \ assert interval_limitation_metrics == "МЕСЯЦ", \
f"Actual value '{interval_limitation_metrics}' is not equal expected 'месяц' for category 'Метрики'" f"Actual value '{interval_limitation_metrics}' is not equal expected 'месяц' for category 'Метрики'"
data_limitation_syslog = streaming_data_settings["data_limitation_syslog"] data_limitation_syslog = streaming_data_settings["data_limitation_syslog"]
assert data_limitation_syslog == "3", \ assert data_limitation_syslog == "4", \
f"Actual value '{data_limitation_syslog}' is not equal expected '3' for category 'Системный лог'" f"Actual value '{data_limitation_syslog}' is not equal expected '4' for category 'Системный лог'"
interval_limitation_syslog = streaming_data_settings["interval_limitation_syslog"] interval_limitation_syslog = streaming_data_settings["interval_limitation_syslog"]
assert interval_limitation_syslog == "месяц", \ assert interval_limitation_syslog == "МЕСЯЦ", \
f"Actual value '{interval_limitation_syslog}' is not equal expected 'месяц' for category 'Системный лог'" f"Actual value '{interval_limitation_syslog}' is not equal expected 'месяц' for category 'Системный лог'"
data_limitation_tasks = streaming_data_settings["data_limitation_tasks"] data_limitation_tasks = streaming_data_settings["data_limitation_tasks"]
assert data_limitation_tasks == "3", \ assert data_limitation_tasks == "4", \
f"Actual value '{data_limitation_tasks}' is not equal expected '3' for category 'Действия'" f"Actual value '{data_limitation_tasks}' is not equal expected '4' for category 'Действия'"
interval_limitation_tasks = streaming_data_settings["interval_limitation_tasks"] interval_limitation_tasks = streaming_data_settings["interval_limitation_tasks"]
assert interval_limitation_tasks == "месяц", \ assert interval_limitation_tasks == "МЕСЯЦ", \
f"Actual value '{interval_limitation_tasks}' is not equal expected 'месяц' for category 'Действия'" 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_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
@ -336,8 +425,73 @@ class TestBackupSettingsTab:
f"Actual value '{streaming_data_auto_backup}' \ f"Actual value '{streaming_data_auto_backup}' \
is not equal expected '0 0 22 * * 7' for field 'Потоковые данные Время создания резервной копии'" is not equal expected '0 0 22 * * 7' for field 'Потоковые данные Время создания резервной копии'"
# temporarily until fix 1280 # восстановление ранее сохраненных значений
backup_settings_tab.click_cancel_button() 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: def _get_default_value(self, setting_name: str, default_settings: dict) -> str| None:

View File

@ -0,0 +1,284 @@
"""Модуль тестов вкладки 'Сертификаты'.
Содержит тесты для проверки корректности отображения
и функциональности элементов вкладки 'Сертификаты'.
"""
import string
import ssl
import random
import os
import json
from datetime import datetime
import jsondiff
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.certificates_tab import CertificatesTab
# @pytest.mark.smoke
class TestCertificatesTab:
"""Набор тестов для вкладки 'Обслуживание и диагностика/Сертификаты'.
Проверяет корректность отображения и функциональность элементов вкладки 'Сертификаты'.
"""
@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_certificates_tab_content(self, browser: Page) -> None:
"""Тест содержимого вкладки 'Сертификаты'.
Проверяет:
Наличие и корректность элементов интерфейса
"""
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
# Проверка элементов интерфейса
certificates_tab.check_content()
# @pytest.mark.develop
def test_certificates_tab_check_viewed_certificate(self, browser: Page) -> None:
"""Проверка соответствия выводимого сертификата информации из базы данных."""
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
certificates_tab.click_certificate_tab_button()
certificates_tab.wait_for_timeout(5000)
viewed_certificate = certificates_tab.get_certificate()
db_certificate_response = certificates_tab.send_get_api_request("api/certs/infoCert")
if db_certificate_response.status == 200:
response_body = certificates_tab.get_response_body(db_certificate_response)
if response_body:
response_body_json = json.dumps(response_body, ensure_ascii=False)
viewed_certificate_json = json.dumps(viewed_certificate, ensure_ascii=False)
diff = jsondiff.diff(response_body_json, viewed_certificate_json, syntax='symmetric')
assert len(diff) == 0, "Viewed certificate does not match the one taken from DB. DIFF is {diff}"
# @pytest.mark.develop
def test_certificates_tab_check_exported_certificate(self, browser: Page) -> None:
"""Проверка соответствия выводимого сертификата и содержимого экспортированного сертификата."""
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
certificates_tab.click_certificate_tab_button()
viewed_certificate = certificates_tab.get_certificate()
cert_file = certificates_tab.export_certificate()
try:
exported_certificate = ssl._ssl._test_decode_cert(cert_file)
except Exception as e:
assert False, f"Error decoding certificate {cert_file}: {e}"
else:
self._compare_certificates(exported_certificate, viewed_certificate)
finally:
# Удаление экспортированного файла
if os.path.exists(cert_file):
os.remove(cert_file)
# @pytest.mark.develop
def test_certificates_tab_check_import_certificate_input(self, browser: Page) -> None:
"""Частичная проверка действий при импорте сертификата."""
password = "12345"
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
certificates_tab.click_import_tab_button()
assert certificates_tab.is_import_button_disabled(), "Import certificate button should be disabled"
certificates_tab.input_password_field(password)
assert not certificates_tab.is_import_button_disabled(), "Import certificate button should be enabled"
actual_password = certificates_tab.get_password_field_value()
assert actual_password == password, \
f"Actual password input field value {actual_password} is not equal expected value {password}"
# @pytest.mark.develop
def test_certificates_tab_check_reissue_certificate_input(self, browser: Page) -> None:
"""Проверка заполнения полей при перевыпуске сертификата."""
input_values = {"CN":"Entcor-e", "O":"Entcor-e", "OU":"Entcor-e",
"C":"RU", "ST":"Moscow", "L":"Moscow"}
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
certificates_tab.click_reissue_tab_button()
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
certificates_tab.input_identification_cert_name_field(input_values["CN"])
certificates_tab.input_identification_organization_field(input_values["O"])
certificates_tab.input_identification_org_unit_field(input_values["OU"])
certificates_tab.input_location_country_field(input_values["C"])
certificates_tab.input_location_state_field(input_values["ST"])
certificates_tab.input_location_city_field(input_values["L"])
assert not certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be enabled"
actual_identification_fields_values = certificates_tab.get_identification_fields_values()
actual_location_fields_values = certificates_tab.get_location_fields_values()
val = actual_identification_fields_values.get("CN")
assert val == input_values["CN"], \
f"Actual value for field 'CN' {val} is not equal expected {input_values['CN']}"
val = actual_identification_fields_values.get("O")
assert val == input_values["O"], \
f"Actual value for field 'O' {val} is not equal expected {input_values['O']}"
val = actual_identification_fields_values.get("OU")
assert val == input_values["OU"], \
f"Actual value for field 'OU' {val} is not equal expected {input_values['OU']}"
val = actual_location_fields_values.get("C")
assert val == input_values["C"], \
f"Actual value for field 'C' {val} is not equal expected {input_values['C']}"
val = actual_location_fields_values.get("ST")
assert val == input_values["ST"], \
f"Actual value for field 'ST' {val} is not equal expected {input_values['ST']}"
val = actual_location_fields_values.get("L")
assert val == input_values["L"], \
f"Actual value for field 'L' {val} is not equal expected {input_values['L']}"
# @pytest.mark.develop
def test_certificates_tab_check_reissue_certificate_input_incorrect(self, browser: Page) -> None:
"""Проверка некорректного заполнения полей при перевыпуске сертификата."""
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
certificates_tab.click_reissue_tab_button()
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
cert_name = self._generate_random_string(65)
certificates_tab.input_identification_cert_name_field(cert_name)
identification_fields_values = certificates_tab.get_identification_fields_values()
val = identification_fields_values.get("CN")
assert len(val) == 64, "Field 'Certificate Name' should be no more than 64 characters long"
certificates_tab.input_location_country_field("R")
certificates_tab.input_location_state_field("")
certificates_tab.check_alert('error',
'Поле СТРАНА (С) должно \n содержать 2 символа')
certificates_tab.input_location_country_field("RUS")
location_fields_values = certificates_tab.get_location_fields_values()
val = location_fields_values.get("C")
assert val == "RU", "Field 'Country' should be only 2 characters long"
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
# @pytest.mark.develop
def test_certificates_tab_check_reissue_certificate_input_mandatory_fields(self, browser: Page) -> None:
"""Проверка некорректного заполнения полей при перевыпуске сертификата."""
# Инициализация страницы
certificates_tab = CertificatesTab(browser)
certificates_tab.click_reissue_tab_button()
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
# Кнопка перевыпуска сертификата становится активной только после заполнения обязательных полей
cert_name = self._generate_random_string(15)
certificates_tab.input_identification_cert_name_field(cert_name)
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
org_name = self._generate_random_string(5)
certificates_tab.input_identification_organization_field(org_name)
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
certificates_tab.input_location_country_field("RU")
assert not certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be enabled"
certificates_tab.input_identification_organization_field("")
assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled"
org_name = self._generate_random_string(5)
certificates_tab.input_identification_organization_field(org_name)
assert not certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be enabled"
# Вспомогательные функции
def _compare_certificates(self, exported: dict, viewed: dict) -> None:
""" Сравнение содержимого отображаемого сертификата и экпортированного """
fields = {"countryName":"C", "stateOrProvinceName":"ST", "localityName":"L",
"organizationName":"O", "commonName":"CN"}
version = "v" + str(exported["version"])
viewed_version = viewed["baseInfo"]["version"]
assert viewed_version == version, \
f"Viewed certificate version {viewed_version} is not equal exported certificate version {version}"
serial_number = exported["serialNumber"]
viewed_serial_number = viewed["baseInfo"]["serialNumber"].upper()
assert viewed_serial_number == serial_number, \
f"Viewed certificate serial number {viewed_serial_number} is not equal exported \
certificate serial number {serial_number}"
not_before = exported["notBefore"]
time_string = viewed["validity"]["notBefore"]
datetime_object = datetime.fromisoformat(time_string.replace('Z', '+00:00' if not time_string.endswith('+00:00') else ''))
viewed_not_before = datetime_object.strftime("%B %d %H:%M:%S %Y") + " GMT"
assert viewed_version == version, \
f"Viewed certificate validity not before {viewed_not_before} is not equal exported certificate {not_before}"
not_after = exported["notAfter"]
time_string = viewed["validity"]["notAfter"]
datetime_object = datetime.fromisoformat(time_string.replace('Z', '+00:00' if not time_string.endswith('+00:00') else ''))
viewed_not_after = datetime_object.strftime("%B %d %H:%M:%S %Y") + " GMT"
assert viewed_version == version, \
f"Viewed certificate validity not after {viewed_not_after} is not equal exported certificate {not_after}"
count = len(exported["subject"])
for i in range(count):
name = exported["subject"][i][0][0]
field = fields.get(name)
if field:
val = exported["subject"][i][0][1]
viewed_val = viewed["subject"][field]
assert viewed_val == val, \
f"Viewed certificate field {field} value {viewed_val} is not equal exported certificate {val}"
def _generate_random_string(self, length):
# Набор символов: ascii_letters + digits (буквы и цифры)
characters = string.ascii_letters + string.digits
# Выбираем случайные символы length раз
random_string = ''.join(random.choices(characters, k=length))
return random_string

View File

@ -65,7 +65,8 @@ class TestUsersTabEditUser:
ut = UsersTab(browser) ut = UsersTab(browser)
# Удаляем тестовых пользователей # Удаляем тестовых пользователей
test_users = ["TestUser", "TestUserAutoOperator", "TestUserAutoAdmin"] test_users = ["TestUser", "TestUserAutoOperator", "TestUserAutoAdmin",
"TestUserTestOperator", "TestUserToBlock"]
for user_name in test_users: for user_name in test_users:
# Проверяем существует ли пользователь и удаляем его # Проверяем существует ли пользователь и удаляем его
@ -167,6 +168,8 @@ class TestUsersTabEditUser:
if len(new_password) == 0: if len(new_password) == 0:
assert False, "Unsuccessful password reset" assert False, "Unsuccessful password reset"
ut.close_edit_user_window(user_data["name"])
mp.do_logout()
new_lp = LoginPage(browser) new_lp = LoginPage(browser)
new_lp.do_login(username=user_data["name"], password=new_password) new_lp.do_login(username=user_data["name"], password=new_password)
@ -214,3 +217,85 @@ class TestUsersTabEditUser:
mp.click_subpanel_item("Пользователи") mp.click_subpanel_item("Пользователи")
mp.click_subpanel_item("Пользователи") mp.click_subpanel_item("Пользователи")
ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"]) ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"])
def test_edit_user_name(self, browser: Page, cleanup_users: None) -> None:
"""Проверяет изменение имени пользователя.
Args:
browser: Экземпляр страницы Playwright.
cleanup_users: Фикстура для автоматического удаления пользователя после теста.
"""
user_data: Dict[str, str] = {"name": "TestUserAutoOperator", "role": "Оператор", "password": "1232456789abcd"}
mp = MainPage(browser)
ut = UsersTab(browser)
browser.wait_for_timeout(500)
ut.open_add_user_window()
ut.add_new_user(user_data)
mp.click_subpanel_item("Пользователи")
mp.click_subpanel_item("Пользователи")
ut.open_edit_user_page_by_user(user_data["name"], user_data["role"])
new_user_data = {"name": "TestUserTestOperator"}
ut.edit_user(user_data["name"], new_user_data)
mp.click_subpanel_item("Пользователи")
mp.click_subpanel_item("Пользователи")
ut.should_be_user_in_table(new_user_data["name"], user_data["role"])
ut.open_edit_user_page_by_user(new_user_data["name"], user_data["role"])
ut.delete_user(new_user_data["name"])
mp.click_subpanel_item("Пользователи")
mp.click_subpanel_item("Пользователи")
ut.should_not_be_user_in_table(new_user_data["name"], user_data["role"])
# @pytest.mark.develop
def test_block_user(self, browser: Page, cleanup_users: None) -> None:
"""Проверяет возможность блокировки пользователя.
Args:
browser: Экземпляр страницы Playwright.
cleanup_users: Фикстура для автоматического удаления пользователя после теста.
"""
user_data: Dict[str, str] = {"name": "TestUserToBlock", "role": "Администратор", "password": "123456789abcd"}
mp = MainPage(browser)
ut = UsersTab(browser)
browser.wait_for_timeout(500)
ut.open_add_user_window()
ut.add_new_user(user_data)
mp.do_logout()
lp_to_check = LoginPage(browser)
lp_to_check.do_login(username=user_data["name"], password=user_data["password"])
mp_to_check = MainPage(browser)
mp_to_check.do_logout()
admin_lp = LoginPage(browser)
admin_lp.do_login()
admin_mp = MainPage(browser)
admin_mp.should_be_navigation_panel()
admin_mp.click_main_navigation_panel_item("Настройки")
admin_mp.click_subpanel_item("Пользователи")
admin_ut = UsersTab(browser)
admin_ut.open_edit_user_page_by_user(user_data["name"], user_data["role"])
new_user_data = {"blocking_checked": True}
admin_ut.edit_user(user_data["name"], new_user_data)
admin_mp.do_logout()
lp_to_check_blocked = LoginPage(browser)
lp_to_check_blocked.do_unsuccessful_login(username=user_data["name"], password=user_data["password"])
admin_lp_1 = LoginPage(browser)
admin_lp_1.do_login()
admin_mp_1 = MainPage(browser)
admin_mp_1.should_be_navigation_panel()
admin_mp_1.click_main_navigation_panel_item("Настройки")
admin_mp_1.click_subpanel_item("Пользователи")
admin_ut_1 = UsersTab(browser)
admin_ut_1.open_edit_user_page_by_user(user_data["name"], user_data["role"])
admin_ut_1.delete_user(user_data["name"])
admin_mp_1.click_subpanel_item("Пользователи")
admin_mp_1.click_subpanel_item("Пользователи")
admin_ut_1.should_not_be_user_in_table(user_data["name"], user_data["role"])

View File

@ -59,3 +59,23 @@ class TestUsersTab:
ut = UsersTab(browser) ut = UsersTab(browser)
ut.should_be_toolbar_buttons() ut.should_be_toolbar_buttons()
def test_users_tab_check_row_highlighting(self, browser: Page) -> None:
"""Проверяет выделение цветом указанной строки таблицы.
Args:
browser: Экземпляр страницы Playwright.
"""
ut = UsersTab(browser)
# Проверка наличия таблицы пользователей
ut.should_be_users_table()
# Получение количества строк в таблице
rows_count = ut.get_rows_count()
# Проверка выделения строк
ut.check_users_table_row_highlighting(0)
ut.check_users_table_row_highlighting(int(rows_count / 2))
ut.check_users_table_row_highlighting(rows_count - 1)