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/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/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/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/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/tests/e2e/test_certificates_tab.py b/tests/e2e/test_certificates_tab.py new file mode 100644 index 0000000..df8aad1 --- /dev/null +++ b/tests/e2e/test_certificates_tab.py @@ -0,0 +1,273 @@ +"""Модуль тестов вкладки 'Сертификаты'. + +Содержит тесты для проверки корректности отображения +и функциональности элементов вкладки 'Сертификаты'. +""" + +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) + certificates_tab.check_alert('error', + 'Поле не может содержать более 64 \n символов') + + # Временно пока работает неправильно + # certificates_tab.input_location_country_field("R") + # certificates_tab.check_alert('error', + # 'Поле должно содержать 2 \n символа') + # certificates_tab.input_location_country_field("RUS") + # certificates_tab.check_alert('error', + # 'Поле должно содержать 2 \n символа') + + assert certificates_tab.is_reissue_button_disabled(), "Reissue certificate button should be disabled" + + @pytest.mark.skip(reason="Временно пока работает неправильно") + 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" + + # Вспомогательные функции + 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