From 1adef646d8e795df75c6b68bf805cb480c4a30bb Mon Sep 17 00:00:00 2001 From: nsubbot Date: Wed, 17 Sep 2025 12:37:27 +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=BE=20=D0=BC=D0=BE=D0=B4=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=BA=D0=BD=D0=BE=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components_derived/modal_change_password.py | 144 ++++++++++++++++++++ components_derived/user_card.py | 5 +- elements/button_element.py | 5 +- locators/text_input_locators.py | 14 ++ tests/e2e/test_user_card.py | 139 ++++++++++++++++++- 5 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 components_derived/modal_change_password.py create mode 100644 locators/text_input_locators.py diff --git a/components_derived/modal_change_password.py b/components_derived/modal_change_password.py new file mode 100644 index 0000000..34f4593 --- /dev/null +++ b/components_derived/modal_change_password.py @@ -0,0 +1,144 @@ +"""Модуль modal_change_password содержит класс для работы с окном изменения пароля текущего пользователя. + +Класс ChangePasswordModalWindow наследует базовый функционал ModalWindowComponent +и реализует методы для изменения пароля пользователя. +""" + +from playwright.sync_api import Page +from tools.logger import get_logger +from locators.text_input_locators import TextInputLocators +from locators.modal_window_locators import ModalWindowLocators +from elements.text_element import Text +from elements.text_input_element import TextInput +from data.environment import host +from components.modal_window_component import ModalWindowComponent +from components.alert_component import AlertComponent + +logger = get_logger("CHANGE_PASSWORD_MODAL_WINDOW") + + +class ChangePasswordModalWindow(ModalWindowComponent): + """Модальное окно изменения пароля текущего пользователя. + + Наследует ModalWindowComponent и добавляет: + - Поля задания пароля + - Кнопки действий (Сохранить, Отменить) + """ + + def __init__(self, page: Page): + """Инициализирует элементы формы редактирования пользователя.""" + + super().__init__(page) + + modal_window_locator = page.locator(ModalWindowLocators.MODAL_WINDOW) + + # Тулбар с заголовком + user_name = host.get_current_user_name() + self.add_toolbar_title(f"Изменить пароль для пользователя {user_name}?") + + # Поля ввода пароля + loc = modal_window_locator.get_by_label("Введите текущий пароль *") + old_password_input = TextInput(page, loc, "old_password_input") + self.add_content_item("old_password_input", old_password_input) + + loc = modal_window_locator.get_by_label("Введите новый пароль *") + new_password_input = TextInput(page, loc, "new_password_input") + self.add_content_item("new_password_input", new_password_input) + + loc = modal_window_locator.get_by_label("Введите повторно новый пароль *") + confirm_password_input = TextInput(page, loc, "confirm_password_input") + self.add_content_item("confirm_password_input", confirm_password_input) + + input_form_error_message = Text(page, + modal_window_locator.locator(TextInputLocators.INPUT_FORM_MESSAGE), + "input form error message") + self.add_content_item("input_form_error_message", input_form_error_message) + + # Добавление кнопок действий + locator_button_save = self.page.get_by_role("button", name="Сохранить") + self.add_button(locator_button_save, "save") + + locator_button_cancel = self.page.get_by_role("button", name="Отменить") + self.add_button(locator_button_cancel, "cancel") + + # Alert при успешном добавлении пользователя + self.alert = AlertComponent(page) + + # Действия: + def click_cancel_button(self) -> None: + """Нажимает кнопку 'Отменить'""" + + self.get_button_by_name("cancel").click() + + def change_password(self, old_password: str, new_password: str): + """Заполняет элементы формы, нажимает кнопку 'Сохранить'""" + + error = "" + + self.get_content_item("old_password_input").input_value(old_password) + self.get_content_item("new_password_input").input_value(new_password) + self.get_content_item("confirm_password_input").input_value(new_password) + + button_save = self.get_button_by_name("save") + + if button_save.is_disabled(): + error_message = self.get_content_item("input_form_error_message") + error = error_message.get_text(0) + return False, error + + button_save.click() + + is_changed = False + + alert_type = self.alert.get_alert_type() + if alert_type == "success": + self.alert.check_alert_presence(' Пароль успешно изменён ') + self.alert.check_alert_absence(' Пароль успешно изменён ') + is_changed = True + elif alert_type == "error": + # to do: fix message after translation + self.alert.check_alert_presence(' Old password not equal ') + self.alert.check_alert_absence(' Old password not equal ') + + error = "Old password is not equal real password" + else: + error = f"Got unexpected alert type {alert_type}" + + return is_changed, error + + def get_password_inputs(self) -> []: + """Возвращает список полей ввода пароля (для тестовых целей).""" + + text_inputs = [] + text_inputs.append(self.get_content_item("old_password_input")) + text_inputs.append(self.get_content_item("new_password_input")) + text_inputs.append(self.get_content_item("confirm_password_input")) + return text_inputs + + # Проверки: + def check_content(self): + """Проверяет наличие и корректность всех элементов формы.""" + + self.check_by_window_title() + + self.get_content_item("old_password_input").check_visibility( + "Old password input form is missing" + ) + self.get_content_item("new_password_input").check_visibility( + "New password input form is missing" + ) + self.get_content_item("confirm_password_input").check_visibility( + "Confirm password input form is missing" + ) + + self.check_button_visibility("cancel") + + button_save = self.get_button_by_name("save") + is_disabled = button_save.is_disabled() + assert is_disabled, "Button 'Сохранить' should be hidden" + + def check_error_message(self, text: str) -> None: + """Проверяет сообщение об ошибке, возникшее при заполнении полей формы.""" + + error_message = self.get_content_item("input_form_error_message") + error_message.check_have_text(text, "Unexpected error message") diff --git a/components_derived/user_card.py b/components_derived/user_card.py index cc0cb63..2cefc59 100644 --- a/components_derived/user_card.py +++ b/components_derived/user_card.py @@ -11,6 +11,7 @@ from elements.button_element import Button from data.roles_dict import roles_dict from data.environment import host from components.base_component import BaseComponent +from components_derived.modal_change_password import ChangePasswordModalWindow from components_derived.dialog_user_settings import UserSettingsDialogWindow logger = get_logger("USER_CARD") @@ -69,6 +70,7 @@ class UserCard(BaseComponent): # окна, отрываемые после нажатия кнопок self.user_settings_dialog_window = UserSettingsDialogWindow(page) + self.change_password_modal_window = ChangePasswordModalWindow(page) # Действия: # def click_close_button(self): @@ -79,13 +81,14 @@ class UserCard(BaseComponent): # self.close_button.click() - def click_change_password_button(self) -> None: + def click_change_password_button(self) -> ChangePasswordModalWindow: """Нажимает кнопку открытия окна изменения пароля. Выполняет клик по кнопке 'Изменить пароль' в карточке пользователя. """ self.change_password_button.click() + return self.change_password_modal_window def click_logout_button(self) -> None: """Нажимает кнопку выхода из системы. diff --git a/elements/button_element.py b/elements/button_element.py index 71d5867..f6c77b7 100644 --- a/elements/button_element.py +++ b/elements/button_element.py @@ -31,4 +31,7 @@ class Button(BaseElement): # (Методы действий будут добавлены по мере необходимости) # Проверки: - # (Методы проверок будут добавлены по мере необходимости) + def is_disabled(self) -> bool: + """ Возвращает значение, отключена ли кнопка (является скрытой) """ + + return self.locator.is_disabled() diff --git a/locators/text_input_locators.py b/locators/text_input_locators.py new file mode 100644 index 0000000..3a67296 --- /dev/null +++ b/locators/text_input_locators.py @@ -0,0 +1,14 @@ +"""Модуль text_input_locators содержит локаторы элементов ввода текста. + +Класс TextInputLocators предоставляет XPath локаторы для работы +с элементами ввода текста на страницах приложения. +""" + +class TextInputLocators: + """Локаторы для элементов ввода текста. + + Содержит XPath локаторы для: + INPUT_FORM_MESSAGE (str): сообщения-предупреждения поля формы ввода + """ + + INPUT_FORM_MESSAGE = "//div[contains(@class,'v-messages__message')]" diff --git a/tests/e2e/test_user_card.py b/tests/e2e/test_user_card.py index 5edbeac..c3f4f20 100644 --- a/tests/e2e/test_user_card.py +++ b/tests/e2e/test_user_card.py @@ -6,9 +6,12 @@ import pytest from playwright.sync_api import Page +from pages.users_tab import UsersTab from pages.main_page import MainPage from pages.login_page import LoginPage +user_data = {"name": "TestUserForChangePwd", "role": "Администратор", "password": "qwerty", "new_password": "ytrewq"} + # @pytest.mark.smoke class TestUserCard: """Класс тестов для проверки карточки пользователя. @@ -20,6 +23,56 @@ class TestUserCard: browser: Фикстура для работы с браузером. """ + @pytest.fixture(scope="function") + def create_user(self, browser: Page) -> None: + """Фикстура для создания тестового пользователя.""" + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + ut = UsersTab(browser) + + # Создание нового пользователя + mp.click_main_navigation_panel_item("Настройки") + mp.click_subpanel_item("Пользователи") + ut.open_add_user_window() + ut.add_new_user(user_data) + + # Обновление списка пользователей (двойной клик - возможно баг?) + mp.click_subpanel_item("Пользователи") + mp.click_subpanel_item("Пользователи") + + # Проверка наличия пользователя в таблице + ut.should_be_user_in_table(user_data["name"], user_data["role"]) + + # Выход из системы текущего пользователя + mp.do_logout() + + yield + + @pytest.fixture(scope="function") + def cleanup_user(self, browser: Page) -> None: + """Фикстура для удаления тестового пользователя после теста.""" + yield + + # Выход из системы текущего пользователя + mp = MainPage(browser) + mp.do_logout() + + # Авторизация администратором для очистки + login_page = LoginPage(browser) + login_page.do_login() + + # Удаляем пользователя + mp_admin = MainPage(browser) + mp_admin.click_main_navigation_panel_item("Настройки") + mp_admin.click_subpanel_item("Пользователи") + + ut = UsersTab(browser) + ut.open_edit_user_page_by_user(user_data["name"], user_data["role"]) + ut.delete_user(user_data["name"]) + # @pytest.mark.develop def test_user_card_content(self, browser: Page) -> None: """Проверяет наличие и корректность элементов карточки пользователя. @@ -38,7 +91,7 @@ class TestUserCard: # @pytest.mark.develop def test_open_close_user_settings_window(self, browser: Page) -> None: - """Проверяет возможностьоткрытия и закрытия диалогового окна просмотра сессионных данных пользователя. + """Проверяет возможность открытия и закрытия диалогового окна просмотра сессионных данных пользователя. Args: browser: Экземпляр страницы Playwright. @@ -74,3 +127,87 @@ class TestUserCard: user_settings_window.check_window_visibility() user_settings_window.check_content() + + # @pytest.mark.develop + def test_change_password_window_content(self, browser: Page) -> None: + """Проверяет наличие и корректность элементов окна изменения пароля текущего пользователя. + + Args: + browser: Экземпляр страницы Playwright. + """ + + lp = LoginPage(browser) + lp.do_login() + + mp = MainPage(browser) + + user_card = mp.click_user_button() + change_password_window = user_card.click_change_password_button() + change_password_window.check_content() + + # @pytest.mark.develop + def test_change_password_successful(self, browser: Page, + create_user: None, + cleanup_user: None) -> None: + """Проверяет успешное изменение пароля текущего пользователя. + + Args: + browser: Экземпляр страницы Playwright. + """ + + # Вход в систему для тестового пользователя + lp = LoginPage(browser) + lp.do_login(username=user_data["name"], password=user_data["password"]) + + # Инициализация главной страницы + mp = MainPage(browser) + + user_card = mp.click_user_button() + change_password_window = user_card.click_change_password_button() + is_changed, error = change_password_window.change_password(user_data["password"], user_data["new_password"]) + assert is_changed, f"Unsucessful attempt to change password: {error}" + + @pytest.mark.develop + def test_change_password_unsuccessful(self, browser: Page, + create_user: None, + cleanup_user: None) -> None: + """Проверяет неуспешное изменение пароля текущего пользователя. + + Args: + browser: Экземпляр страницы Playwright. + """ + + # Вход в систему для тестового пользователя + lp = LoginPage(browser) + lp.do_login(username=user_data["name"], password=user_data["password"]) + + # Инициализация главной страницы + mp = MainPage(browser) + + # Значение полей нового пароля и подтверждения нового пароля не совпадают + user_card = mp.click_user_button() + change_password_window = user_card.click_change_password_button() + password_inputs = change_password_window.get_password_inputs() + + password_inputs[0].input_value(user_data["password"]) + password_inputs[1].input_value(user_data["new_password"]) + password_inputs[2].input_value("12345") + + change_password_window.check_error_message("Пароли не совпадают") + change_password_window.click_cancel_button() + + # Используется неправильный старый пароль + user_card = mp.click_user_button() + change_password_window = user_card.click_change_password_button() + is_changed, _ = change_password_window.change_password("123456789", user_data["new_password"]) + assert not is_changed, "Sucessful attempt to change password for incorrect old password" + change_password_window.click_cancel_button() + + # Пустое поле ввода пароля на примере поля ввода старого пароля + user_card = mp.click_user_button() + change_password_window = user_card.click_change_password_button() + is_changed, error = change_password_window.change_password("", user_data["new_password"]) + assert not is_changed, "Sucessful attempt to change password for empty old password input" + err_message = "Это поле обязательно для заполнения" + assert error != err_message, f"Expected error message '{err_message}' is not equal '{error}'" + change_password_window.click_cancel_button()