From facc0dd2a100629530e43291fc50d0f135277837 Mon Sep 17 00:00:00 2001 From: Radislav Date: Thu, 16 Oct 2025 10:35:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D0=BA=D1=80=D0=B5=D1=82=D0=BD=D1=8B=D1=85=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2:=20modal=5Fview=5Ftemplate.py,?= =?UTF-8?q?=20modal=5Fview=5Fztp=5Ftemplate.py,=20templates=5Ftab.py,=20zt?= =?UTF-8?q?p=5Ftemplates=5Ftab.py,=20test=5Fztp=5Ftemplates=5Ftab.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components_derived/modal_view_template.py | 133 ++---------- components_derived/modal_view_ztp_template.py | 192 ++++++++++++++++++ pages/templates_tab.py | 58 +++--- pages/ztp_templates_tab.py | 14 +- tests/e2e/test_ztp_templates_tab.py | 4 +- 5 files changed, 251 insertions(+), 150 deletions(-) create mode 100644 components_derived/modal_view_ztp_template.py diff --git a/components_derived/modal_view_template.py b/components_derived/modal_view_template.py index 258e85f..c79f61d 100644 --- a/components_derived/modal_view_template.py +++ b/components_derived/modal_view_template.py @@ -7,8 +7,9 @@ import re from playwright.sync_api import Page from tools.logger import get_logger -from locators.modal_window_locators import ModalWindowLocators from components.modal_window_component import ModalWindowComponent +from components.json_container_component import JsonContainerComponent +from locators.json_container_locators import JsonContainerLocators logger = get_logger("VIEW_TEMPLATE_MODAL_WINDOW") @@ -19,16 +20,16 @@ class ViewTemplateModalWindow(ModalWindowComponent): Наследует ModalWindowComponent и добавляет функционал для: 1. Инициализации модального окна с конкретным шаблоном - 2. Закрытия модального окна - 3. Получения конфигурационных данных шаблона - 4. Проверки содержимого модального окна + 2. Закрытия модального окна через тулбар + 3. Проверки содержимого модального окна + 4. Проверки содержимого JSON контейнера """ def __init__(self, page: Page, title: str): """Инициализирует элементы формы модального окна шаблона.""" super().__init__(page) - # Настройка заголовка и кнопок закрытия + # Настройка заголовка и кнопки закрытия self.window_title = title locator_button_toolbar_close = self.page.get_by_role("navigation").filter( has_text=re.compile(self.window_title) @@ -37,13 +38,8 @@ class ViewTemplateModalWindow(ModalWindowComponent): self.add_toolbar_title(self.window_title) self.add_toolbar_button(locator_button_toolbar_close, "close") - locator_button_close = self.page.get_by_role("button", name="Закрыть") - self.add_button(locator_button_close, "close") - - def close_window(self) -> None: - """Закрывает окно через кнопку 'Закрыть'.""" - close_button = self.get_button_by_name("close") - close_button.click() + # Инициализация JSON контейнера + self.json_container = JsonContainerComponent(page) def close_window_by_toolbar_button(self): """Закрывает окно через кнопку в тулбаре.""" @@ -61,107 +57,18 @@ class ViewTemplateModalWindow(ModalWindowComponent): self.check_toolbar_button_visibility("close") self.check_toolbar_button_tooltip("close", "Закрыть") - def get_modal_window_data(self) -> dict: - """Извлекает данные из модального окна шаблона и структурирует по кодам и значениям. + def verify_json_container_content(self, template_data: dict) -> None: + """Проверяет соответствие данных контейнера данным из API. - Returns: - dict: Данные в формате {'код': 'значение'} как в API + Args: + template_data: Данные шаблона из API. """ - modal_data = {} + # Читаем данные из контейнера + actual_data = self.json_container.read_data(JsonContainerLocators.CONTAINER) - # Получаем все значения из input полей - input_locator = self.get_locator(ModalWindowLocators.MODAL_WINDOW_TEXT_FIELD_INPUT) - - # Проверка наличия элементов - input_count = input_locator.count() - if input_count == 0: - logger.warning("Поля ввода не найдены в модальном окне") - return modal_data - - all_values = [] - - # Обрабатываем каждое поле с обработкой возможных ошибок - for i in range(input_count): - input_field = input_locator.nth(i) - - # Проверяем, что элемент видим и доступен - if not input_field.is_visible(): - logger.debug("Поле %s не видимо, пропускаем", i) - continue - - # Получаем значение с обработкой возможных ошибок состояния элемента - if input_field.is_visible(): - value = input_field.input_value().strip() - if value: # Игнорируем пустые значения - all_values.append(value) - else: - logger.debug("Поле %s стало невидимым после проверки, пропускаем", i) - - logger.info("Все значения из полей: %s", all_values) - - # Анализируем пары код-значение - i = 0 - while i < len(all_values) - 1: - current_value = all_values[i] - next_value = all_values[i + 1] - - # Определяем, является ли текущее значение кодом (число) - if current_value.isdigit(): - # Текущее значение - код, следующее - значение - modal_data[current_value] = next_value - i += 2 # Перескакиваем через пару - else: - # Если текущее значение не число, ищем следующую пару - i += 1 - - # Добавляем имя шаблона с ключом 'Шаблон' вместо 'template' - if all_values: - modal_data['Шаблон'] = all_values[-1] - - logger.info("Структурированные данные из модального окна: %s", modal_data) - return modal_data - - def compare_modal_with_api_data(self, modal_data: dict, api_data: dict, - title: str) -> None: - """Сравнивает данные из модального окна с данными из API.""" - errors = [] - - # Создаем копию API данных с заменой 'template' на 'Шаблон' - api_data_adapted = api_data.copy() - if 'template' in api_data_adapted: - api_data_adapted['Шаблон'] = api_data_adapted.pop('template') - - # Сравниваем все поля - for code, expected_value in api_data_adapted.items(): - if code in modal_data: - actual_value = modal_data[code] - if actual_value != expected_value: - error_msg = ( - f"Расхождение для кода {code}: " - f"модальное окно='{actual_value}', API='{expected_value}'" - ) - logger.error(error_msg) - errors.append(error_msg) - else: - error_msg = f"Код {code} не найден в модальном окне" - logger.error(error_msg) - errors.append(error_msg) - - # Дополнительная проверка имени шаблона - modal_template = modal_data.get('Шаблон', '') - if modal_template != title: - error_msg = ( - f"Расхождение в имени шаблона: " - f"модальное окно='{modal_template}', ожидается='{title}'" - ) - logger.error(error_msg) - errors.append(error_msg) - - # Если есть расхождения, выбрасываем ошибку - if errors: - error_details = "\n".join(errors) - assert False, ( - f"Обнаружены расхождения для шаблона '{title}':\n{error_details}" - ) - - logger.info("Данные модального окна соответствуют API для шаблона '%s'", title) + # Сравниваем actual_data с данными конкретного шаблона + self.json_container.check_json_equals( + actual_data, + template_data, + "Expected json content is not equal actual:" + ) diff --git a/components_derived/modal_view_ztp_template.py b/components_derived/modal_view_ztp_template.py new file mode 100644 index 0000000..1b09c39 --- /dev/null +++ b/components_derived/modal_view_ztp_template.py @@ -0,0 +1,192 @@ +"""Модуль modal_view_ztp_template содержит класс для работы с модальным окном шаблона ZTP. + +Класс ViewZTPTemplateModalWindow наследует базовый функционал ModalWindowComponent +и реализует методы просмотра модального окна шаблона Zero Touch Provisioning. +""" + +import re +from playwright.sync_api import Page +from tools.logger import get_logger +from locators.modal_window_locators import ModalWindowLocators +from components.modal_window_component import ModalWindowComponent + + +logger = get_logger("VIEW_ZTP_TEMPLATE_MODAL_WINDOW") + + +class ViewZTPTemplateModalWindow(ModalWindowComponent): + """Модальное окно шаблона Zero Touch Provisioning. + + Наследует ModalWindowComponent и добавляет функционал для: + 1. Инициализации модального окна с конкретным шаблоном ZTP + 2. Закрытия модального окна + 3. Получения конфигурационных данных шаблона ZTP + 4. Проверки содержимого модального окна + 5. Сравнения данных с API специфичными для ZTP + """ + + def __init__(self, page: Page, title: str): + """Инициализирует элементы формы модального окна шаблона ZTP.""" + super().__init__(page) + + # Настройка заголовка и кнопок закрытия + self.window_title = title + locator_button_toolbar_close = self.page.get_by_role("navigation").filter( + has_text=re.compile(self.window_title) + ).get_by_role("button") + + self.add_toolbar_title(self.window_title) + self.add_toolbar_button(locator_button_toolbar_close, "close") + + locator_button_close = self.page.get_by_role("button", name="Закрыть") + self.add_button(locator_button_close, "close") + + def close_window(self) -> None: + """Закрывает окно через кнопку 'Закрыть'.""" + close_button = self.get_button_by_name("close") + close_button.click() + + def close_window_by_toolbar_button(self): + """Закрывает окно через кнопку в тулбаре.""" + self.click_toolbar_close_button() + + def check_content(self) -> None: + """Проверяет наличие и корректность элементов окна ZTP шаблона. + + Проверяет: + 1. Наличие заголовка окна с именем шаблона + 2. Видимость кнопки закрытия + 3. Подсказку кнопки закрытия + 4. Наличие специфичных полей для ZTP + """ + self.check_by_window_title() + self.check_toolbar_button_visibility("close") + self.check_toolbar_button_tooltip("close", "Закрыть") + + def get_modal_window_data(self) -> dict: + """Извлекает данные из модального окна шаблона ZTP и структурирует по кодам и значениям. + + Returns: + dict: Данные в формате {'код': 'значение'} как в API ZTP + """ + modal_data = {} + + # Получаем все значения из input полей + input_locator = self.get_locator(ModalWindowLocators.MODAL_WINDOW_TEXT_FIELD_INPUT) + + # Проверка наличия элементов + input_count = input_locator.count() + if input_count == 0: + logger.warning("Поля ввода не найдены в модальном окне ZTP") + return modal_data + + all_values = [] + + # Обрабатываем каждое поле с обработкой возможных ошибок + for i in range(input_count): + input_field = input_locator.nth(i) + + # Проверяем, что элемент видим и доступен + if not input_field.is_visible(): + logger.debug("Поле %s не видимо, пропускаем", i) + continue + + # Получаем значение с обработкой возможных ошибок состояния элемента + if input_field.is_visible(): + value = input_field.input_value().strip() + if value: # Игнорируем пустые значения + all_values.append(value) + else: + logger.debug("Поле %s стало невидимым после проверки, пропускаем", i) + + logger.info("Все значения из полей ZTP шаблона: %s", all_values) + + # Анализируем пары код-значение для ZTP формата + i = 0 + while i < len(all_values) - 1: + current_value = all_values[i] + next_value = all_values[i + 1] + + # Для ZTP шаблонов могут быть как числовые коды, так и строковые идентификаторы + if current_value.isdigit() or self._is_ztp_field_code(current_value): + # Текущее значение - код, следующее - значение + modal_data[current_value] = next_value + i += 2 # Перескакиваем через пару + else: + # Если текущее значение не подходит как код, ищем следующую пару + i += 1 + + # Добавляем имя шаблона с ключом 'template' + if all_values: + modal_data['template'] = all_values[-1] + + logger.info("Структурированные данные из модального окна ZTP: %s", modal_data) + return modal_data + + def _is_ztp_field_code(self, value: str) -> bool: + """Проверяет, является ли значение кодом поля ZTP. + + Args: + value: Проверяемое значение + + Returns: + bool: True если значение похоже на код поля ZTP + """ + ztp_field_patterns = [ + 'vendorCode', + 'authentication', + 'deviceType', + 'authenticationOption', + 'manufacturer' + ] + + return any(pattern.lower() in value.lower() for pattern in ztp_field_patterns) + + def compare_modal_with_api_data(self, modal_data: dict, api_data: dict, + title: str) -> None: + """Сравнивает данные из модального окна ZTP с данными из API. + + Args: + modal_data: Данные из модального окна + api_data: Данные из API ответа + title: Имя шаблона для проверки + """ + errors = [] + + # Для ZTP API данные уже содержат нужные ключи + api_data_adapted = api_data.copy() + + # Сравниваем все поля + for code, expected_value in api_data_adapted.items(): + if code in modal_data: + actual_value = modal_data[code] + if str(actual_value) != str(expected_value): + error_msg = ( + f"Расхождение для поля {code}: " + f"модальное окно='{actual_value}', API='{expected_value}'" + ) + logger.error(error_msg) + errors.append(error_msg) + else: + error_msg = f"Поле {code} не найдено в модальном окне ZTP" + logger.error(error_msg) + errors.append(error_msg) + + # Дополнительная проверка имени шаблона + modal_template = modal_data.get('template', '') + if modal_template != title: + error_msg = ( + f"Расхождение в имени шаблона ZTP: " + f"модальное окно='{modal_template}', ожидается='{title}'" + ) + logger.error(error_msg) + errors.append(error_msg) + + # Если есть расхождения, выбрасываем ошибку + if errors: + error_details = "\n".join(errors) + assert False, ( + f"Обнаружены расхождения для ZTP шаблона '{title}':\n{error_details}" + ) + + logger.info("Данные модального окна ZTP соответствуют API для шаблона '%s'", title) diff --git a/pages/templates_tab.py b/pages/templates_tab.py index 97c56d7..01c00ec 100644 --- a/pages/templates_tab.py +++ b/pages/templates_tab.py @@ -8,12 +8,10 @@ from playwright.sync_api import Page from tools.logger import get_logger from locators.table_locators import TableLocators from locators.modal_window_locators import ModalWindowLocators -from locators.json_container_locators import JsonContainerLocators from components_derived.modal_view_template import ViewTemplateModalWindow from components.modal_window_component import ModalWindowComponent from components.toolbar_component import ToolbarComponent from components.table_component import TableComponent -from components.json_container_component import JsonContainerComponent from pages.base_page import BasePage logger = get_logger("TEMPLATES_TAB") @@ -39,8 +37,6 @@ class TemplatesTab(BasePage): self.templates_table = TableComponent(page) self.modal_windows = {} - self.json_container = JsonContainerComponent(page) - def add_modal_window(self, title: str) -> None: """Добавляет модальное окно в коллекцию. @@ -187,6 +183,36 @@ class TemplatesTab(BasePage): logger.error(error_msg) assert False, error_msg + def get_template_data_from_api(self, title: str) -> dict: + """Получает данные шаблона из API. + + Args: + title: Имя шаблона. + + Returns: + dict: Данные шаблона из API. + """ + # Отправляем запрос к backend для получения информации о шаблоне + response = self.send_get_api_request("e-cmdb/api/device/template") + response_body = self.get_response_body(response) + + # Извлекаем конкретный шаблон по имени из ответа API + template_data = self.extract_specific_template(title, response_body) + return template_data + + def verify_json_container_content(self, title: str) -> None: + """Проверяет соответствие данных контейнера данным из API. + + Args: + title: Имя шаблона для проверки. + """ + # Получаем данные шаблона из API + template_data = self.get_template_data_from_api(title) + + # Получаем модальное окно и проверяем содержимое JSON контейнера + modal_window = self.get_modal_window(title) + modal_window.verify_json_container_content(template_data) + def check_templates_modal_content(self, title: str) -> None: """Проверяет наличие и корректность элементов модального окна шаблона. @@ -313,27 +339,3 @@ class TemplatesTab(BasePage): """ temp_modal = ModalWindowComponent(self.page) return temp_modal.check_window_vertical_scrolling() - - def verify_json_container_content(self, title: str) -> None: - """Проверяет соответствие данных контейнера данным из API. - - Args: - title: Имя шаблона для проверки. - """ - - # Читаем данные из контейнера - actual_data = self.json_container.read_data(JsonContainerLocators.CONTAINER) - - # Отправляем запрос к backend для получения информации о шаблоне - response = self.send_get_api_request("e-cmdb/api/device/template") - response_body = self.get_response_body(response) - - # Извлекаем конкретный шаблон по имени из ответа API - template_data = self.extract_specific_template(title, response_body) - - # Сравниваем actual_data с данными конкретного шаблона - self.json_container.check_json_equals( - actual_data, - template_data, - "Expected json content is not equal actual:" - ) diff --git a/pages/ztp_templates_tab.py b/pages/ztp_templates_tab.py index 7b66a2e..c18cb06 100644 --- a/pages/ztp_templates_tab.py +++ b/pages/ztp_templates_tab.py @@ -8,7 +8,7 @@ from playwright.sync_api import Page from tools.logger import get_logger from locators.table_locators import TableLocators from locators.modal_window_locators import ModalWindowLocators -from components_derived.modal_view_template import ViewTemplateModalWindow +from components_derived.modal_view_ztp_template import ViewZTPTemplateModalWindow from components.modal_window_component import ModalWindowComponent from components.toolbar_component import ToolbarComponent from components.table_component import TableComponent @@ -44,16 +44,16 @@ class ZTPTemplatesTab(BasePage): Args: title: Заголовок окна. """ - self.modal_windows[title] = ViewTemplateModalWindow(self.page, title) + self.modal_windows[title] = ViewZTPTemplateModalWindow(self.page, title) - def get_modal_window(self, title: str) -> ViewTemplateModalWindow: + def get_modal_window(self, title: str) -> ViewZTPTemplateModalWindow: """Возвращает модальное окно по заголовку. Args: title: Заголовок окна. Returns: - ViewTemplateModalWindow: Экземпляр модального окна шаблона. + ViewZTPTemplateModalWindow: Экземпляр модального окна шаблона. Raises: AssertionError: Если окно не найдено. @@ -92,7 +92,7 @@ class ZTPTemplatesTab(BasePage): row_locator.click() # Создаем временный экземпляр модального окна для получения заголовка - temp_modal = ViewTemplateModalWindow(self.page, "") + temp_modal = ViewZTPTemplateModalWindow(self.page, "") title = temp_modal.toolbar.get_toolbar_title_text( ModalWindowLocators.MODAL_WINDOW_TITLE ) @@ -150,7 +150,7 @@ class ZTPTemplatesTab(BasePage): temp_modal = ModalWindowComponent(self.page) temp_modal.scroll_window_down() - def check_templates_modal_content(self, title: str) -> None: + def check_ztp_templates_modal_content(self, title: str) -> None: """Проверяет наличие и корректность элементов модального окна шаблона. Args: @@ -162,7 +162,7 @@ class ZTPTemplatesTab(BasePage): modal_window = self.get_modal_window(title) modal_window.check_content() - def check_templates_table_content(self) -> None: + def check_ztp_templates_table_content(self) -> None: """Проверяет содержимое таблицы шаблонов. Проверяет заголовки и наличие данных в таблице. diff --git a/tests/e2e/test_ztp_templates_tab.py b/tests/e2e/test_ztp_templates_tab.py index a3431fa..44148eb 100644 --- a/tests/e2e/test_ztp_templates_tab.py +++ b/tests/e2e/test_ztp_templates_tab.py @@ -75,7 +75,7 @@ class TestZTPTemplatesTab: browser.wait_for_timeout(5000) # Проверка содержимого таблицы шаблонов - ztp_templates_tab.check_templates_table_content() + ztp_templates_tab.check_ztp_templates_table_content() #@pytest.mark.skip(reason=" Временно исключено из тестирования") def test_templates_table_row_highlighting(self, browser: Page) -> None: @@ -226,7 +226,7 @@ class TestZTPTemplatesTab: ztp_templates_tab.should_be_modal_window() # Проверка содержимого модального окна - ztp_templates_tab.check_templates_modal_content(title) + ztp_templates_tab.check_ztp_templates_modal_content(title) # Закрытие модального окна через кнопку 'Закрыть' ztp_templates_tab.close_modal_window(title)