From c67460b486c0ef6b3fc7ee482c5e52e0dcb3937a Mon Sep 17 00:00:00 2001 From: Radislav Date: Thu, 18 Sep 2025 10:10:17 +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=D0=B0=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D1=88=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components_derived/modal_view_template.py | 74 ++++ pages/templates_tab.py | 392 ++++++++++++++++++++++ tests/e2e/test_templates_tab.py | 259 ++++++++++++++ 3 files changed, 725 insertions(+) create mode 100644 components_derived/modal_view_template.py create mode 100644 pages/templates_tab.py create mode 100644 tests/e2e/test_templates_tab.py diff --git a/components_derived/modal_view_template.py b/components_derived/modal_view_template.py new file mode 100644 index 0000000..a53e5f0 --- /dev/null +++ b/components_derived/modal_view_template.py @@ -0,0 +1,74 @@ +"""Модуль modal_view_template содержит класс для работы с модальным окном шаблона. + +Класс ViewTemplateModalWindow наследует базовый функционал ModalWindowComponent +и реализует методы просмотра модального окна шаблона. +""" + +import re +from playwright.sync_api import Page +from tools.logger import get_logger +from components.modal_window_component import ModalWindowComponent + + +logger = get_logger("VIEW_TEMPLATE_MODAL_WINDOW") + + +class ViewTemplateModalWindow(ModalWindowComponent): + """Модальное окно шаблона. + + Наследует ModalWindowComponent и добавляет функционал для: + 1. Инициализации модального окна с конкретным шаблоном + 2. Закрытия модального окна + 3. Получения конфигурационных данных шаблона + 4. Проверки содержимого модального окна + """ + + def __init__(self, page: Page, template_name: str): + """Инициализирует элементы формы модального окна шаблона.""" + + super().__init__(page) + + # Настройка заголовка и кнопки закрытия + self.window_title = template_name + 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") + + def close_window(self) -> None: + """Закрывает окно через кнопку 'Закрыть'.""" + + close_button = self.get_button_by_name("close") + close_button.click() + + def check_content(self) -> None: + """Проверяет наличие и корректность элементов окна. + + Проверяет: + 1. Наличие заголовка окна с именем шаблона + 2. Видимость кнопки закрытия + 3. Подсказку кнопки закрытия + """ + + self.check_by_window_title() + self.check_toolbar_button_visibility("close") + self.check_toolbar_button_tooltip("close", "Закрыть") + + # Разрабатывается ========================================================= + def _get_config_data(self) -> dict: + """Получает конфигурационные данные из модального окна. + + Returns: + dict: Словарь с конфигурационными данными или пустой словарь + """ + config_data = {} + + # Ищем контейнер с конфигурационными данными + config_container = self.page.locator( + "//*[@id='app']/div[2]/div/div/div/div/div/div/div/div/div[2]/div/div/div" + ) + + return config_data + diff --git a/pages/templates_tab.py b/pages/templates_tab.py new file mode 100644 index 0000000..d2d558c --- /dev/null +++ b/pages/templates_tab.py @@ -0,0 +1,392 @@ +"""Модуль вкладки 'Шаблоны'. + +Содержит класс TemplatesTab для работы с таблицей шаблонов. +Позволяет проверять состояние и взаимодействовать с элементами вкладки. +""" + +from playwright.sync_api import Page +from locators.table_locators import TableLocators +from locators.modal_window_locators import ModalWindowLocators +from components_derived.modal_view_template import ViewTemplateModalWindow +from components.toolbar_component import ToolbarComponent +from components.table_component import TableComponent +from pages.base_page import BasePage + + +class TemplatesTab(BasePage): + """Класс для работы с вкладкой 'Шаблоны'. + + Предоставляет методы для взаимодействия с таблицей шаблонов и проверки + её состояния. + + Args: + page: Экземпляр страницы Playwright. + """ + + def __init__(self, page: Page) -> None: + """Инициализирует компоненты вкладки 'Шаблоны'.""" + + super().__init__(page) + + self.toolbar = ToolbarComponent(page, "Шаблоны") + + self.templates_table = TableComponent(page) + self.modal_windows = {} + + def add_modal_window(self, title: str) -> None: + """Добавляет модальное окно в коллекцию. + + Args: + title: Заголовок окна. + """ + + self.modal_windows[title] = ViewTemplateModalWindow(self.page, title) + + def get_modal_window(self, title: str): + """Возвращает модальное окно по заголовку. + + Args: + title: Заголовок окна. + + Returns: + ModalWindowComponent: Экземпляр модального окна. + + Raises: + AssertionError: Если окно не найдено. + """ + + modal_window = self.modal_windows.get(title) + + if modal_window is None: + assert False, f"Modal window with title '{title}' not found" + return modal_window + + def delete_modal_window(self, title: str) -> None: + """Удаляет модальное окно из коллекции. + + Args: + title: Заголовок окна. + + Raises: + AssertionError: Если окно не найдено. + """ + + if self.modal_windows.get(title) is None: + assert False, f"Modal window with title '{title}' not found" + self.modal_windows[title] = None + + def open_template_modal(self, row_index: int = 0) -> None: + """Открывает модальное окно шаблона по клику на строку таблицы. + + Args: + row_index: Индекс строки для клика (по умолчанию 0 - первая строка). + """ + row_locator = self.templates_table.get_row_locator( + TableLocators.TABLE_WORK_AREA, + row_index + ) + row_locator.click() + + # Получаем имя шаблона из выбранной строки + table_content = self.templates_table.read(TableLocators.TABLE_WORK_AREA) + template_name = table_content[row_index + 1][0] # +1 потому что первая строка - заголовки + + # Добавляем модальное окно в коллекцию после открытия + self.add_modal_window(template_name) + + return template_name + + def close_modal_window_by_toolbar_button(self, title: str) -> None: + """Закрывает модальное окно через кнопку в тулбаре. + + Args: + title: Заголовок окна. + """ + + modal_window = self.get_modal_window(title) + modal_window.click_toolbar_close_button() + self.delete_modal_window(title) + + def get_rows_count(self) -> int: + """Возвращает количество строк в таблице (без заголовка). + + Returns: + int: Количество строк с данными. + + Raises: + AssertionError: Если таблица пуста. + """ + + table_content = self.templates_table.read(TableLocators.TABLE_WORK_AREA) + rows_count = len(table_content) + + if rows_count == 0: + assert False, "The contents of the table are missing" + + return rows_count - 1 + + def get_first_template_name(self) -> str: + """Получает имя шаблона из первой строки таблицы. + + Returns: + str: Имя шаблона из первого столбца первой строки. + + Raises: + AssertionError: Если таблица пуста или имя не найдено. + """ + table_content = self.templates_table.read(TableLocators.TABLE_WORK_AREA) + + if len(table_content) < 2: # Заголовок + хотя бы одна строка + assert False, "Table is empty or missing data rows" + + # Первая строка с данными (индекс 1, так как индекс 0 - заголовки) + first_row = table_content[1] + + if len(first_row) == 0: + assert False, "First row is empty" + + template_name = first_row[0] # Первый столбец - имя шаблона + return template_name + + def scroll_templates_table_up(self) -> None: + """Прокручивает таблицу шаблонов вверх.""" + + self.templates_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER) + + def scroll_templates_table_down(self) -> None: + """Прокручивает таблицу шаблонов вниз.""" + + self.templates_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER) + + def scroll_modal_up(self) -> None: + """Прокручивает содержимое модального окна вверх.""" + self.templates_table.scroll_up(ModalWindowLocators.MODAL_WINDOW_SCROLL_CONTAINER) + + def scroll_modal_down(self) -> None: + """Прокручивает содержимое модального окна вниз.""" + self.templates_table.scroll_down(ModalWindowLocators.MODAL_WINDOW_SCROLL_CONTAINER) + + def check_templates_modal_content(self, template_name: str) -> None: + """Проверяет наличие и корректность элементов модального окна шаблона. + + Args: + template_name: Имя шаблона для проверки заголовка окна. + + Raises: + AssertionError: Если элементы окна некорректны. + """ + modal_window = self.get_modal_window(template_name) + modal_window.check_content() + + def check_templates_table_content(self) -> None: + """Проверяет содержимое таблицы шаблонов. + + Проверяет заголовки и наличие данных в таблице. + + Raises: + AssertionError: Если таблица пуста или заголовки неверны. + """ + + expected_headers = [ + 'Имя', + 'Описание', + 'Тип устройства', + 'Производитель' + ] + + table_content = self.templates_table.read(TableLocators.TABLE_WORK_AREA) + + if len(table_content) == 0: + assert False, "The contents of the table are missing" + + actual_headers = table_content[0] + + self.check_equals( + actual_headers, + expected_headers, + f"Expected table headers {expected_headers} are not equal {actual_headers}" + ) + + if len(table_content) == 1: + assert False, "Table body is missing" + + def check_templates_table_verticall_scrolling(self) -> bool: + """Проверяет возможность вертикальной прокрутки таблицы. + + Returns: + bool: True если прокрутка возможна, иначе False. + """ + + return self.templates_table.is_scrollable_vertically( + TableLocators.TABLE_SCROLL_CONTAINER + ) + + def check_templates_table_first_row_visibility(self) -> None: + """Проверяет видимость первой строки таблицы. + + Raises: + AssertionError: Если строка не видна. + """ + + self.templates_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA) + + def check_templates_table_last_row_visibility(self) -> None: + """Проверяет видимость последней строки таблицы. + + Raises: + AssertionError: Если строка не видна. + """ + + self.templates_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA) + + def check_templates_table_row_highlighting(self, row_index: int) -> None: + """Проверяет выделение указанной строки таблицы. + + Args: + row_index: Индекс проверяемой строки. + + Raises: + AssertionError: Если строка не выделена. + """ + + self.templates_table.check_row_highlighting( + TableLocators.TABLE_WORK_AREA, + row_index + ) + + def should_be_toolbar(self) -> None: + """Проверяет наличие тулбара на вкладке. + + Raises: + AssertionError: Если тулбар отсутствует. + """ + + self.toolbar.check_toolbar_presence("Toolbar is missing") + + def should_be_templates_table(self) -> None: + """Проверяет наличие таблицы шаблонов. + + Raises: + AssertionError: Если таблица отсутствует. + """ + + self.templates_table.check_visibility( + TableLocators.TABLE_WORK_AREA, + "Templates table is missing" + ) + + def should_be_modal_window(self) -> None: + """Проверяет наличие модального окна. + + Raises: + AssertionError: Если модальное окно отсутствует. + """ + self.templates_table.check_visibility( + ModalWindowLocators.MODAL_WINDOW, + "Modal window is not visible" + ) + + def should_not_be_modal_window(self) -> None: + """Проверяет, что модальное окно отсутствует. + + Raises: + AssertionError: Если модальное окно все еще видно. + """ + is_visible = self.page.locator(ModalWindowLocators.MODAL_WINDOW).is_visible(timeout=1000) + if is_visible: + assert False, "Modal window should not be visible" + + def check_modal_vertical_scrolling(self) -> bool: + """Проверяет возможность вертикального скроллинга в модальном окне. + + Returns: + bool: True если скроллинг возможен, иначе False. + """ + return self.templates_table.is_scrollable_vertically( + ModalWindowLocators.MODAL_WINDOW_SCROLL_CONTAINER + ) + + # Разрабатывается ========================================================= + def get_template_data_from_api(self, template_name: str) -> dict: + """Получает JSON данные конкретного шаблона из API. + + Args: + template_name: Имя шаблона. + + Returns: + dict: JSON данные шаблона из API. + + Raises: + AssertionError: Если не удалось получить данные из API. + """ + + # Отправляем запрос к API для получения данных конкретного шаблона + response = self.send_get_api_request("e-cmdb/api/device/template") + response_data = self.get_response_body(response) + print(response_data) + + if response.status_code != 200: + assert False, f"API request failed with status {response.status_code}" + + # Проверяем, что ответ содержит данные нужного шаблона + if 'name' in response_data and response_data['name'] != template_name: + assert False, f"API returned data for wrong template: expected '{template_name}', got '{response_data['name']}'" + + return response_data + + def verify_modal_content_with_api(self, template_name: str) -> None: + """Проверяет соответствие данных модального окна данным из API. + + Args: + template_name: Имя шаблона для проверки. + + Raises: + AssertionError: Если данные не соответствуют API. + """ + # Получаем данные из модального окна + modal_window = self.get_modal_window(template_name) + modal_data = modal_window.get_modal_content_data() + + # Получаем данные из API + api_data = self.get_template_data_from_api(template_name) + + # Сравниваем данные + self.compare_modal_with_api_data(modal_data, api_data, template_name) + + + def compare_modal_with_api_data(self, modal_data: dict, api_data: dict, template_name: str) -> None: + """Сравнивает JSON конфигурационные данные модального окна с данными из API. + + Args: + modal_data: JSON данные из модального окна. + api_data: JSON данные из API. + template_name: Имя шаблона для сообщений об ошибках. + + Raises: + AssertionError: Если JSON конфигурационные данные не совпадают. + """ + # Проверяем, что modal_data содержит данные + if not modal_data: + assert False, f"No modal data found for template '{template_name}'" + + # Проверяем, что api_data содержит данные + if not api_data: + assert False, f"No API data found for template '{template_name}'" + + # Получаем конфигурационные данные из обоих источников + modal_config = modal_data.get('config', {}) + api_config = api_data.get('config', {}) + + # Проверяем, что оба источника содержат конфигурацию + if not modal_config: + assert False, f"No config data found in modal for template '{template_name}'" + + if not api_config: + assert False, f"No config data found in API for template '{template_name}'" + + # Сравниваем JSON конфигурации + self.check_equals( + modal_config, + api_config, + f"JSON config data mismatch for template '{template_name}'" + ) diff --git a/tests/e2e/test_templates_tab.py b/tests/e2e/test_templates_tab.py new file mode 100644 index 0000000..6140792 --- /dev/null +++ b/tests/e2e/test_templates_tab.py @@ -0,0 +1,259 @@ +"""Модуль тестов вкладки 'Шаблоны'. + +Содержит тесты для проверки функциональности +работы с шаблонами. +""" +import pytest + +from typing import Dict +from playwright.sync_api import Page +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.templates_tab import TemplatesTab + + +class TestTemplatesTab: + """Набор тестов для вкладки 'Шаблоны'. + + Проверяет корректность отображения и функциональность элементов вкладки Шаблоны. + + Тесты покрывают следующие сценарии: + 1. test_templates_tab_content - Проверка содержимого вкладки (тулбар, таблица шаблонов) + 2. test_templates_table_row_highlighting - Проверка выделения строк в таблице шаблонов + 3. test_templates_table_scrolling - Проверка вертикального скроллинга таблицы шаблонов + 4. test_templates_modal_window_content - Проверка содержимого модального окна шаблона + 5. test_templates_modal_window_scrolling - Проверка скроллинга модального окна шаблона + 6. test_templates_modal_window_api_data_consistency - [В разработке] + Проверка соответствия данных модального окна данным из API + """ + + @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("Шаблоны") + + def test_templates_tab_content(self, browser: Page) -> None: + """Тест содержимого вкладки 'Шаблоны'. + + Проверяет: + 1. Наличие и корректность тулбара + 2. Наличие таблицы шаблонов + 3. Соответствие содержимого таблицы + """ + # Инициализация страницы сеансов + templates_tab = TemplatesTab(browser) + + # Проверка тулбара вкладки + templates_tab.should_be_toolbar() + + # Проверка наличия таблицы шаблонов + templates_tab.should_be_templates_table() + + browser.wait_for_timeout(5000) + + # Проверка содержимого таблицы шаблонов + templates_tab.check_templates_table_content() + + def test_templates_table_row_highlighting(self, browser: Page) -> None: + """Проверка выделения строк в таблице шаблонов. + + Проверяет корректность выделения строк при клике на различные позиции: + 1. Первая строка + 2. Последняя строка + 3. Строка в середине таблицы + """ + + templates_tab = TemplatesTab(browser) + + # Проверка тулбара вкладки + templates_tab.should_be_toolbar() + + # Проверка наличия таблицы шаблонов + templates_tab.should_be_templates_table() + + # Получение количества строк в таблице + rows_count = templates_tab.get_rows_count() + + # Проверка выделения строк + templates_tab.check_templates_table_row_highlighting(0) + templates_tab.check_templates_table_row_highlighting(rows_count - 1) + templates_tab.check_templates_table_row_highlighting(int(rows_count / 2)) + + def test_templates_table_scrolling(self, browser: Page) -> None: + """Проверка вертикального скроллинга таблицы шаблонов. + + Проверяет: + 1. Возможность вертикальной прокрутки таблицы + 2. Видимость строк после прокрутки вниз и вверх + 3. Корректность отображения данных после скроллинга + """ + + templates_tab = TemplatesTab(browser) + + browser.wait_for_timeout(2000) + + # Проверка возможности вертикального скроллинга + is_scrollable = templates_tab.check_templates_table_verticall_scrolling() + + if is_scrollable: + print("Таблица поддерживает вертикальный скроллинг") + + # Прокрутка вниз + templates_tab.scroll_templates_table_down() + + browser.wait_for_timeout(1000) + + # Проверка видимости последней строки после прокрутки + templates_tab.check_templates_table_last_row_visibility() + + # Прокрутка вверх + templates_tab.scroll_templates_table_up() + + browser.wait_for_timeout(1000) + + # Проверка видимости первой строки после прокрутки + templates_tab.check_templates_table_first_row_visibility() + else: + print("Таблица не поддерживает вертикальный скроллинг - проверяем базовую функциональность") + + # Проверка видимости первой строки + templates_tab.check_templates_table_first_row_visibility() + + #@pytest.mark.skip(reason="Временно исключено из тестирования") + def test_templates_modal_window_content(self, browser: Page) -> None: + """Тест содержимого модального окна шаблона. + + Проверяет: + 1. Открытие модального окна при клике на строку таблицы + 2. Наличие и содержимое модального окна + 3. Наличие тулбара в модальном окне + 4. Проверка кнопки закрытия и её подсказки + 5. Закрытие модального окна + """ + # Инициализация страницы шаблонов + templates_tab = TemplatesTab(browser) + + # Проверка наличия таблицы шаблонов + templates_tab.should_be_templates_table() + + # Добавляем задержку для загрузки данных + browser.wait_for_timeout(2000) + + # Открываем модальное окно, кликая на первую строку таблицы + template_name = templates_tab.open_template_modal(0) + + # Добавляем задержку для открытия модального окна + browser.wait_for_timeout(1000) + + # Проверка открытия модального окна + templates_tab.should_be_modal_window() + + # Проверка содержимого модального окна + templates_tab.check_templates_modal_content(template_name) + + # Закрытие модального окна через кнопку закрытия + templates_tab.close_modal_window_by_toolbar_button(template_name) + + # Проверяем, что модальное окно закрылось + templates_tab.should_not_be_modal_window() + + #@pytest.mark.skip(reason="Временно исключено из тестирования") + def test_templates_modal_window_scrolling(self, browser: Page) -> None: + """Тест скроллинга модального окна шаблона. + + Проверяет: + 1. Открытие модального окна при клике на строку таблицы + 2. Возможность вертикального скроллинга содержимого модального окна + 3. Закрытие модального окна + """ + + # Инициализация страницы шаблонов + templates_tab = TemplatesTab(browser) + + # Проверка наличия таблицы шаблонов + templates_tab.should_be_templates_table() + + # Добавляем задержку для загрузки данных + browser.wait_for_timeout(2000) + + # Открываем модальное окно, кликая на первую строку таблицы + template_name = templates_tab.open_template_modal(0) + + # Добавляем задержку для открытия модального окна + browser.wait_for_timeout(1000) + + # Проверка открытия модального окна + templates_tab.should_be_modal_window() + + # Проверка вертикального скроллинга модального окна + is_scrollable = templates_tab.check_modal_vertical_scrolling() + + if is_scrollable: + print("Модальное окно поддерживает вертикальный скроллинг") + + # Прокрутка вниз + templates_tab.scroll_modal_down() + browser.wait_for_timeout(1000) + + # Прокрутка вверх + templates_tab.scroll_modal_up() + browser.wait_for_timeout(1000) + else: + print("Модальное окно не поддерживает вертикальный скроллинг") + + # Закрытие модального окна через кнопку закрытия + templates_tab.close_modal_window_by_toolbar_button(template_name) + + # Проверяем, что модальное окно закрылось + templates_tab.should_not_be_modal_window() + + @pytest.mark.skip(reason="Разрабатывается. Временно исключено из тестирования") + def test_templates_modal_window_api_data_consistency(self, browser: Page) -> None: + """Тест соответствия данных модального окна данным из API. + + Проверяет: + 1. Открытие модального окна при клике на строку таблицы + 2. Соответствие JSON конфигурационных данных в модальном окне данным из API + 3. Закрытие модального окна + """ + # Инициализация страницы шаблонов + templates_tab = TemplatesTab(browser) + + # Проверка наличия таблицы шаблонов + templates_tab.should_be_templates_table() + + # Добавляем задержку для загрузки данных + browser.wait_for_timeout(2000) + + # Открываем модальное окно, кликая на первую строку таблицы + template_name = templates_tab.open_template_modal(0) + + # Добавляем задержку для открытия модального окна + browser.wait_for_timeout(2000) + + # Проверка открытия модального окна + templates_tab.should_be_modal_window() + + # Проверка соответствия данных модального окна данным из API + templates_tab.verify_modal_content_with_api(template_name) + + # Закрытие модального окна через кнопку закрытия + templates_tab.close_modal_window_by_toolbar_button(template_name) + + # Проверяем, что модальное окно закрылось + templates_tab.should_not_be_modal_window()