From 3417feb4be333b6ecdccbbc400ff1938698b077a Mon Sep 17 00:00:00 2001 From: Radislav Date: Tue, 14 Oct 2025 12:49:20 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20components=5Fderived/mod?= =?UTF-8?q?al=5Fview=5Fztp=5Ftemplate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components_derived/modal_view_template.py | 121 +---------- components_derived/modal_view_ztp_template.py | 192 ++++++++++++++++++ pages/ztp_templates_tab.py | 14 +- tests/e2e/test_ztp_templates_tab.py | 4 +- 4 files changed, 204 insertions(+), 127 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..57ec655 100644 --- a/components_derived/modal_view_template.py +++ b/components_derived/modal_view_template.py @@ -7,7 +7,6 @@ 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 @@ -19,16 +18,15 @@ class ViewTemplateModalWindow(ModalWindowComponent): Наследует ModalWindowComponent и добавляет функционал для: 1. Инициализации модального окна с конкретным шаблоном - 2. Закрытия модального окна - 3. Получения конфигурационных данных шаблона - 4. Проверки содержимого модального окна + 2. Закрытия модального окна через тулбар + 3. Проверки содержимого модального окна """ 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,14 +35,6 @@ 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() - def close_window_by_toolbar_button(self): """Закрывает окно через кнопку в тулбаре.""" self.click_toolbar_close_button() @@ -60,108 +50,3 @@ class ViewTemplateModalWindow(ModalWindowComponent): self.check_by_window_title() self.check_toolbar_button_visibility("close") self.check_toolbar_button_tooltip("close", "Закрыть") - - def get_modal_window_data(self) -> dict: - """Извлекает данные из модального окна шаблона и структурирует по кодам и значениям. - - Returns: - dict: Данные в формате {'код': 'значение'} как в API - """ - 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("Поля ввода не найдены в модальном окне") - 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) 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/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)