From 0f6083f85bcab739a9ed0274fa050b16aafd4528 Mon Sep 17 00:00:00 2001 From: nsubbot Date: Wed, 26 Nov 2025 16:41:57 +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=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20'?= =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8/=D0=A3?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F/Pu?= =?UTF-8?q?sh-=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interactive_dropdown_list.py | 78 +++++++ .../settings_form_component.py | 18 +- locators/settings_form_locators.py | 9 +- pages/push_notifications_settings_tab.py | 195 ++++++++++++++++++ pages/session_settings_tab.py | 2 +- .../test_push_notifications_settings_tab.py | 124 +++++++++++ 6 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 components_derived/interactive_dropdown_list.py rename {components => components_derived}/settings_form_component.py (83%) create mode 100644 pages/push_notifications_settings_tab.py create mode 100644 tests/e2e/test_push_notifications_settings_tab.py diff --git a/components_derived/interactive_dropdown_list.py b/components_derived/interactive_dropdown_list.py new file mode 100644 index 0000000..f53bbf2 --- /dev/null +++ b/components_derived/interactive_dropdown_list.py @@ -0,0 +1,78 @@ +"""Модуль interactive_dropdown_list_component содержит класс для работы с интерактивными выпадающими списками, +позволяющими сделать выбор нескольких элементов. + +Класс InteractiveDropdownList наследует базовый функционал BaseComponent и добавляет +методы для взаимодействия с интерактивными выпадающими списками на странице. +""" + +from playwright.sync_api import Page, Locator, expect +from tools.logger import get_logger +from components.base_component import BaseComponent + +logger = get_logger("INTERACTIVE_DROPDOWN_LIST") + +class InteractiveDropdownList(BaseComponent): + """Класс для работы с выпадающими списками. + + Наследует функциональность BaseElement и добавляет специфичные + методы для выбора и проверки элементов списка. + """ + + def __init__(self, page: Page) -> None: + """Инициализирует компонент интерактивного выпадающего списка. + + Args: + page: Экземпляр страницы Playwright. + """ + + super().__init__(page) + + # Действия: + def get_checkbox_locator(self, text: str) -> Locator: + """Возвращает локатор чек-бокса для элемента списка с указанным текстом. + + Args: + text (str): Текст элемента для выбора. + """ + + checkbox_locator = self.get_locator('div.v-list__tile__title').get_by_text(text). \ + locator("../..").locator("//input[@role='checkbox']") + expect(checkbox_locator).to_be_visible(), \ + f"Checkbox for dropdown list item with text {text} is missing" + return checkbox_locator + + def deselect_item_with_text(self, text: str) -> None: + """Выбирает элемент списка по указанному тексту. + + Args: + text (str): Текст элемента для выбора. + """ + + self.get_checkbox_locator(text).uncheck(force=True) + + def select_item_with_text(self, text: str) -> None: + """Выбирает элемент списка по указанному тексту. + + Args: + text (str): Текст элемента для выбора. + """ + self.get_checkbox_locator(text).check(force=True) + + def get_selected_items(self, locator: str|Locator) -> list[str]: + """Возвращает список отмеченных элементов.""" + + selected_items = [] + + list_locator = self.get_locator(locator) + + items = list_locator.get_by_role("listitem").all() + + for item in items: + if item.get_by_role("checkbox").is_checked(): + item_text = item.text_content().strip() + if item_text: + selected_items.append(item_text) + + return selected_items + + # Проверки: diff --git a/components/settings_form_component.py b/components_derived/settings_form_component.py similarity index 83% rename from components/settings_form_component.py rename to components_derived/settings_form_component.py index ed20ced..59ec226 100644 --- a/components/settings_form_component.py +++ b/components_derived/settings_form_component.py @@ -4,11 +4,12 @@ from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.settings_form_locators import SettingsFormLocators +from elements.tooltip_button_element import TooltipButton from elements.button_element import Button from components.toolbar_component import ToolbarComponent from components.base_component import BaseComponent -logger = get_logger("MODAL_WINDOW") +logger = get_logger("SETTINGS_FORM") class SettingsFormComponent(BaseComponent): @@ -42,7 +43,12 @@ class SettingsFormComponent(BaseComponent): self.buttons.append(Button(self.page, locator, name)) - def get_button_by_name(self, name: str) -> Button | None: + def add_tooltip_button(self, locator: str, name: str) -> None: + """Добавляет кнопку в форму.""" + + self.buttons.append(TooltipButton(self.page, locator, name)) + + def get_button_by_name(self, name: str) -> Button | TooltipButton | None: """Ищет и возвращает кнопку по имени или None, если не найдена.""" for button in self.buttons: @@ -64,6 +70,14 @@ class SettingsFormComponent(BaseComponent): return self.is_scrollable_vertically(locator) + def check_button_tooltip(self, name: str, tooltip: str) -> None: + """Проверяет текст подсказки кнопки. """ + + button = self.get_button_by_name(name) + if button is None: + raise AssertionError(f"Unsupported button name {name}") + button.check_tooltip_with_text(tooltip) + def check_button_visibility(self, name: str) -> None: """Проверяет наличие кнопки по имени. Вызывает ошибку, если не найдена.""" diff --git a/locators/settings_form_locators.py b/locators/settings_form_locators.py index 905012a..7b7356d 100644 --- a/locators/settings_form_locators.py +++ b/locators/settings_form_locators.py @@ -7,10 +7,17 @@ class SettingsFormLocators: """Локаторы для компонента формы ввода и отображения полей настроек. - Содержит XPath локаторы для: + Содержит XPath/CSS локаторы для: SETTTINGS_FORM_SCROLL_CONTAINER (str): контейнера с прокруткой модального окна SETTTINGS_FORM_TITLE (str): заголовка тулбара + DROPDOWN_LIST (str): выпадающего списка + SELECTED_VALUES (str): строки с выбранными из списка значениями + CLEAR_SELECTION_BUTTON (str): кнопки удаления строки с выбранными из списка значениями """ SETTTINGS_FORM_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]" SETTTINGS_FORM_TITLE = f"{SETTTINGS_FORM_SCROLL_CONTAINER}//div[contains(@class, 'v-toolbar__title')]" + + DROPDOWN_LIST = "//div[contains(@class, 'menuable__content__active')]" + SELECTED_VALUES = "//div[@class='v-select__selections']" + CLEAR_SELECTION_BUTTON = "div.v-input__icon--clear" diff --git a/pages/push_notifications_settings_tab.py b/pages/push_notifications_settings_tab.py new file mode 100644 index 0000000..a5c1c59 --- /dev/null +++ b/pages/push_notifications_settings_tab.py @@ -0,0 +1,195 @@ +"""Модуль вкладки настройки Push уведомлений. + +Содержит класс PushNotificationsSettings для работы с вкладкой настройки Push уведомлений. +Позволяет проверять состояние и взаимодействовать с элементами вкладки. +""" + +import re +from playwright.sync_api import Page +from locators.settings_form_locators import SettingsFormLocators +from elements.text_input_element import TextInput +from elements.text_element import Text +from components.toolbar_component import ToolbarComponent +from components.alert_component import AlertComponent +from components_derived.settings_form_component import SettingsFormComponent +from components_derived.interactive_dropdown_list import InteractiveDropdownList +from pages.base_page import BasePage + + +class PushNotificationsSettingsTab(BasePage): + """Класс для работы с вкладкой настройки Push уведомлений. + + Предоставляет методы для взаимодействия с вкладкой настройки Push уведомлений. + + Args: + page: Экземпляр страницы Playwright. + """ + + def __init__(self, page: Page) -> None: + """Инициализирует компоненты вкладки настройки Push уведомлений.""" + + super().__init__(page) + + + self.toolbar = ToolbarComponent(page, "Push уведомления") + + # Форма для отображения/редактирования полей настроек Push уведомлений + self.settings_form = SettingsFormComponent(page) + self.settings_form.add_toolbar_title("Общие") + + message_setting_label = Text(page, + page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_text('Сообщение'), + "message_setting_label") + self.settings_form.add_content_item("message_setting_label", message_setting_label) + + loc_message_input = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_label('Сообщение').nth(1) + message_setting_input = TextInput(page, loc_message_input, "message_setting_input") + self.settings_form.add_content_item("message_setting_input", message_setting_input) + + users_settings_locator = page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_label('Пользователи') + users_setting_label = Text(page, users_settings_locator, "users_setting_label") + self.settings_form.add_content_item("users_setting_label", users_setting_label) + + users_setting_input = TextInput(page, + page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_role("combobox"), + "users_setting_input") + self.settings_form.add_content_item("users_setting_input", users_setting_input) + self.settings_form.add_content_item("users_list", InteractiveDropdownList(page)) + + self.settings_form.add_tooltip_button(page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_role("button", name='Отправить'), + "submit_button") + + self.alert = AlertComponent(page) + + # Действия: + def clear_users_setting_value(self) -> None: + """Очищает текущее значение поля настроек 'Пользователи'.""" + + selected_users = self.get_users_setting_value() + if len(selected_users) > 0: + clear_selection_button = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_role("combobox").locator(SettingsFormLocators.CLEAR_SELECTION_BUTTON) + clear_selection_button.click() + + def click_submit_button(self) -> None: + """Нажатие кнопки 'Отправить' в форме ввода настроек.""" + + self.settings_form.check_button_visibility("submit_button") + self.settings_form.get_button_by_name("submit_button").click() + + def get_message_setting_value(self) -> str: + """Возвращает текущее значение поля настроек 'Сообщение'. + + Returns: + str : Текущее значение поля настроек 'Сообщение'. + """ + + input_field = self.settings_form.get_content_item("message_setting_input") + return input_field.get_input_value().strip() + + def get_users_setting_value(self) -> str: + """Возвращает текущее значение поля настроек 'Пользователи'. + + Returns: + str : Текущее значение поля настроек 'Пользователи'. + """ + + users_setting_field_loc = self.page.locator(SettingsFormLocators.SETTTINGS_FORM_SCROLL_CONTAINER).\ + get_by_role("combobox").locator(SettingsFormLocators.SELECTED_VALUES) + + return users_setting_field_loc.text_content().strip() + + def input_message(self, text: str) -> None: + """Заполнение поля 'Сообщение'.""" + + message_input = self.settings_form.get_content_item("message_setting_input") + message_input.clear() + message_input.input_value(text) + + def deselect_users(self, users: list[str]) -> None: + """Изменение значения поля 'Пользователи' путем отмены выбора из выпадающего списка заданных имен.""" + + assert len(users) != 0, "Users list should not be empty" + + self.settings_form.get_content_item("users_setting_input").click() + users_list = self.settings_form.get_content_item("users_list") + + for user in users: + users_list.deselect_item_with_text(user) + + # Закрываем выпадающий список (кликаем вне его) + self.page.mouse.click(10, 10) + + def select_users(self, users: list[str]) -> None: + """Заполнение поля 'Пользователи' путем выбора из выпадающего списка заданных имен.""" + + assert len(users) != 0, "Users list should not be empty" + + self.settings_form.get_content_item("users_setting_input").click() + users_list = self.settings_form.get_content_item("users_list") + + for user in users: + users_list.select_item_with_text(user) + + # Закрываем выпадающий список (кликаем вне его) + self.page.mouse.click(10, 10) + + # Проверки: + def check_content(self): + """Проверяет наличие и корректность всех элементов страницы.""" + + self.should_be_toolbar() + + self.should_be_form_toolbar() + + for name in self.settings_form.content_items.keys(): + if name == "users_list": + self.settings_form.get_content_item("users_setting_input").click() + users_list = self.settings_form.get_content_item(name) + selected_users = users_list.get_selected_items(SettingsFormLocators.DROPDOWN_LIST) + assert len(selected_users) == 0, "There should be no selected users" + else: + item = self.settings_form.get_content_item(name) + item.check_visibility( + f"Push notifications settings input form item with name '{name}' is missing" + ) + + self.settings_form.check_button_visibility("submit_button") + self.settings_form.check_button_tooltip("submit_button", "Отправить Push уведомление") + + def should_be_toolbar(self) -> None: + """Проверяет наличие тулбара страницы. + + Raises: + AssertionError: Если тулбар или кнопка редактирования отсутствуют. + """ + loc = self.page.get_by_role("navigation").filter( + has_text=re.compile("Push уведомления")).locator("div").nth(1) + self.toolbar.check_toolbar_presence_by_locator(loc, "Toolbar with title 'Push уведомления' is missing") + + def should_be_form_toolbar(self) -> None: + """Проверяет наличие тулбара формы редактирования настроек. + + Raises: + AssertionError: Если тулбар отсутствует. + """ + + self.settings_form.should_be_toolbar() + + def should_be_success_alert(self) -> None: + """Проверяет наличие сообщения об успешной отправке push-уведомления. + + Raises: + AssertionError: Если тулбар отсутствует. + """ + + alert_type = self.alert.get_alert_type() + assert alert_type == "success", f"Expected success alert, but got {alert_type} alert" + + self.alert.check_alert_presence('\nPush-уведомление\nуспешно отправлено\n') + self.alert.check_alert_absence('\nPush-уведомление\nуспешно отправлено\n') diff --git a/pages/session_settings_tab.py b/pages/session_settings_tab.py index 7d6181a..93e8dcf 100644 --- a/pages/session_settings_tab.py +++ b/pages/session_settings_tab.py @@ -9,8 +9,8 @@ from locators.settings_form_locators import SettingsFormLocators from elements.text_input_element import TextInput from elements.text_element import Text from components.toolbar_component import ToolbarComponent -from components.settings_form_component import SettingsFormComponent from components.alert_component import AlertComponent +from components_derived.settings_form_component import SettingsFormComponent from pages.base_page import BasePage diff --git a/tests/e2e/test_push_notifications_settings_tab.py b/tests/e2e/test_push_notifications_settings_tab.py new file mode 100644 index 0000000..9cfecca --- /dev/null +++ b/tests/e2e/test_push_notifications_settings_tab.py @@ -0,0 +1,124 @@ +"""Модуль тестов вкладки настройки Push уведомлений. + +Содержит тесты для проверки корректности отображения +и функциональности элементов страницы настройки Push уведомлений. +""" + +import pytest +from playwright.sync_api import Page +from pages.login_page import LoginPage +from pages.main_page import MainPage +from pages.push_notifications_settings_tab import PushNotificationsSettingsTab + + +# @pytest.mark.smoke +class TestPushNotificationsSettingsTab: + """Набор тестов для вкладки настройки Push уведомлений. + + Проверяет корректность отображения и функциональность элементов вкладки настройки Push уведомлений. + + Тесты покрывают следующие сценарии: + 1. test_session_settings_tab_content: Тест содержимого вкладки настройки Push уведомлений + + """ + + @pytest.fixture(scope="function", autouse=True) + def setup(self, browser: Page) -> None: + """Фикстура для подготовки тестового окружения. + + Выполняет: + 1. Авторизацию в системе + 2. Переход на вкладку настройки Push уведомлений через панель навигации + """ + # Авторизация в системе + 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("Push уведомления") + + # @pytest.mark.develop + def test_push_notifications_settings_tab_content(self, browser: Page) -> None: + """Тест содержимого вкладки настройки Push уведомлений. + + Проверяет: + Наличие и корректность элементов интерфейса + """ + expected_msg_value = "test" + + # Инициализация вкладки + push_notification_settings_tab = PushNotificationsSettingsTab(browser) + + # Проверка элементов интерфейса + push_notification_settings_tab.check_content() + + msg_value = push_notification_settings_tab.get_message_setting_value() + assert msg_value == expected_msg_value, \ + f"Actual message field value {msg_value} is not equal expected message field value {expected_msg_value}" + + # @pytest.mark.develop + def test_send_push_notification(self, browser: Page) -> None: + """Тест содержимого вкладки настройки Push уведомлений. + + Проверяет: + Заполнение полей и отправку Push уведомления + """ + receivers = ["admin", "manager", "operator"] + + # Инициализация вкладки + push_notification_settings_tab = PushNotificationsSettingsTab(browser) + + expected_msg_value = "My test message" + push_notification_settings_tab.input_message(expected_msg_value) + msg_value = push_notification_settings_tab.get_message_setting_value() + assert msg_value == expected_msg_value, \ + f"Actual message field value {msg_value} is not equal expected message field value {expected_msg_value}" + + push_notification_settings_tab.select_users(receivers) + + sep = ", " + expected_users = sep.join(receivers) + selected_users = push_notification_settings_tab.get_users_setting_value() + assert selected_users == expected_users, \ + f"Actual users field value {selected_users} is not equal expected users field value {expected_users}" + + push_notification_settings_tab.click_submit_button() + push_notification_settings_tab.should_be_success_alert() + + # @pytest.mark.develop + def test_users_setting_input(self, browser: Page) -> None: + """Тест содержимого вкладки настройки Push уведомлений. + + Проверяет: + Заполнение и очистку поля 'Пользователи' + """ + receivers = ["manager", "operator", "collector"] + sep = ", " + + # Инициализация вкладки + push_notification_settings_tab = PushNotificationsSettingsTab(browser) + + push_notification_settings_tab.select_users(receivers) + + expected_users = sep.join(receivers) + selected_users = push_notification_settings_tab.get_users_setting_value() + assert selected_users == expected_users, \ + f"Actual users field value {selected_users} is not equal expected users field value {expected_users}" + + receivers.remove("collector") + push_notification_settings_tab.deselect_users(["collector"]) + + expected_users = sep.join(receivers) + selected_users = push_notification_settings_tab.get_users_setting_value() + assert selected_users == expected_users, \ + f"Actual users field value {selected_users} is not equal expected users field value {expected_users}" + + push_notification_settings_tab.clear_users_setting_value() + selected_users = push_notification_settings_tab.get_users_setting_value() + assert len(selected_users) == 0, "There should be no selected users"