diff --git a/components/date_picker_component.py b/components/date_picker_component.py index ad4c8a7..53a76fc 100644 --- a/components/date_picker_component.py +++ b/components/date_picker_component.py @@ -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() diff --git a/components/events_container_component.py b/components/events_container_component.py index a8d3ebe..57b1264 100644 --- a/components/events_container_component.py +++ b/components/events_container_component.py @@ -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: diff --git a/components/navbar_component.py b/components/navbar_component.py index 51fea43..7ac7632 100644 --- a/components/navbar_component.py +++ b/components/navbar_component.py @@ -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: """ diff --git a/components/toolbar_custom_component.py b/components/toolbar_custom_component.py new file mode 100644 index 0000000..6cfedf9 --- /dev/null +++ b/components/toolbar_custom_component.py @@ -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" diff --git a/components_derived/container_actions_events.py b/components_derived/container_actions_events.py index 22ded36..8414c55 100644 --- a/components_derived/container_actions_events.py +++ b/components_derived/container_actions_events.py @@ -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() diff --git a/components_derived/container_audit_events.py b/components_derived/container_audit_events.py index 210032f..b4cab87 100644 --- a/components_derived/container_audit_events.py +++ b/components_derived/container_audit_events.py @@ -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") diff --git a/components_derived/container_events.py b/components_derived/container_events.py index 239f428..157a845 100644 --- a/components_derived/container_events.py +++ b/components_derived/container_events.py @@ -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") diff --git a/components_derived/container_maintenance_events.py b/components_derived/container_maintenance_events.py index cbcd81f..75721e4 100644 --- a/components_derived/container_maintenance_events.py +++ b/components_derived/container_maintenance_events.py @@ -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() diff --git a/components_derived/container_system_log_events.py b/components_derived/container_system_log_events.py index ef34b9a..f9d4455 100644 --- a/components_derived/container_system_log_events.py +++ b/components_derived/container_system_log_events.py @@ -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() diff --git a/components_derived/date_input_component.py b/components_derived/date_input_component.py index a8a16a7..db7da12 100644 --- a/components_derived/date_input_component.py +++ b/components_derived/date_input_component.py @@ -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 diff --git a/components_derived/import_certificate_form.py b/components_derived/import_certificate_form.py new file mode 100644 index 0000000..ee48dc8 --- /dev/null +++ b/components_derived/import_certificate_form.py @@ -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() diff --git a/components_derived/modal_edit_user.py b/components_derived/modal_edit_user.py index de80679..0b32745 100644 --- a/components_derived/modal_edit_user.py +++ b/components_derived/modal_edit_user.py @@ -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() diff --git a/components_derived/modal_view_task.py b/components_derived/modal_view_task.py index 7407452..48371fc 100644 --- a/components_derived/modal_view_task.py +++ b/components_derived/modal_view_task.py @@ -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) diff --git a/components_derived/reissue_certificate_form.py b/components_derived/reissue_certificate_form.py new file mode 100644 index 0000000..c9a4a10 --- /dev/null +++ b/components_derived/reissue_certificate_form.py @@ -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() diff --git a/components_derived/selection_bar_component.py b/components_derived/selection_bar_component.py index 686a85e..eb50036 100644 --- a/components_derived/selection_bar_component.py +++ b/components_derived/selection_bar_component.py @@ -76,7 +76,7 @@ class SelectionBarComponent(BaseComponent): options = self.selected_values_list.get_item_names( SelectionBarLocators.LIST_ITEMS ) - + # Закрываем список (кликаем вне его) self.page.mouse.click(10, 10) self.wait_for_timeout(500) @@ -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: """Выбор значения из списка""" diff --git a/components_derived/view_certificate_form.py b/components_derived/view_certificate_form.py new file mode 100644 index 0000000..63f5d49 --- /dev/null +++ b/components_derived/view_certificate_form.py @@ -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") diff --git a/elements/tab_button_element.py b/elements/tab_button_element.py index 20de798..90fe9b3 100644 --- a/elements/tab_button_element.py +++ b/elements/tab_button_element.py @@ -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 diff --git a/locators/certificate_locators.py b/locators/certificate_locators.py new file mode 100644 index 0000000..b7cdf8f --- /dev/null +++ b/locators/certificate_locators.py @@ -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']" + diff --git a/locators/json_container_locators.py b/locators/json_container_locators.py index 0f073bb..6237969 100644 --- a/locators/json_container_locators.py +++ b/locators/json_container_locators.py @@ -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')]" diff --git a/locators/selection_bar_locators.py b/locators/selection_bar_locators.py index 001e994..4c488c3 100644 --- a/locators/selection_bar_locators.py +++ b/locators/selection_bar_locators.py @@ -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']" diff --git a/pages/backup_settings_tab.py b/pages/backup_settings_tab.py index c5863c5..c2d4f81 100644 --- a/pages/backup_settings_tab.py +++ b/pages/backup_settings_tab.py @@ -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): """Класс для работы с вкладкой настройки резервного копирования. @@ -81,6 +80,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: """Возвращает текущее значение полей настроек 'Инвентаризация/Параметры планировщика'. diff --git a/pages/certificates_tab.py b/pages/certificates_tab.py new file mode 100644 index 0000000..db16fa9 --- /dev/null +++ b/pages/certificates_tab.py @@ -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() diff --git a/pages/ldap_settings_tab.py b/pages/ldap_settings_tab.py index 767f59d..2bbb091 100644 --- a/pages/ldap_settings_tab.py +++ b/pages/ldap_settings_tab.py @@ -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: """Проверяет наличие тулбара страницы, наличие и функциональность кнопок тулбара. diff --git a/pages/license_tab.py b/pages/license_tab.py index 30daca1..1648d8b 100644 --- a/pages/license_tab.py +++ b/pages/license_tab.py @@ -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: diff --git a/pages/main_page.py b/pages/main_page.py index 7643504..a9585c0 100644 --- a/pages/main_page.py +++ b/pages/main_page.py @@ -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: diff --git a/pages/session_settings_tab.py b/pages/session_settings_tab.py index 3e8f61e..952e496 100644 --- a/pages/session_settings_tab.py +++ b/pages/session_settings_tab.py @@ -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: diff --git a/pages/users_tab.py b/pages/users_tab.py index 560d0f8..976ff92 100644 --- a/pages/users_tab.py +++ b/pages/users_tab.py @@ -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: """Проверяет наличие тулбара. diff --git a/tests/components/scrolling/run.bat b/tests/components/scrolling/run.bat index 2d86da3..77e7347 100644 --- a/tests/components/scrolling/run.bat +++ b/tests/components/scrolling/run.bat @@ -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 \ No newline at end of file diff --git a/tests/components/scrolling/test_navigation_panel.py b/tests/components/scrolling/test_navigation_panel.py index 560e356..1ce4b4a 100644 --- a/tests/components/scrolling/test_navigation_panel.py +++ b/tests/components/scrolling/test_navigation_panel.py @@ -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) diff --git a/tests/components/scrolling/test_session_settings.py b/tests/components/scrolling/test_session_settings.py index da34eee..9ed7b22 100644 --- a/tests/components/scrolling/test_session_settings.py +++ b/tests/components/scrolling/test_session_settings.py @@ -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) diff --git a/tests/components/scrolling/test_user_modal_window.py b/tests/components/scrolling/test_user_modal_window.py index 96c2403..3292bdc 100644 --- a/tests/components/scrolling/test_user_modal_window.py +++ b/tests/components/scrolling/test_user_modal_window.py @@ -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" diff --git a/tests/components/unit_tests/test_date_input.py b/tests/components/unit_tests/test_date_input.py index 495d517..caa51fa 100644 --- a/tests/components/unit_tests/test_date_input.py +++ b/tests/components/unit_tests/test_date_input.py @@ -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" diff --git a/tests/components/unit_tests/test_date_picker.py b/tests/components/unit_tests/test_date_picker.py index 552b534..f8c5218 100644 --- a/tests/components/unit_tests/test_date_picker.py +++ b/tests/components/unit_tests/test_date_picker.py @@ -179,7 +179,10 @@ class TestDatePickerComponent: browser.wait_for_timeout(300) month_num = months.index(current_month) + 1 - expected_date = f"15.{month_num}.{current_year}" + 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, \ f"Expected date {expected_date} is not equal actual date {actual_date}" diff --git a/tests/e2e/event_panel/test_actions_events_container.py b/tests/e2e/event_panel/test_actions_events_container.py index fad6bee..74a0f4e 100644 --- a/tests/e2e/event_panel/test_actions_events_container.py +++ b/tests/e2e/event_panel/test_actions_events_container.py @@ -53,25 +53,27 @@ class TestActionsEventsContainer: # Получение количества строк в таблице Реальное время rows_count = actions_events_container.get_events_table_rows_count() - # Проверка выделения строк - actions_events_container.check_events_table_row_highlighting(0) - if rows_count > 1: - actions_events_container.check_events_table_row_highlighting(rows_count - 1) - if rows_count > 3: - actions_events_container.check_events_table_row_highlighting(int(rows_count / 2)) + if rows_count != 0: + # Проверка выделения строк + actions_events_container.check_events_table_row_highlighting(0) + if rows_count > 1: + actions_events_container.check_events_table_row_highlighting(rows_count - 1) + if rows_count > 3: + actions_events_container.check_events_table_row_highlighting(int(rows_count / 2)) actions_events_container.click_archive_button() # Получение количества строк в таблице Архив rows_count = actions_events_container.get_events_table_rows_count() - # Проверка выделения строк - actions_events_container.check_events_table_row_highlighting(0) - if rows_count > 1: - actions_events_container.check_events_table_row_highlighting(rows_count - 1) - if rows_count > 3: - actions_events_container.check_events_table_row_highlighting(int(rows_count / 2)) + if rows_count != 0: + # Проверка выделения строк + actions_events_container.check_events_table_row_highlighting(0) + if rows_count > 1: + actions_events_container.check_events_table_row_highlighting(rows_count - 1) + if rows_count > 3: + actions_events_container.check_events_table_row_highlighting(int(rows_count / 2)) - @pytest.mark.develop + # @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() diff --git a/tests/e2e/event_panel/test_audit_events_container.py b/tests/e2e/event_panel/test_audit_events_container.py index 5b3c9f3..7db2b46 100644 --- a/tests/e2e/event_panel/test_audit_events_container.py +++ b/tests/e2e/event_panel/test_audit_events_container.py @@ -52,10 +52,13 @@ 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) - audit_events_container.check_events_table_row_highlighting(rows_count - 1) - audit_events_container.check_events_table_row_highlighting(int(rows_count / 2)) + 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() diff --git a/tests/e2e/event_panel/test_audit_events_container_security.py b/tests/e2e/event_panel/test_audit_events_container_security.py index e506acb..e1601e8 100644 --- a/tests/e2e/event_panel/test_audit_events_container_security.py +++ b/tests/e2e/event_panel/test_audit_events_container_security.py @@ -103,10 +103,13 @@ class TestAuditEventsContainerSecurity: # Получение количества строк в таблице rows_count = security_events_container.get_events_table_rows_count() - # Проверка выделения строк - security_events_container.check_events_table_row_highlighting(0) - security_events_container.check_events_table_row_highlighting(rows_count - 1) - security_events_container.check_events_table_row_highlighting(int(rows_count / 2)) + if rows_count != 0: + # Проверка выделения строк + security_events_container.check_events_table_row_highlighting(0) + if rows_count > 1: + security_events_container.check_events_table_row_highlighting(rows_count - 1) + if rows_count > 3: + security_events_container.check_events_table_row_highlighting(int(rows_count / 2)) # Выход из системы текущего пользователя mp.do_logout() @@ -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() diff --git a/tests/e2e/event_panel/test_event_panel.py b/tests/e2e/event_panel/test_event_panel.py index d0f64ef..56dbea3 100644 --- a/tests/e2e/event_panel/test_event_panel.py +++ b/tests/e2e/event_panel/test_event_panel.py @@ -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: """Проверяет состояние и количество кнопок расширения рабочей области панели событий. diff --git a/tests/e2e/event_panel/test_events_tab_container.py b/tests/e2e/event_panel/test_events_tab_container.py index 6d53750..0c0a7f8 100644 --- a/tests/e2e/event_panel/test_events_tab_container.py +++ b/tests/e2e/event_panel/test_events_tab_container.py @@ -53,10 +53,13 @@ class TestEventsTabContainer: # Получение количества строк в таблице rows_count = events_tab_container.get_events_table_rows_count() - # Проверка выделения строк - events_tab_container.check_events_table_row_highlighting(0) - events_tab_container.check_events_table_row_highlighting(rows_count - 1) - events_tab_container.check_events_table_row_highlighting(int(rows_count / 2)) + if rows_count != 0: + # Проверка выделения строк + events_tab_container.check_events_table_row_highlighting(0) + if rows_count > 1: + events_tab_container.check_events_table_row_highlighting(rows_count - 1) + if rows_count > 3: + events_tab_container.check_events_table_row_highlighting(int(rows_count / 2)) @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий") def test_events_table_scrolling(self, browser: Page): @@ -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() diff --git a/tests/e2e/event_panel/test_maintenance_events_container.py b/tests/e2e/event_panel/test_maintenance_events_container.py index 8a0ea69..7489a54 100644 --- a/tests/e2e/event_panel/test_maintenance_events_container.py +++ b/tests/e2e/event_panel/test_maintenance_events_container.py @@ -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,11 +51,13 @@ class TestMaintenanceEventsContainer: # Получение количества строк в таблице rows_count = maintenance_events_container.get_events_table_rows_count() - - # Проверка выделения строк - maintenance_events_container.check_events_table_row_highlighting(0) - maintenance_events_container.check_events_table_row_highlighting(rows_count - 1) - maintenance_events_container.check_events_table_row_highlighting(int(rows_count / 2)) + if rows_count != 0: + # Проверка выделения строк + maintenance_events_container.check_events_table_row_highlighting(0) + if rows_count > 1: + maintenance_events_container.check_events_table_row_highlighting(rows_count - 1) + if rows_count > 3: + maintenance_events_container.check_events_table_row_highlighting(int(rows_count / 2)) @pytest.mark.skip(reason="Отсутствуют данные для вывода в таблицу событий") def test_events_table_scrolling(self, browser: Page): @@ -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() diff --git a/tests/e2e/event_panel/test_system_log_events_container.py b/tests/e2e/event_panel/test_system_log_events_container.py index 223fa2e..c1e9382 100644 --- a/tests/e2e/event_panel/test_system_log_events_container.py +++ b/tests/e2e/event_panel/test_system_log_events_container.py @@ -55,10 +55,13 @@ class TestSystemLogEventsContainer: # Получение количества строк в таблице rows_count = system_log_events_container.get_events_table_rows_count() - # Проверка выделения строк - system_log_events_container.check_events_table_row_highlighting(0) - system_log_events_container.check_events_table_row_highlighting(rows_count - 1) - system_log_events_container.check_events_table_row_highlighting(int(rows_count / 2)) + 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() diff --git a/tests/e2e/sessions/test_current_sessions_tab.py b/tests/e2e/sessions/test_current_sessions_tab.py index 5ebcd05..08977a9 100644 --- a/tests/e2e/sessions/test_current_sessions_tab.py +++ b/tests/e2e/sessions/test_current_sessions_tab.py @@ -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() diff --git a/tests/e2e/sessions/test_session_settings_tab.py b/tests/e2e/sessions/test_session_settings_tab.py index d9569d0..81dacd7 100644 --- a/tests/e2e/sessions/test_session_settings_tab.py +++ b/tests/e2e/sessions/test_session_settings_tab.py @@ -74,7 +74,7 @@ class TestSessionSettingsTab: else: print(f"Error request session setings data from API: {response.status_text}") - #@pytest.mark.develop + # @pytest.mark.develop def test_edit_session_settings(self, browser: Page) -> None: """Тест проверки возможности редактирования выбранных полей формы настройки времени жизни сеансов. @@ -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: diff --git a/tests/e2e/test_backup_settings_tab.py b/tests/e2e/test_backup_settings_tab.py index 5ed584b..9cc0b8f 100644 --- a/tests/e2e/test_backup_settings_tab.py +++ b/tests/e2e/test_backup_settings_tab.py @@ -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: diff --git a/tests/e2e/test_certificates_tab.py b/tests/e2e/test_certificates_tab.py new file mode 100644 index 0000000..72fcab2 --- /dev/null +++ b/tests/e2e/test_certificates_tab.py @@ -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 diff --git a/tests/e2e/users/test_edit_user.py b/tests/e2e/users/test_edit_user.py index 480ce38..96574da 100644 --- a/tests/e2e/users/test_edit_user.py +++ b/tests/e2e/users/test_edit_user.py @@ -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"]) diff --git a/tests/e2e/users/test_users_tab.py b/tests/e2e/users/test_users_tab.py index 90ce2a7..77a2e5a 100644 --- a/tests/e2e/users/test_users_tab.py +++ b/tests/e2e/users/test_users_tab.py @@ -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)