Radislav 2026-04-24 08:11:47 +03:00
commit a07cb43b80
46 changed files with 1717 additions and 186 deletions

View File

@ -86,7 +86,7 @@ class DatePickerComponent(BaseComponent):
days_table_locator = self.page.locator(DatePickerLocators.DATE_PICKER_TABLE_DAYS)
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()
if visible:
day_button_locator.click()

View File

@ -94,7 +94,7 @@ class EventsContainerComponent(BaseComponent):
self.last_page.click()
def click_filter_button(self) -> EventsFilterPanel:
"""Нажатие кнопки перехода на первую страницу"""
"""Нажатие кнопки фильтр"""
self.toolbar.click_button("filter_button")
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)
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,
index: int,
convert2timestamp=False) -> bool:

View File

@ -252,12 +252,13 @@ class NavigationPanelComponent(BaseComponent):
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:
locator: Локатор элемента или строка с CSS/XPath.
item_name: Текст элемента для проверки.
parent: Текст родительского элемента (необязательный параметр)
Note:
Временная обработка для элементов с текстом 'Шаблоны'.
@ -265,17 +266,13 @@ class NavigationPanelComponent(BaseComponent):
msg = f"Navigation panel item '{item_name}' is not visible"
## временно: в навигационной панели есть две панели с именем Шаблоны
## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны
## Шаблоны_2 для Настройки/ZTP/Шаблоны
loc = self.get_locator(locator)
if item_name == "Шаблоны_1":
loc = loc.get_by_text("Шаблоны").first
elif item_name == "Шаблоны_2":
loc = loc.get_by_text("Шаблоны").nth(1)
else:
loc = loc.get_by_text(item_name)
self.check_visibility(loc, msg)
if parent:
parent_loc = f"//div[contains(@class, 'v-treeview-node') and contains(.,'{parent}')]"
loc = loc.locator(parent_loc)
item_loc = loc.get_by_text(item_name).first
self.check_visibility(item_loc, msg)
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)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
logger.info("Table body is missing")
else:
rows_count = len(events_table)
for j in range(1, rows_count-1):
self.check_events_table_status_button(j, "Статус")
self.should_be_pagination_buttons()

View File

@ -62,6 +62,9 @@ class AuditEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
logger.info("Table body is missing")
@ -97,6 +100,9 @@ class AuditEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
logger.info("Table body is missing")

View File

@ -61,6 +61,9 @@ class EventsTabContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
logger.info("Table body is missing")

View File

@ -66,9 +66,19 @@ class MaintenanceEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
rows_count = len(events_table)
if rows_count == 1:
logger.info("Table body is missing")
else:
j = 1
while j < rows_count:
self.check_events_table_status_button(j, "Состояние")
j += 1
self.should_be_pagination_buttons()

View File

@ -61,9 +61,21 @@ class SystemLogEventsContainer(EventsContainerComponent):
assert False, "The contents of the events table are missing"
self.check_events_table_headers(events_table[0], expected_headers)
for i in range(len(expected_headers)):
actual_state = self.get_arrow_button_state(i)
assert actual_state == "down", f"Arrow state for column {i} should be 'down'"
if len(events_table) == 1:
logger.info("Table body is missing")
else:
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()

View File

@ -181,6 +181,7 @@ class DateInput(BaseComponent):
result = False
inner_text = self.switch_mode_button.get_text(0).strip()
print(inner_text)
if inner_text == "keyboard":
result = True
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(
self.page.locator(ModalWindowLocators.INPUT_FORM_USER_DATA))
# Поле Имя
loc = elements_locators.get("Имя").locator(ModalWindowLocators.INPUT_FORM_USER_DATA_FIELD_NAME)
name_input = TextInput(page, loc, "name_input")
@ -213,16 +212,16 @@ class EditUserModalWindow(ModalWindowComponent):
if "blocking_checked" in fields:
checkbox = self.get_content_item("blocking_checkbox")
if user_data["blocking_checked"]:
checkbox.check()
checkbox.check(force=True)
else:
checkbox.uncheck()
checkbox.uncheck(force=True)
if "push_notification_checked" in fields:
checkbox = self.get_content_item("push_notification_checkbox")
if user_data["push_notification_checked"]:
checkbox.check()
checkbox.check(force=True)
else:
checkbox.uncheck()
checkbox.uncheck(force=True)
save_button = self.get_button_by_name("save")
save_button.click()

View File

@ -74,3 +74,9 @@ class ViewTaskModalWindow(ModalWindowComponent):
""" Проверка соответствия заголовка таблицы ожидаемому"""
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

@ -153,7 +153,13 @@ class SelectionBarComponent(BaseComponent):
self.selection_bar_locator.click(force=True)
# Ждем появления выпадающего списка
self.wait_for_timeout(1500)
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:
"""Выбор значения из списка"""

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

@ -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')]"
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"
# Локаторы для элементов выпадающего списка
LIST_ACTIVE = "//div[contains(@class, 'menuable__content__active')]"
LISTBOX = "//div[@role='list']"
LIST_ITEMS = "//div[contains(@class, 'menuable__content__active')]//div[@role='list']//div[@role='listitem']"

View File

@ -16,7 +16,6 @@ from components_derived.settings_form_component import SettingsFormComponent
from components_derived.selection_bar_component import SelectionBarComponent
from pages.base_page import BasePage
class BackupSettingsTab(BasePage):
"""Класс для работы с вкладкой настройки резервного копирования.
@ -82,6 +81,12 @@ class BackupSettingsTab(BasePage):
self.toolbar.check_button_visibility("save")
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:
"""Удаление ранее выбранного имени дампа"""
@ -94,7 +99,6 @@ class BackupSettingsTab(BasePage):
dump_selector = self.streaming_data_settings.get_content_item("streaming_data_dump_selector")
dump_selector.clear_selections()
def create_inventory_copy(self) -> None:
"""Создать резервную копию Системы."""
@ -147,6 +151,33 @@ class BackupSettingsTab(BasePage):
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:
"""Возвращает текущее значение полей настроек 'Инвентаризация/Параметры планировщика'.

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

@ -296,6 +296,19 @@ class LDAPAuthSettingsTab(BasePage):
if name == "password_hidden_icon":
is_hidden_state = item.is_password_hidden()
assert is_hidden_state, "Password hidden icon should be in hidden state"
hidden_password = self.get_password_setting_value()
self.click_password_hidden_icon()
is_hidden_state = item.is_password_hidden()
assert not is_hidden_state, "Password hidden icon should be in viewed state"
viewed_password = self.get_password_setting_value()
assert len(hidden_password) == len(viewed_password), \
"The lengths of hidden and viewed passwords should be equal"
self.click_password_hidden_icon()
is_hidden_state = item.is_password_hidden()
assert is_hidden_state, "Password hidden icon should be in hidden state"
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара.

View File

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

View File

@ -205,7 +205,7 @@ class MainPage(BasePage):
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:
@ -214,7 +214,7 @@ class MainPage(BasePage):
self.navigation_panel.check_item_visibility(
NavigationPanelLocators.PANEL_MAIN,
item_name
item_name, parent
)
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)
# temporararily
self.click_cancel_button()
# self.click_save_button()
# alert_type = self.alert.get_alert_type()
@ -183,16 +181,14 @@ class SessionSettingsTab(BasePage):
"""Скроллинг вниз формы настроек времени жизни сессии.
"""
locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
has_text="Время жизни сеанса")
locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.settings_form.scroll_down(locator)
def scroll_up(self) -> None:
"""Скроллинг вверх формы настроек времени жизни сессии.
"""
locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
has_text="Время жизни сеанса")
locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
self.settings_form.scroll_up(locator)
# Проверки:
@ -227,8 +223,7 @@ class SessionSettingsTab(BasePage):
"""Проверка возможности вертикального скроллинга формы настроек времени жизни сессии.
"""
locator = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).filter(
has_text="Время жизни сеанса")
locator = self.page.locator(SettingsFormLocators.SETTINGS_FORM_INPUT_FORM_CONTAINER)
return self.settings_form.check_vertical_scrolling(locator)
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"
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:
"""Открывает окно добавления пользователя.
@ -396,6 +408,21 @@ class UsersTab(BasePage):
if verify:
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:
"""Проверяет наличие тулбара.

View File

@ -1,5 +1,5 @@
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': 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': 800, 'height': 200}" test_session_settings.py

View File

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

View File

@ -32,7 +32,7 @@ class TestSessionSettingsForm:
mp.click_subpanel_item("Настройки", parent="Сеансы")
def test_scrolling(self, browser: Page) -> None:
"""Проверяет прокрутку таблицы статусов сервисов.
"""Проверяет прокрутку формы редактирования настроек.
Args:
browser: Экземпляр страницы Playwright.
@ -55,5 +55,5 @@ class TestSessionSettingsForm:
sst.wait_for_timeout(3000)
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)

View File

@ -78,7 +78,7 @@ class TestUsersModalWindow:
ut = UsersTab(browser)
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()
assert is_scrollable_vertically, "Should be vertical scrolling"

View File

@ -20,7 +20,7 @@ class TestDateInputComponent:
# @pytest.mark.develop
def test_date_input_content(self, browser: Page) -> None:
"""Проверяет содержимое компонента ввода даты.
put
Args:
browser: Экземпляр страницы Playwright.
"""
@ -61,7 +61,8 @@ put
date_input.click_switch_mode_button()
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, проверяем результат
expected_date = "15.01.2024"
date_input.input_date(expected_date)
@ -95,7 +96,8 @@ put
date_input.click_switch_mode_button()
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:
date_input.input_date("1.01.2020")
except AssertionError as e:
@ -169,7 +171,8 @@ put
date_input.click_switch_mode_button()
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, проверяем результат
date_input.input_date("15.01.2024")
browser.wait_for_timeout(300)
@ -208,7 +211,8 @@ put
date_input.click_switch_mode_button()
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"
else:
# выбираем 16 января 2024 08:11, проверяем результат
@ -245,26 +249,27 @@ put
date_input.click_switch_mode_button()
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, проверяем результат
date_input.input_date("15.01.2024")
browser.wait_for_timeout(300)
try:
date_input.time_date("1:01")
date_input.input_time("1:01")
except AssertionError as e:
actual_err = f"{e}"
expected_err = "Incorrect time format: should be 'hh:mm'"
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:
date_input.input_date("o2.01")
date_input.input_date("o2.01.2024")
except AssertionError as 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, \
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:
date_input.input_time("01:1")
@ -272,14 +277,14 @@ put
actual_err = f"{e}"
expected_err = "Incorrect time format: should be 'hh:mm'"
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:
date_input.input_date("01:o1")
date_input.input_date("01.o1.2024")
except AssertionError as 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, \
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:
assert False, "Should be text date input mode"

View File

@ -179,6 +179,9 @@ class TestDatePickerComponent:
browser.wait_for_timeout(300)
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}"
actual_date = date_input.get_date_field_value()
assert expected_date == actual_date, \

View File

@ -53,6 +53,7 @@ class TestActionsEventsContainer:
# Получение количества строк в таблице Реальное время
rows_count = actions_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк
actions_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
@ -64,6 +65,7 @@ class TestActionsEventsContainer:
# Получение количества строк в таблице Архив
rows_count = actions_events_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк
actions_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
@ -71,7 +73,7 @@ class TestActionsEventsContainer:
if rows_count > 3:
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):
"""Проверяет возможность скроллинга таблицы событий на примере таблицы Архив.
@ -174,6 +176,15 @@ class TestActionsEventsContainer:
task_view_window.check_stages_table_headers(stages_table_content[0], expected_task_headers)
stages_table_content.pop(0)
rows_count = len(stages_table_content)
if rows_count != 0:
# Проверка выделения строк
task_view_window.check_stages_table_row_highlighting(0)
if rows_count > 1:
task_view_window.check_stages_table_row_highlighting(rows_count - 1)
if rows_count > 3:
task_view_window.check_stages_table_row_highlighting(int(rows_count / 2))
payload = {"filter": {"page": 1},
"count": 40}
@ -244,9 +255,9 @@ class TestActionsEventsContainer:
# convert2timestamp=True)
# assert is_descending_order, "Column data should be in descending order"
# @pytest.mark.develop
@pytest.mark.develop
def test_real_time_events_table_pagination(self, browser: Page):
"""Проверяет возможность пагинации таблицы событий.
"""Проверяет возможность пагинации таблицы событий на примере вкладки 'Реальное время'.
Args:
browser: Экземпляр страницы Playwright.
@ -266,6 +277,7 @@ class TestActionsEventsContainer:
# Проверка начального состояния
tested_pages = 1
pages = 1
payload = {"filter": {"page": 1},
"count": 40}
@ -357,7 +369,7 @@ class TestActionsEventsContainer:
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages:
if counter == tested_pages and counter == pages:
actions_events_container.should_be_final_state()
else:
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()
if rows_count != 0:
# Проверка выделения строк
audit_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
audit_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
audit_events_container.check_events_table_row_highlighting(int(rows_count / 2))
def test_events_table_scrolling(self, browser: Page):
@ -180,6 +183,7 @@ class TestAuditEventsContainer:
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "default",
@ -279,7 +283,7 @@ class TestAuditEventsContainer:
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages:
if counter == tested_pages and counter == pages:
audit_events_container.should_be_final_state()
else:
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()
if rows_count != 0:
# Проверка выделения строк
security_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
security_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
security_events_container.check_events_table_row_highlighting(int(rows_count / 2))
# Выход из системы текущего пользователя
@ -243,6 +246,7 @@ class TestAuditEventsContainerSecurity:
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "default",
@ -343,7 +347,7 @@ class TestAuditEventsContainerSecurity:
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages:
if counter == tested_pages and counter == pages:
security_events_container.should_be_final_state()
else:
security_events_container.should_be_all_enabled()

View File

@ -39,7 +39,7 @@ class TestEventPanel:
# Проверяем соответствие тултипов информации на кнопках
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():
button_counter = button_event_counters.get(event)
@ -49,7 +49,7 @@ class TestEventPanel:
if button_counter != counter:
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:
"""Проверяет состояние и количество кнопок расширения рабочей области панели событий.

View File

@ -53,9 +53,12 @@ class TestEventsTabContainer:
# Получение количества строк в таблице
rows_count = events_tab_container.get_events_table_rows_count()
if rows_count != 0:
# Проверка выделения строк
events_tab_container.check_events_table_row_highlighting(0)
if rows_count > 1:
events_tab_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
events_tab_container.check_events_table_row_highlighting(int(rows_count / 2))
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
@ -184,6 +187,7 @@ class TestEventsTabContainer:
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "syslogs",
@ -277,7 +281,7 @@ class TestEventsTabContainer:
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages:
if counter == tested_pages and counter == pages:
events_tab_container.should_be_final_state()
else:
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.check_content()
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
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()
if rows_count != 0:
# Проверка выделения строк
maintenance_events_container.check_events_table_row_highlighting(0)
if rows_count > 1:
maintenance_events_container.check_events_table_row_highlighting(rows_count - 1)
if rows_count > 3:
maintenance_events_container.check_events_table_row_highlighting(int(rows_count / 2))
@pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий")
@ -74,6 +75,9 @@ class TestMaintenanceEventsContainer:
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()
# Проверка, что панель с таблицей открыта
@ -127,7 +131,7 @@ class TestMaintenanceEventsContainer:
# Проверка видимости первой строки после прокрутки
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):
"""Проверяет сортировку колонки 'Время' в таблице событий.
@ -164,7 +168,7 @@ class TestMaintenanceEventsContainer:
convert2timestamp=True)
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):
"""Проверяет возможность пагинации таблицы событий.
@ -184,6 +188,7 @@ class TestMaintenanceEventsContainer:
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"id": [ "/physical"],
@ -196,7 +201,11 @@ class TestMaintenanceEventsContainer:
response_body = mp.get_response_body(response)
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:
tested_pages = 5
@ -278,7 +287,7 @@ class TestMaintenanceEventsContainer:
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages:
if counter == tested_pages and counter == pages:
maintenance_events_container.should_be_final_state()
else:
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()
if rows_count != 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)
if rows_count > 3:
system_log_events_container.check_events_table_row_highlighting(int(rows_count / 2))
def test_events_table_scrolling(self, browser: Page):
@ -185,6 +188,7 @@ class TestSystemLogEventsContainer:
# Проверка начального состояния
tested_pages = 1
pages = 1
# to do: некорректный запрос в бэк, должно быть "filter": {"page": 0}
payload = {"table": "logs",
@ -278,7 +282,7 @@ class TestSystemLogEventsContainer:
counter += 1
browser.wait_for_timeout(2000)
if counter == tested_pages:
if counter == tested_pages and counter == pages:
system_log_events_container.should_be_final_state()
else:
system_log_events_container.should_be_all_enabled()

View File

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

View File

@ -91,12 +91,14 @@ class TestSessionSettingsTab:
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():
# updated_value = updated_settings.get(key)
# assert updated_value == value, f"{key} updated value {updated_value} is not equal expected value {value}"
for key, value in new_settings.items():
updated_value = updated_settings.get(key)
assert updated_value == value, f"{key} updated value {updated_value} is not equal expected value {value}"
# temporarily
session_settings_tab.click_cancel_button()
# @pytest.mark.develop
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
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. Авторизацию в системе
2. Переход на вкладку 'Резервное копирование' через панель навигации
"""
browser.add_init_script("window.__PLAYWRIGHT__ = true;")
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
@ -66,20 +62,15 @@ class TestBackupSettingsTab:
backup_settings_tab.check_content()
# запрос текущих установок настройки 'Инвентаризация/Параметры планировщика'
current_settings = {}
expected_inventory_settings = {}
cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb")
if cur_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(cur_settings_response)
if response_body:
current_settings = response_body[0].copy()
expected_inventory_settings = response_body[0].copy()
# Проверка соответствия для значений настройки 'Инвентаризация/Параметры планировщика'
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:
if len(expected_inventory_settings) == 0:
# запрос дефолтных значений настройки 'Инвентаризация/Параметры планировщика'
default_settings = {}
default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupcmdb/meta")
@ -88,34 +79,42 @@ class TestBackupSettingsTab:
if response_body:
default_settings = response_body["fields"].copy()
expected_inventory_settings["auto_backup"] = self._get_default_value("auto_backup",
default_settings)
expected_inventory_settings["backup_limitation"] = self._get_default_value("backup_limitation",
default_settings)
expected = current_settings["auto_backup"] if len(current_settings) != 0 else None
if expected is None:
expected = self._get_default_value("auto_backup", default_settings)
# Проверка соответствия для значений настройки 'Инвентаризация/Параметры планировщика'
inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
inventory_auto_backup = inventory_scheduler_settings.get("auto_backup")
inventory_backup_limitation = inventory_scheduler_settings.get("backup_limitation")
if inventory_auto_backup:
expected = expected_inventory_settings["auto_backup"]
assert inventory_auto_backup == expected,\
f"Actual value {inventory_auto_backup} \
is not equal expected {expected} for field 'Время создания резервной копии'"
else:
assert False, "No value setting for field 'Время создания резервной копии'"
expected = current_settings["backup_limitation"] if len(current_settings) != 0 else None
if expected is None:
expected = self._get_default_value("backup_limitation", default_settings)
if inventory_backup_limitation:
expected = expected_inventory_settings["backup_limitation"]
assert inventory_backup_limitation == expected,\
f"Actual value {inventory_backup_limitation} \
is not equal expected {expected} for field 'Количество резервных копий'"
else:
assert False, "No value setting for field 'Количество резервных копий'"
# запрос текущих установок настройки 'Потоковые данные'
current_sd_settings = {}
expected_sd_settings = {}
cur_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata")
if cur_settings_response.status == 200:
response_body = backup_settings_tab.get_response_body(cur_settings_response)
if response_body:
current_sd_settings = response_body[0].copy()
expected_sd_settings = response_body[0].copy()
# Проверка соответствия для значений настройки 'Потоковые данные/Параметры планировщика'
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:
if len(expected_sd_settings) == 0:
# запрос дефолтных значений настройки 'Потоковые данные'
default_sd_settings = {}
default_settings_response = backup_settings_tab.send_get_api_request("e-cmdb/api/backupstreamingdata/meta")
@ -124,19 +123,60 @@ class TestBackupSettingsTab:
if response_body:
default_sd_settings = response_body["fields"].copy()
expected_sd_settings["auto_backup"] = self._get_default_value("auto_backup", default_sd_settings)
expected_sd_settings["data_limitation_default"] = self._get_default_value(
"data_limitation_default", default_sd_settings)
expected_sd_settings["interval_limitation_default"] = self._get_default_value(
"interval_limitation_default", default_sd_settings)
expected = current_sd_settings["auto_backup"] if len(current_settings) != 0 else None
if expected is None:
expected = self._get_default_value("auto_backup", default_sd_settings)
expected_sd_settings["data_limitation_logs"] = self._get_default_value("data_limitation_logs",
default_sd_settings)
expected_sd_settings["interval_limitation_logs"] = self._get_default_value(
"interval_limitation_logs", default_sd_settings)
expected_sd_settings["data_limitation_metrics"] = self._get_default_value("data_limitation_metrics",
default_sd_settings)
expected_sd_settings["interval_limitation_metrics"] = self._get_default_value(
"interval_limitation_metrics",
default_sd_settings)
expected_sd_settings["data_limitation_syslog"] = self._get_default_value("data_limitation_syslog",
default_sd_settings)
expected_sd_settings["interval_limitation_syslog"] = self._get_default_value(
"interval_limitation_syslog", default_sd_settings)
expected_sd_settings["data_limitation_tasks"] = self._get_default_value("data_limitation_tasks",
default_sd_settings)
expected_sd_settings["interval_limitation_tasks"] = self._get_default_value(
"interval_limitation_tasks", default_sd_settings)
# Проверка соответствия для значений настроек 'Потоковые данные и Параметры планировщика'
dates = {"day":"ДЕНЬ", "hour":"ЧАС", "month":"МЕСЯЦ", "year":"ГОД"}
streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
sd_auto_backup = streaming_data_scheduler_settings.get("auto_backup")
streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
if sd_auto_backup:
expected = expected_sd_settings["auto_backup"]
assert sd_auto_backup == expected,\
f"Actual value {sd_auto_backup} \
is not equal expected {expected} for field 'Время создания резервной копии'"
else:
assert False, "No value setting for field 'Время создания резервной копии' streaming data"
settings_list = streaming_data_settings.keys()
for setting in settings_list:
expected = expected_sd_settings[setting]
if dates.get(expected):
expected = dates[expected]
actual = streaming_data_settings[setting]
assert actual == expected,\
f"Actual value {actual} is not equal expected {expected} for field '{setting}'"
# @pytest.mark.develop
def test_backup_settings_tab_check_backup_copies_amount(self, browser: Page) -> None:
"""Тест проверки количества резервных копий."""
# TO-DO: Тест проверки правильности времени их создания
# To-DO: Ограничение на количество копий для потоковых данных?
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
@ -148,8 +188,9 @@ class TestBackupSettingsTab:
# получение списка резервных копий настройки 'Инвентаризация'
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"
# @pytest.mark.develop
@ -217,10 +258,8 @@ class TestBackupSettingsTab:
assert is_disabled, "Streaming data button to restore copy should be disabled"
# @pytest.mark.develop
def test_backup_settings_tab_check_download_copy(self, browser: Page) -> None:
"""Тест проверки возможности загрузки резервной копии."""
# TO-DO: Тест проверки возможности загрузки резервной копии для потоковых данных
def test_backup_settings_tab_check_inventory_download_copy(self, browser: Page) -> None:
"""Тест проверки возможности загрузки резервной копии 'Инвентаризация'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
@ -239,13 +278,42 @@ class TestBackupSettingsTab:
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:
"""Тест проверки возможности изменения значения настроек 'Инвентаризация/Параметры планировщика'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# считываем и запоминаем текущие знчения
orig_inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
orig_auto_backup = orig_inventory_scheduler_settings.get("auto_backup")
assert orig_auto_backup, "Сouldn't read the value of 'auto backup' from inventory scheduler settings"
orig_backup_limitation = orig_inventory_scheduler_settings.get("backup_limitation")
assert orig_backup_limitation, "Сouldn't read the value of 'backup limitation' from inventory scheduler settings"
# устанавливаем новые значения
backup_settings_tab.click_edit_button()
backup_settings_tab.input_inventory_backup_creation_time("0 0 22 * * 7")
@ -254,6 +322,9 @@ class TestBackupSettingsTab:
backup_settings_tab.decrease_inventory_backups_number()
backup_settings_tab.increase_inventory_backups_number()
backup_settings_tab.click_save_button()
# проверка ожидаемых значений
inventory_scheduler_settings = backup_settings_tab.get_inventory_scheduler_settings_values()
inventory_auto_backup = inventory_scheduler_settings["auto_backup"]
inventory_backup_limitation = inventory_scheduler_settings["backup_limitation"]
@ -264,70 +335,86 @@ class TestBackupSettingsTab:
f"Actual value '{inventory_backup_limitation}' \
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:
"""Тест проверки возможности изменения значения настроек 'Потоковые данные'."""
# Инициализация страницы сеансов
backup_settings_tab = BackupSettingsTab(browser)
# считываем и запоминаем текущие знчения
orig_streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
for name in orig_streaming_data_settings:
val = orig_streaming_data_settings.get(name)
assert val, f"Сouldn't read the value of '{name}' from inventory scheduler settings"
orig_streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
orig_auto_backup = orig_streaming_data_scheduler_settings.get("auto_backup")
assert orig_auto_backup, "Сouldn't read the value of 'auto backup' from streaming data scheduler settings"
# устанавливаем новые значения
backup_settings_tab.click_edit_button()
backup_settings_tab.input_audit_time_period("3", "месяц")
backup_settings_tab.input_audit_time_period("4", "месяц")
backup_settings_tab.increase_audit_time_period()
backup_settings_tab.decrease_audit_time_period()
backup_settings_tab.input_logs_time_period("3", "месяц")
backup_settings_tab.input_logs_time_period("4", "месяц")
backup_settings_tab.increase_logs_time_period()
backup_settings_tab.decrease_logs_time_period()
backup_settings_tab.input_metrics_time_period("3", "месяц")
backup_settings_tab.input_metrics_time_period("4", "месяц")
backup_settings_tab.increase_metrics_time_period()
backup_settings_tab.decrease_metrics_time_period()
backup_settings_tab.input_syslog_time_period("3", "месяц")
backup_settings_tab.input_syslog_time_period("4", "месяц")
backup_settings_tab.increase_syslog_time_period()
backup_settings_tab.decrease_syslog_time_period()
backup_settings_tab.input_tasks_time_period("3", "месяц")
backup_settings_tab.input_tasks_time_period("4", "месяц")
backup_settings_tab.increase_tasks_time_period()
backup_settings_tab.decrease_tasks_time_period()
backup_settings_tab.input_streaming_data_backup_creation_time("0 0 22 * * 7")
backup_settings_tab.click_save_button()
# проверка ожидаемых значений
streaming_data_settings = backup_settings_tab.get_streaming_data_settings_values()
data_limitation_default = streaming_data_settings["data_limitation_default"]
assert data_limitation_default == "3", \
f"Actual value '{data_limitation_default}' is not equal expected '3' for category 'Аудит'"
assert data_limitation_default == "4", \
f"Actual value '{data_limitation_default}' is not equal expected '4' for category 'Аудит'"
interval_limitation_default = streaming_data_settings["interval_limitation_default"]
assert interval_limitation_default == "месяц", \
assert interval_limitation_default == "МЕСЯЦ", \
f"Actual value '{interval_limitation_default}' is not equal expected 'месяц' for category 'Аудит'"
data_limitation_logs = streaming_data_settings["data_limitation_logs"]
assert data_limitation_logs == "3", \
f"Actual value '{data_limitation_logs}' is not equal expected '3' for category 'Логи'"
assert data_limitation_logs == "4", \
f"Actual value '{data_limitation_logs}' is not equal expected '4' for category 'Логи'"
interval_limitation_logs = streaming_data_settings["interval_limitation_logs"]
assert interval_limitation_logs == "месяц", \
assert interval_limitation_logs == "МЕСЯЦ", \
f"Actual value '{interval_limitation_logs}' is not equal expected 'месяц' for category 'Логи'"
data_limitation_metrics = streaming_data_settings["data_limitation_metrics"]
assert data_limitation_metrics == "3", \
f"Actual value '{data_limitation_metrics}' is not equal expected '3' for category 'Метрики'"
assert data_limitation_metrics == "4", \
f"Actual value '{data_limitation_metrics}' is not equal expected '4' for category 'Метрики'"
interval_limitation_metrics = streaming_data_settings["interval_limitation_metrics"]
assert interval_limitation_metrics == "месяц", \
assert interval_limitation_metrics == "МЕСЯЦ", \
f"Actual value '{interval_limitation_metrics}' is not equal expected 'месяц' for category 'Метрики'"
data_limitation_syslog = streaming_data_settings["data_limitation_syslog"]
assert data_limitation_syslog == "3", \
f"Actual value '{data_limitation_syslog}' is not equal expected '3' for category 'Системный лог'"
assert data_limitation_syslog == "4", \
f"Actual value '{data_limitation_syslog}' is not equal expected '4' for category 'Системный лог'"
interval_limitation_syslog = streaming_data_settings["interval_limitation_syslog"]
assert interval_limitation_syslog == "месяц", \
assert interval_limitation_syslog == "МЕСЯЦ", \
f"Actual value '{interval_limitation_syslog}' is not equal expected 'месяц' for category 'Системный лог'"
data_limitation_tasks = streaming_data_settings["data_limitation_tasks"]
assert data_limitation_tasks == "3", \
f"Actual value '{data_limitation_tasks}' is not equal expected '3' for category 'Действия'"
assert data_limitation_tasks == "4", \
f"Actual value '{data_limitation_tasks}' is not equal expected '4' for category 'Действия'"
interval_limitation_tasks = streaming_data_settings["interval_limitation_tasks"]
assert interval_limitation_tasks == "месяц", \
assert interval_limitation_tasks == "МЕСЯЦ", \
f"Actual value '{interval_limitation_tasks}' is not equal expected 'месяц' for category 'Действия'"
streaming_data_scheduler_settings = backup_settings_tab.get_streaming_data_scheduler_settings_values()
@ -336,8 +423,73 @@ class TestBackupSettingsTab:
f"Actual value '{streaming_data_auto_backup}' \
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:

View File

@ -0,0 +1,283 @@
"""Модуль тестов вкладки 'Сертификаты'.
Содержит тесты для проверки корректности отображения
и функциональности элементов вкладки 'Сертификаты'.
"""
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()
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)
# Удаляем тестовых пользователей
test_users = ["TestUser", "TestUserAutoOperator", "TestUserAutoAdmin"]
test_users = ["TestUser", "TestUserAutoOperator", "TestUserAutoAdmin",
"TestUserTestOperator", "TestUserToBlock"]
for user_name in test_users:
# Проверяем существует ли пользователь и удаляем его
@ -167,6 +168,8 @@ class TestUsersTabEditUser:
if len(new_password) == 0:
assert False, "Unsuccessful password reset"
ut.close_edit_user_window(user_data["name"])
mp.do_logout()
new_lp = LoginPage(browser)
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("Пользователи")
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.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)