diff --git a/alert_component.py b/alert_component.py new file mode 100644 index 0000000..3c4d876 --- /dev/null +++ b/alert_component.py @@ -0,0 +1,98 @@ +from elements.text_element import Text +from playwright.sync_api import Page, expect +from tools.logger import get_logger + +from components.base_component import BaseComponent + +logger = get_logger("ALERT") + + +class AlertComponent(BaseComponent): + """Компонент для работы с alert-окнами. + + Поддерживает различные типы alert-окон: error, success, info, warning. + + Атрибуты: + page: экземпляр страницы Playwright + alert_type: тип alert-окна (error/success/info/warning) + text: текстовый элемент сообщения alert-окна + """ + + def __init__(self, page: Page, alert_type: str): + """Инициализация компонента alert-окна. + + Args: + page: экземпляр страницы Playwright + alert_type: тип alert-окна (error/success/info/warning) + + Raises: + ValueError: если передан неподдерживаемый тип alert-окна + """ + super().__init__(page) + + alert_types = ["error", "success", "info", "warning"] + if alert_type not in alert_types: + raise ValueError("Unsupported type of alert window") + + self.alert_type = alert_type + self.text = Text(page, + f"//div[@class='v-alert {self.alert_type}']/div", + "Alert message") + + # Действия: + def get_text(self) -> str: + """Получение текста сообщения из alert-окна. + + Returns: + str: текст сообщения alert-окна + """ + return self.text.get_text(0) + + # Проверки: + def check_presence(self, text: str)-> None: + """Проверка наличия alert-окна с заданным текстом. + + Args: + text: текст для проверки (если пустая строка - проверяется только наличие окна) + + Raises: + AssertionError: если alert-окно не найдено + """ + msg = f"No {self.alert_type} alert window on page" + if text == "": + expect(self.page.get_by_role("alert")).to_be_visible(), msg + else: + expect( + self.page.get_by_role("alert").filter( + has_text=text)).to_be_visible(), msg + + def check_absence(self, text: str, timeout: int = 30000): + """Проверка отсутствия alert-окна с заданным текстом. + + Args: + text: текст для проверки + timeout: время ожидания исчезновения (в миллисекундах) + + Raises: + AssertionError: если alert-окно не исчезает в течение заданного времени + """ + seconds = int(timeout / 1000) + msg = f"Alert { + self.alert_type} window should disappear after {seconds} seconds" + expect( + self.page.get_by_role("alert").filter( + has_text=text)).to_be_hidden( + timeout=timeout), msg + + def check_text(self, alert_text: str): + """Проверка точного соответствия текста в alert-окне. + + Args: + alert_text: ожидаемый текст сообщения + + Raises: + AssertionError: если текст не соответствует ожидаемому + """ + self.text.check_have_text( + alert_text, f"Unexpected message in alert { + self.alert_type} window") diff --git a/components/alert_component.py b/components/alert_component.py index eb6b332..6b782cb 100644 --- a/components/alert_component.py +++ b/components/alert_component.py @@ -1,3 +1,9 @@ +"""Модуль для работы с компонентом alert-окна в Playwright. + +Содержит класс AlertComponent для взаимодействия с различными типами +alert-окон (error, success, info, warning) и проверки их состояния. +""" + from playwright.sync_api import Page, expect from tools.logger import get_logger from elements.text_element import Text @@ -7,25 +13,21 @@ logger = get_logger("ALERT") class AlertComponent(BaseComponent): - """Компонент для работы с alert-окнами. + """Компонент для работы с alert-окнами Playwright. - Поддерживает различные типы alert-окон: error, success, info, warning. - - Атрибуты: - page: экземпляр страницы Playwright - alert_type: тип alert-окна (error/success/info/warning) - text: текстовый элемент сообщения alert-окна + Поддерживает типы: error, success, info, warning. + Позволяет проверять наличие, отсутствие и текст сообщений. """ def __init__(self, page: Page, alert_type: str): - """Инициализация компонента alert-окна. + """Инициализирует компонент alert-окна. Args: - page: экземпляр страницы Playwright - alert_type: тип alert-окна (error/success/info/warning) + page: Экземпляр страницы Playwright. + alert_type: Тип alert-окна (error/success/info/warning). Raises: - ValueError: если передан неподдерживаемый тип alert-окна + ValueError: Если передан неподдерживаемый тип alert-окна. """ super().__init__(page) @@ -37,25 +39,24 @@ class AlertComponent(BaseComponent): self.alert_type = alert_type self.text = Text(page, f"//div[@class='v-alert {self.alert_type}']/div", "Alert message") - # Действия: def get_text(self) -> str: - """Получение текста сообщения из alert-окна. + """Возвращает текст сообщения из alert-окна. Returns: - str: текст сообщения alert-окна + str: Текст сообщения. """ return self.text.get_text(0) - # Проверки: def check_alert_presence(self, text: str): - """Проверка наличия alert-окна с заданным текстом. + """Проверяет наличие alert-окна с заданным текстом. Args: - text: текст для проверки (если пустая строка - проверяется только наличие окна) + text: Текст для проверки. Если пустая строка - проверяет только + наличие окна. Raises: - AssertionError: если alert-окно не найдено + AssertionError: Если alert-окно не найдено. """ msg = f"No {self.alert_type} alert window on page" @@ -65,28 +66,29 @@ class AlertComponent(BaseComponent): expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_visible(), msg def check_alert_absence(self, text: str, timeout: int = 30000): - """Проверка отсутствия alert-окна с заданным текстом. + """Проверяет отсутствие alert-окна с заданным текстом. Args: - text: текст для проверки - timeout: время ожидания исчезновения (в миллисекундах) + text: Текст для проверки. + timeout: Время ожидания исчезновения (мс). Raises: - AssertionError: если alert-окно не исчезает в течение заданного времени + AssertionError: Если окно не исчезает в течение заданного времени. """ + seconds = int(timeout/1000) msg = f"Alert {self.alert_type} window should disappear after {seconds} seconds" expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_hidden(timeout=timeout), msg def check_text(self, alert_text: str): - """Проверка точного соответствия текста в alert-окне. + """Проверяет точное соответствие текста в alert-окне. Args: - alert_text: ожидаемый текст сообщения + alert_text: Ожидаемый текст сообщения. Raises: - AssertionError: если текст не соответствует ожидаемому + AssertionError: Если текст не соответствует ожидаемому. """ self.text.check_have_text(alert_text, - f"Unexpected message in alert {self.alert_type} window") + f"Unexpected message in alert {self.alert_type} window") diff --git a/components/base_component.py b/components/base_component.py index bbe395e..98e11bf 100644 --- a/components/base_component.py +++ b/components/base_component.py @@ -1,3 +1,8 @@ +"""Базовый модуль для работы с компонентами страницы. + +Содержит базовый класс для взаимодействия с элементами страницы через Playwright. +""" + from playwright.sync_api import Page, Locator, expect from tools.logger import get_logger @@ -11,17 +16,15 @@ class BaseComponent: - получение локаторов - проверка видимости элементов - работа с прокруткой - - Атрибуты: - page: экземпляр страницы Playwright """ def __init__(self, page: Page): """Инициализация базового компонента. Args: - page: экземпляр страницы Playwright + page: экземпляр страницы Playwright. """ + self.page = page # Действия: @@ -29,14 +32,15 @@ class BaseComponent: """Получение объекта Locator из строки или существующего Locator. Args: - locator: строка с CSS/XPath селектором или объект Locator + locator: строка с CSS/XPath селектором или объект Locator. Returns: - Locator: объект для работы с элементом + Locator: объект для работы с элементом. Raises: - TypeError: если передан некорректный тип локатора + TypeError: если передан некорректный тип локатора. """ + if isinstance(locator, Locator): return locator elif isinstance(locator, str): @@ -55,53 +59,57 @@ class BaseComponent: # return elements # Проверки: - def check_presence(self, locator, msg): + def check_presence(self, locator: str | Locator, msg: str) -> None: """Проверка видимости элемента на странице. Args: - locator: локатор элемента (строка или объект Locator) - msg: сообщение об ошибке при неудачной проверке + locator: локатор элемента (строка или объект Locator). + msg: сообщение об ошибке при неудачной проверке. Raises: - AssertionError: если элемент не виден на странице + AssertionError: если элемент не виден на странице. """ + loc = self.get_locator(locator) expect(loc).to_be_visible(visible=True, timeout=12000), msg - def is_scrollable_vertically(self, locator) -> bool: + def is_scrollable_vertically(self, locator: str | Locator) -> bool: """Проверка возможности вертикальной прокрутки элемента. Args: - locator: локатор элемента + locator: локатор элемента. Returns: - bool: True если элемент можно прокрутить вертикально + bool: True если элемент можно прокрутить вертикально. """ + loc = self.get_locator(locator) return loc.evaluate("el => el.scrollHeight > el.clientHeight") - def is_scrollable_horizontally(self, locator) -> bool: + def is_scrollable_horizontally(self, locator: str | Locator) -> bool: """Проверка возможности горизонтальной прокрутки элемента. Args: - locator: локатор элемента + locator: локатор элемента. Returns: - bool: True если элемент можно прокрутить горизонтально + bool: True если элемент можно прокрутить горизонтально. """ + loc = self.get_locator(locator) return loc.evaluate("el => el.scrollWidth > el.clientWidth") # Методы прокрутки: - def scroll_up(self, locator): + def scroll_up(self, locator: str | Locator) -> None: """Прокрутка элемента вверх. Args: - locator: локатор элемента + locator: локатор элемента. Raises: - AssertionError: если прокрутка не выполнена до конца + AssertionError: если прокрутка не выполнена до конца. """ + loc = self.get_locator(locator) loc.evaluate("el => el.scrollTo(0, 0)") loc.wait_for(timeout=2000) @@ -110,15 +118,16 @@ class BaseComponent: scroll_position = loc.evaluate("el => el.scrollTop") assert scroll_position == 0, "Invalid postion after scroll up" - def scroll_down(self, locator): + def scroll_down(self, locator: str | Locator) -> None: """Прокрутка элемента вниз. Args: - locator: локатор элемента + locator: локатор элемента. Raises: - AssertionError: если прокрутка не выполнена до конца + AssertionError: если прокрутка не выполнена до конца. """ + loc = self.get_locator(locator) loc.evaluate("el => el.scrollTo(0, el.scrollHeight)") loc.wait_for(timeout=2000) @@ -127,15 +136,16 @@ class BaseComponent: scroll_position = loc.evaluate("el => el.scrollTop") assert scroll_position > 0, "Invalid postion after scroll down" - def scroll_left(self, locator): + def scroll_left(self, locator: str | Locator) -> None: """Прокрутка элемента влево. Args: - locator: локатор элемента + locator: локатор элемента. Raises: - AssertionError: если прокрутка не выполнена до конца + AssertionError: если прокрутка не выполнена до конца. """ + loc = self.get_locator(locator) width = loc.evaluate("el => el.scrollWidth") @@ -148,15 +158,16 @@ class BaseComponent: scroll_position = loc.evaluate("el => el.scrollLeft") assert scroll_position == 0, "Invalid postion after scroll left" - def scroll_right(self, locator): + def scroll_right(self, locator: str | Locator) -> None: """Прокрутка элемента вправо. Args: - locator: локатор элемента + locator: локатор элемента. Raises: - AssertionError: если прокрутка не выполнена до конца + AssertionError: если прокрутка не выполнена до конца. """ + loc = self.get_locator(locator) width = loc.evaluate("el => el.scrollWidth") diff --git a/components/card_component.py b/components/card_component.py index 68721a2..07b03df 100644 --- a/components/card_component.py +++ b/components/card_component.py @@ -1,3 +1,8 @@ +"""Модуль компонента карточки пользователя. + +Содержит класс для работы с карточкой пользователя через Playwright. +""" + from playwright.sync_api import Page from tools.logger import get_logger from elements.button_element import Button @@ -9,19 +14,16 @@ logger = get_logger("USER_CARD") class CardComponent(BaseComponent): """Компонент карточки пользователя. - Предоставляет методы для взаимодействия с элементами карточки пользователя. - - Атрибуты: - page: экземпляр страницы Playwright - logout_button: кнопка выхода из системы + Предоставляет методы для взаимодействия с элементами карточки. """ def __init__(self, page: Page): - """Инициализация компонента карточки пользователя. + """Инициализирует компонент карточки пользователя. Args: - page: экземпляр страницы Playwright + page: Экземпляр страницы Playwright. """ + super().__init__(page) self.logout_button = Button( @@ -32,10 +34,11 @@ class CardComponent(BaseComponent): # Действия: def click_logout_button(self): - """Нажатие кнопки выхода из системы. + """Нажимает кнопку выхода из системы. Выполняет клик по кнопке 'Выйти' в карточке пользователя. """ + self.logout_button.click() # Проверки: diff --git a/components/confirm_component.py b/components/confirm_component.py index c9e54d1..78bf60f 100644 --- a/components/confirm_component.py +++ b/components/confirm_component.py @@ -1,3 +1,9 @@ +"""Модуль компонента окна подтверждения действий. + +Содержит класс ConfirmComponent для взаимодействия с окном подтверждения, +включая кнопки подтверждения, отмены и закрытия, а также проверки текста. +""" + from playwright.sync_api import Page from tools.logger import get_logger from locators.confirm_locators import ConfirmLocators @@ -12,6 +18,14 @@ class ConfirmComponent(BaseComponent): """Компонент окна подтверждения действий.""" def __init__(self, page: Page, cancel_button_text: str, allow_button_text: str): + """Инициализация компонента. + + Args: + page: Экземпляр страницы Playwright. + cancel_button_text: Текст кнопки отмены. + allow_button_text: Текст кнопки подтверждения. + """ + super().__init__(page) self.title = Text(page, ConfirmLocators.TITLE, "confirm title") @@ -31,22 +45,37 @@ class ConfirmComponent(BaseComponent): # Действия: def click_allow_button(self) -> None: - """Нажатие кнопки подтверждения действия.""" + """Нажимает кнопку подтверждения действия.""" + self.allow_button.click() def click_cancel_button(self) -> None: - """Нажатие кнопки отмены действия.""" + """Нажимает кнопку отмены действия.""" + self.cancel_button.click() def click_close_button(self) -> None: - """Нажатие кнопки закрытия окна подтверждения.""" + """Нажимает кнопку закрытия окна подтверждения.""" + self.close_button.click() # Проверки: def check_title(self, title: str, msg: str) -> None: - """Проверка текста заголовка окна подтверждения.""" + """Проверяет текст заголовка окна подтверждения. + + Args: + title: Ожидаемый текст заголовка. + msg: Сообщение при ошибке. + """ + self.title.check_have_text(title, msg) def check_text(self, text: str, msg: str) -> None: - """Проверка текста сообщения в окне подтверждения.""" + """Проверяет текст сообщения в окне подтверждения. + + Args: + text: Ожидаемый текст сообщения. + msg: Сообщение при ошибке. + """ + self.text.check_have_text(text, msg) diff --git a/components/json_container_component.py b/components/json_container_component.py index 335c04b..d593952 100644 --- a/components/json_container_component.py +++ b/components/json_container_component.py @@ -1,3 +1,9 @@ +"""Модуль для работы с JSON-контейнерами на веб-страницах. + +Содержит компонент для чтения и проверки JSON-данных в контейнерах. +Использует playwright для взаимодействия с элементами страницы. +""" + import json import jsondiff from playwright.sync_api import Page @@ -10,43 +16,46 @@ logger = get_logger("JSON_CONTAINER") class JsonContainerComponent(BaseComponent): """Компонент для работы с JSON-данными на странице. - Предоставляет методы для чтения и проверки JSON-данных, - отображаемых в специальных контейнерах на странице. - Атрибуты: - page: экземпляр страницы Playwright + + + Предоставляет методы чтения и проверки JSON-данных в контейнерах. + """ def __init__(self, page: Page): - """Инициализация JSON-контейнера. + """Инициализирует JSON-контейнер. Args: - page: экземпляр страницы Playwright + page: Экземпляр страницы Playwright. """ + super().__init__(page) # Действия: def read_data(self, locator): - """Чтение и форматирование JSON-данных из указанного локатора. + """Читает и форматирует JSON-данные из указанного локатора. Args: - locator: локатор элемента, содержащего JSON-данные + locator: Локатор элемента с JSON-данными. Returns: - dict: распарсенный JSON-объект + dict: Распарсенный JSON-объект. Raises: - json.JSONDecodeError: если данные не могут быть преобразованы в JSON + json.JSONDecodeError: Если данные не могут быть преобразованы в JSON. """ + def format_json_string(json_string): - """Вспомогательная функция для форматирования строки JSON. + """Форматирует строку JSON для корректного парсинга. Args: - json_string: сырая строка с JSON-данными + json_string: Сырая строка с JSON-данными. Returns: - str: отформатированная строка JSON + str: Отформатированная строка JSON. """ + substrings = json_string.splitlines() formatted_string_list = [] last_substring = substrings.pop() @@ -85,15 +94,16 @@ class JsonContainerComponent(BaseComponent): # Проверки: def check_json_equals(self, actual, expected, msg): - """Сравнение JSON-объектов на идентичность. + """Сравнивает JSON-объекты на идентичность. Args: - actual: фактический JSON-объект - expected: ожидаемый JSON-объект - msg: сообщение об ошибке + actual: Фактический JSON-объект. + expected: Ожидаемый JSON-объект. + msg: Сообщение об ошибке. Raises: - AssertionError: если объекты не идентичны + AssertionError: Если объекты не идентичны. """ + diff = jsondiff.diff(expected, actual, syntax='symmetric') assert len(diff) == 0, f"{msg}. DIFF is {diff}" diff --git a/components/modal_window_component.py b/components/modal_window_component.py index c35c56b..f78fff1 100644 --- a/components/modal_window_component.py +++ b/components/modal_window_component.py @@ -1,3 +1,6 @@ +"""Модуль компонента модального окна. Содержит класс для работы с модальными окнами, +их элементами и проверками.""" + from playwright.sync_api import Page from tools.logger import get_logger from locators.modal_window_locators import ModalWindowLocators @@ -9,7 +12,8 @@ logger = get_logger("MODAL_WINDOW") class ModalWindowComponent(BaseComponent): - """Компонент модального окна.""" + """Компонент модального окна. Предоставляет методы для взаимодействия с окном, + его содержимым и проверок.""" def __init__(self, page: Page): super().__init__(page) @@ -19,83 +23,101 @@ class ModalWindowComponent(BaseComponent): # Действия: def add_content_item(self, name: str, item: object) -> None: - """Добавление элемента содержимого в окно.""" + """Добавляет элемент содержимого в окно по заданному имени.""" + self.content_items[name] = item def get_content_item(self, name: str) -> object | None: - """Получение элемента содержимого по имени.""" + """Возвращает элемент содержимого по имени или None, если не найден.""" + return self.content_items.get(name) def add_toolbar_title(self, title: str) -> None: - """Добавление заголовка в панель инструментов.""" + """Добавляет заголовок в панель инструментов модального окна.""" + self.toolbar.add_title(title) def add_toolbar_button(self, locator: str, name: str) -> None: - """Добавление кнопки в панель инструментов.""" + """Добавляет кнопку в панель инструментов модального окна.""" + self.toolbar.add_button(locator, name) def add_button(self, locator: str, name: str) -> None: - """Добавление кнопки в окно.""" + """Добавляет кнопку в модальное окно.""" + self.buttons.append(Button(self.page, locator, name)) def get_button_by_name(self, name: str) -> Button | None: - """Поиск кнопки по имени.""" + """Ищет и возвращает кнопку по имени или None, если не найдена.""" + for button in self.buttons: if button.name == name: return button return None def click_button(self, name: str) -> None: - """Нажатие кнопки по имени.""" + """Кликает по кнопке с заданным именем. Вызывает ошибку, если не найдена.""" + button = self.get_button_by_name(name) if button is None: assert False, f"Button with name '{name}' not found" button.click() def click_toolbar_close_button(self) -> None: - """Нажатие кнопки закрытия в панели инструментов.""" + """Кликает по кнопке закрытия в панели инструментов.""" + self.toolbar.click_button("close") def scroll_window_down(self) -> None: - """Прокрутка содержимого окна вниз.""" + """Прокручивает содержимое окна вниз.""" + self.scroll_down(ModalWindowLocators.MODAL_WINDOW) def scroll_window_up(self) -> None: - """Прокрутка содержимого окна вверх.""" + """Прокручивает содержимое окна вверх.""" + self.scroll_up(ModalWindowLocators.MODAL_WINDOW) def scroll_window_left(self) -> None: - """Прокрутка содержимого окна влево.""" + """Прокручивает содержимое окна влево.""" + self.scroll_left(ModalWindowLocators.MODAL_WINDOW) def scroll_window_right(self) -> None: - """Прокрутка содержимого окна вправо.""" + """Прокручивает содержимое окна вправо.""" + self.scroll_right(ModalWindowLocators.MODAL_WINDOW) # Проверки: def check_window_vertical_scrolling(self) -> bool: - """Проверка возможности вертикальной прокрутки.""" + """Проверяет возможность вертикальной прокрутки окна.""" + return self.is_scrollable_vertically(ModalWindowLocators.MODAL_WINDOW) def check_window_horizontal_scrolling(self) -> bool: - """Проверка возможности горизонтальной прокрутки.""" + """Проверяет возможность горизонтальной прокрутки окна.""" + return self.is_scrollable_horizontally(ModalWindowLocators.MODAL_WINDOW) def check_by_window_title(self) -> None: - """Проверка наличия окна по заголовку.""" + """Проверяет наличие окна по заголовку.""" + self.toolbar.check_toolbar_presence(f"Modal window with '{self.toolbar.title}' is missing") def check_button_presence(self, name: str) -> None: - """Проверка наличия кнопки по имени.""" + """Проверяет наличие кнопки по имени. Вызывает ошибку, если не найдена.""" + button = self.get_button_by_name(name) if button is None: assert False, f"Button with name '{name}' not found" button.check_presence(f"Button with name '{name}' is missing") def check_toolbar_button_presence(self, name: str) -> None: - """Проверка наличия кнопки в панели инструментов.""" + """Проверяет наличие кнопки в панели инструментов.""" + self.toolbar.check_button_presence(name) def check_toolbar_button_tooltip(self, name: str, tooltip: str) -> None: - """Проверка подсказки кнопки в панели инструментов.""" + """Проверяет подсказку у кнопки в панели инструментов.""" + self.toolbar.check_button_tooltip(name, tooltip) diff --git a/components/navbar_component.py b/components/navbar_component.py index 6d9721a..4c2d99b 100644 --- a/components/navbar_component.py +++ b/components/navbar_component.py @@ -1,3 +1,5 @@ +"""Модуль компонента панели навигации. Содержит класс для работы с элементами навигации.""" + from playwright.sync_api import Page, Locator from tools.logger import get_logger from locators.navigation_panel_locators import NavigationPanelLocators @@ -7,24 +9,54 @@ logger = get_logger("NAVIGATION_PANEL") class NavigationPanelComponent(BaseComponent): - """Компонент панели навигации.""" + """Компонент панели навигации. Предоставляет методы для взаимодействия с ней.""" def __init__(self, page: Page): + """Инициализирует компонент панели навигации. + + Args: + page: Экземпляр страницы Playwright. + """ + super().__init__(page) # Действия: def get_item_names(self, locator: str | Locator) -> list[str]: - """Получает тексты всех элементов по указанному локатору.""" + """Возвращает тексты всех элементов по указанному локатору. + + Args: + locator: Локатор элементов или строка с CSS/XPath. + + Returns: + Список текстов элементов. + """ + loc = self.get_locator(locator) return loc.all_inner_texts() def click_item(self, locator: str | Locator, item_name: str) -> None: - """Кликает по элементу с указанным текстом.""" + """Кликает по элементу с указанным текстом. + + Args: + locator: Локатор элемента или строка с CSS/XPath. + item_name: Текст элемента для клика. + """ + loc = self.get_locator(locator) loc.get_by_text(item_name).click() def click_sub_item(self, locator: str | Locator, sublevel_number: int, item_name: str) -> None: - """Кликает по вложенному элементу с указанным текстом.""" + """Кликает по вложенному элементу с указанным текстом. + + Args: + locator: Локатор родительского элемента. + sublevel_number: Уровень вложенности (1 или 2). + item_name: Текст элемента для клика. + + Raises: + ValueError: Если уровень вложенности не 1 или 2. + """ + root_locator = self.get_locator(NavigationPanelLocators.NODE_ROOT) children_locator = self.get_locator(NavigationPanelLocators.NODE_CHILDREN) @@ -39,7 +71,15 @@ class NavigationPanelComponent(BaseComponent): # Проверки: def check_item_visibility(self, locator: str | Locator, item_name: str) -> None: - """Проверяет видимость элемента с указанным текстом.""" + """Проверяет видимость элемента с указанным текстом. + + Args: + locator: Локатор элемента или строка с CSS/XPath. + item_name: Текст элемента для проверки. + + Note: + Временная обработка для элементов с текстом 'Шаблоны'. + """ msg = f"Navigation panel item '{item_name}' is not visible" diff --git a/components/table_component.py b/components/table_component.py index d6b8d83..74f8807 100644 --- a/components/table_component.py +++ b/components/table_component.py @@ -1,19 +1,37 @@ +"""Модуль компонента таблицы. Содержит класс для работы с табличными данными.""" + from playwright.sync_api import Page, expect, Locator from tools.logger import get_logger from components.base_component import BaseComponent + logger = get_logger("TABLE") class TableComponent(BaseComponent): - """Компонент таблицы.""" + """Компонент таблицы. Предоставляет методы для работы с табличными данными.""" def __init__(self, page: Page): + """Инициализирует компонент таблицы. + + Args: + page: Экземпляр страницы Playwright. + """ + super().__init__(page) # Действия: - def get_row_locator(self, table_locator, row_index) -> Locator | None: - """Конструирует локатор для строки с заданным индексом.""" + def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None: + """Возвращает локатор строки по индексу. + + Args: + table_locator: Локатор таблицы. + row_index: Индекс строки. + + Returns: + Локатор строки или None, если индекс вне диапазона. + """ + table = self.get_locator(table_locator) rows = table.locator("//tbody/tr") @@ -24,9 +42,16 @@ class TableComponent(BaseComponent): return None def read(self, locator: str | Locator) -> list[list[str]]: - """Читает данные из таблицы.""" - table_data = [] + """Читает данные таблицы, включая заголовки. + Args: + locator: Локатор таблицы. + + Returns: + Двумерный список с данными таблицы. + """ + + table_data = [] table = self.get_locator(locator) # Чтение заголовка таблицы @@ -50,19 +75,35 @@ class TableComponent(BaseComponent): # Проверки: def check_first_row_visibility(self, locator: str | Locator) -> None: - """Проверяет видимость первой строки таблицы.""" + """Проверяет видимость первой строки таблицы. + + Args: + locator: Локатор таблицы. + """ + table = self.get_locator(locator) first_row = table.locator("//tbody/tr").first expect(first_row).to_be_visible(), "The first table row is not visible" def check_last_row_visibility(self, locator: str | Locator) -> None: - """Проверяет видимость последней строки таблицы.""" + """Проверяет видимость последней строки таблицы. + + Args: + locator: Локатор таблицы. + """ + table = self.get_locator(locator) last_row = table.locator("//tbody/tr").last expect(last_row).to_be_visible(), "The last table row is not visible" def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None: - """Проверяет подсветку строки при наведении.""" + """Проверяет изменение цвета строки при наведении. + + Args: + locator: Локатор таблицы. + row_index: Индекс проверяемой строки. + """ + table = self.get_locator(locator) row = table.locator("//tbody/tr").nth(row_index) diff --git a/components/toolbar_component.py b/components/toolbar_component.py index a056aaa..06d146d 100644 --- a/components/toolbar_component.py +++ b/components/toolbar_component.py @@ -1,3 +1,11 @@ +"""Модуль компонента тулбара (панели инструментов). + +Содержит класс ToolbarComponent для работы с элементами тулбара: +- Управление кнопками и их подсказками +- Проверка видимости элементов +- Взаимодействие с панелью инструментов +""" + from playwright.sync_api import Page, expect, Locator from tools.logger import get_logger from locators.toolbar_locators import ToolbarLocators @@ -8,12 +16,12 @@ logger = get_logger("TOOLBAR") class ToolbarComponent(BaseComponent): - """Компонент тулбара (панели инструментов). - Предоставляет методы для работы с панелью инструментов: - - Добавление/управление кнопками - - Проверка видимости элементов - - Взаимодействие с элементами тулбара + + """Компонент тулбара. Предоставляет методы для работы с панелью инструментов. + + + Args: page (Page): Экземпляр страницы Playwright @@ -21,17 +29,19 @@ class ToolbarComponent(BaseComponent): """ def __init__(self, page: Page, title: str): - """Инициализация компонента тулбара.""" + """Инициализирует компонент тулбара с указанным заголовком.""" + super().__init__(page) self.title = title self.buttons = [] def add_title(self, title: str) -> None: - """Устанавливает заголовок тулбара. + """Устанавливает новый заголовок тулбара. Args: - title (str): Новый заголовок тулбара + title (str): Новый заголовок """ + self.title = title def add_button(self, locator: Locator, name: str) -> None: @@ -41,6 +51,7 @@ class ToolbarComponent(BaseComponent): locator (Locator): Локатор кнопки name (str): Уникальное имя кнопки """ + self.buttons.append(TooltipButton(self.page, locator, name)) def get_button_by_name(self, name: str) -> TooltipButton | None: @@ -50,8 +61,9 @@ class ToolbarComponent(BaseComponent): name (str): Имя кнопки Returns: - TooltipButton | None: Экземпляр кнопки или None если не найдена + TooltipButton | None: Найденная кнопка или None """ + for button in self.buttons: if button.name == name: return button @@ -66,6 +78,7 @@ class ToolbarComponent(BaseComponent): Raises: AssertionError: Если кнопка не найдена """ + button = self.get_button_by_name(name) if button is None: raise AssertionError(f"Unsupported button name {name}") @@ -83,6 +96,7 @@ class ToolbarComponent(BaseComponent): Raises: AssertionError: Если имя кнопки не поддерживается """ + button = self.get_button_by_name(name) if button is None: raise AssertionError(f"Unsupported button name {name}") @@ -100,6 +114,7 @@ class ToolbarComponent(BaseComponent): Raises: AssertionError: Если имя кнопки не поддерживается """ + button = self.get_button_by_name(name) if button is None: raise AssertionError(f"Unsupported button name {name}") @@ -111,6 +126,7 @@ class ToolbarComponent(BaseComponent): Args: message (str): Сообщение об ошибке если тулбар не виден """ + locator = self.get_locator(ToolbarLocators.TITLE).filter(has_text=self.title) expect(locator).to_be_visible(), message @@ -123,6 +139,7 @@ class ToolbarComponent(BaseComponent): Raises: AssertionError: Если кнопка не найдена или не видна """ + button = self.get_button_by_name(name) if button is None: raise AssertionError(f"Unsupported button name {name}") @@ -136,8 +153,9 @@ class ToolbarComponent(BaseComponent): tooltip (str): Ожидаемый текст подсказки Raises: - AssertionError: Если кнопка не найдена или текст подсказки не совпадает + AssertionError: Если текст подсказки не совпадает """ + button = self.get_button_by_name(name) if button is None: raise AssertionError(f"Unsupported button name {name}") diff --git a/data/constants.py b/data/constants.py index bd81b39..1b1d24e 100644 --- a/data/constants.py +++ b/data/constants.py @@ -1,14 +1,20 @@ +"""Модуль constants содержит настройки и константы приложения. + +Основной класс Constants предоставляет доступ к переменным окружения, +используемым для аутентификации и других настроек. +""" + import os class Constants: - """Класс для хранения констант и переменных окружения. + """Хранит константы и переменные окружения. - Содержит переменные, используемые для аутентификации и других настроек. - Получает значения из переменных окружения. + + Получает значения из переменных окружения. Используется для аутентификации. Атрибуты: - login (str): Логин для аутентификации - password (str): Пароль для аутентификации + login (str): Логин для аутентификации. + password (str): Пароль для аутентификации. """ try: diff --git a/data/environment.py b/data/environment.py index 7a735a7..348a3fd 100644 --- a/data/environment.py +++ b/data/environment.py @@ -1,8 +1,20 @@ +"""Модуль environment содержит настройки окружения и URL-адресов. + +Класс Environment предоставляет методы для работы с окружением, +токенами и URL-адресами API. +""" + import os from typing import Dict class Environment: - """Класс для работы с окружением и URL-адресами.""" + """Управление окружением, URL-адресами и токенами. + + Атрибуты класса: + TEST (str): Константа для тестового окружения. + DEVELOP (str): Константа для dev-окружения. + URLS (Dict[str, str]): Словарь URL для разных окружений. + """ TEST: str = 'test' DEVELOP: str = 'develop' @@ -13,7 +25,8 @@ class Environment: } def __init__(self) -> None: - """Инициализация объекта окружения.""" + """Инициализирует объект окружения и токены.""" + try: self.env: str = os.getenv('ENV', self.TEST) self.access_token: str = "" @@ -22,7 +35,12 @@ class Environment: self.env: str = self.TEST def get_base_url(self) -> str: - """Возвращает базовый URL для текущего окружения.""" + """Возвращает базовый URL для текущего окружения. + + Возвращает: + str: Базовый URL с путем для теста или без него. + """ + if self.env in self.URLS: if self.env == self.TEST: return self.URLS[self.env] + "e-nms-ui/" @@ -30,17 +48,32 @@ class Environment: raise Exception(f"Unknown value of ENV variable {self.env}") def get_request_url(self) -> str: - """Возвращает URL для API-запросов.""" + """Возвращает URL для API-запросов. + + Возвращает: + str: URL для API-запросов. + """ + if self.env in self.URLS: return self.URLS[self.env] raise Exception(f"Unknown value of ENV variable {self.env}") def set_access_token(self, token: str) -> None: - """Устанавливает токен доступа.""" + """Устанавливает токен доступа. + + Аргументы: + token (str): Токен для установки. + """ + self.token = token def get_access_token(self) -> str: - """Возвращает текущий токен доступа.""" + """Возвращает текущий токен доступа. + + Возвращает: + str: Текущий токен доступа. + """ + return self.token diff --git a/data/roles_dict.py b/data/roles_dict.py index 832bf3e..0511763 100644 --- a/data/roles_dict.py +++ b/data/roles_dict.py @@ -1,7 +1,12 @@ +"""Модуль roles_dict содержит словарь соответствия ролей. + +Содержит сопоставление системных названий ролей с их отображаемыми названиями. +""" + # Словарь соответствия системных названий ролей их отображаемым названиям roles_dict = { "administrator": "Администратор", - "manager": "Контактное лицо", + "manager": "Контактное лицо", "operator": "Оператор", "inform_secur_user": "Специалист информационной безопасности", "user": "Пользователь" diff --git a/docs/config/README_форматирование_кода.md b/docs/config/README_форматирование_кода.md deleted file mode 100644 index 7fc008e..0000000 --- a/docs/config/README_форматирование_кода.md +++ /dev/null @@ -1,25 +0,0 @@ -# Форматирование кода в соответствии с PEP 8 и Google Python Style Guide - -## Требования к форматированию - -1. **Добавление Docstring**: - - Для класса: описание назначения и атрибутов в Google-формате на русском языке - - Для методов: описание аргументов, возвращаемых значений и возможных исключений - -2. **Сохранение комментариев**: - - Разделительные комментарии (например, `#actions:`, `# assertions:`) остаются без изменений - - Закомментированный код сохраняется в оригинальном виде - - Технические комментарии в методах не изменяются - -3. **Перевод комментариев**: - - Разделительные комментарии переводятся (например, `# Действия:`, `# Проверки:`) - - Пояснительные комментарии к логике кода переводятся - - Не переводятся: - - Технические сообщения в `assert`, `raise` и других системных конструкциях - - Закомментированный код - - Сообщения в логах и ошибках - -4. **Форматирование кода**: - - Соответствие PEP 8 (отступы, пробелы вокруг операторов) - - Сохранение исходной структуры кода - - Без изменений рабочей логики программы \ No newline at end of file diff --git a/docs/config/add_docstring.md b/docs/config/add_docstring.md new file mode 100644 index 0000000..ac1faed --- /dev/null +++ b/docs/config/add_docstring.md @@ -0,0 +1,14 @@ +**Добавление Docstring** + +Изменить строго соблюдая требования: +1. Добавить недостающие и улучшить имеющиеся docstrings язык русский. +2. Сделать docstring более компактными, сохранив всю важную информацию. +3. Ограничить в docstring длину строк 79 символами. +4. Добавить docstring перед импортами. +5. Должна быть пустая строка после каждого docstring. +6. Сохранить все текущие комментарии. +7. Запрещено изменять код (изменять только docstring). +8. Не удалять пустые строки. +9. В конце кода должна быть одна пустая строка. + +Вывести полный изменённый код и отчет о выполнении требований \ No newline at end of file diff --git a/elements/base_element.py b/elements/base_element.py index 8c5a907..72f1659 100644 --- a/elements/base_element.py +++ b/elements/base_element.py @@ -1,12 +1,30 @@ +"""Модуль base_element содержит базовый класс для работы с элементами страницы. + +Класс BaseElement предоставляет основные методы взаимодействия с элементами +и их проверки через Playwright. +""" + from playwright.sync_api import Page, Locator, expect, TimeoutError from tools.logger import get_logger logger = get_logger("BASE_ELEMENT") class BaseElement: - """Базовый класс для работы с элементами страницы.""" + """Базовый класс для работы с элементами страницы через Playwright. + + Предоставляет основные методы взаимодействия с элементами: + клики, получение текста, ожидание и проверки состояния. + """ def __init__(self, page: Page, locator: str | Locator, name: str) -> None: + """Инициализирует базовый элемент страницы. + + Args: + page: Экземпляр страницы Playwright + locator: Локатор элемента (строка или объект Locator) + name: Имя элемента для логирования + """ + self.page = page self.name = name self.locator: Locator @@ -20,32 +38,46 @@ class BaseElement: @property def type_of(self) -> str: + """Возвращает тип элемента (для логирования).""" + return "base element" # Действия: def click(self) -> None: + """Выполняет клик по элементу.""" + logger.info(f"Clicking {self.type_of} '{self.name}'") self.locator.click() def get_text(self, index: int) -> str: + """Возвращает текст элемента по указанному индексу.""" + logger.info(f"Get text for {self.type_of} '{self.name}'") return self.locator.nth(index).text_content() def wait_for_element(self, timeout: int = 12000) -> None: + """Ожидает появление элемента в течение заданного времени.""" + logger.info(f"Wait for {self.type_of} '{self.name}'") self.locator.wait_for(timeout=timeout) # Проверки: def check_have_text(self, text: str, msg: str) -> None: + """Проверяет наличие указанного текста в элементе.""" + logger.info(f"Check that {self.type_of} '{self.name}' has text '{text}'") expect(self.locator).to_have_text(text), msg def check_presence(self, msg: str) -> None: + """Проверяет видимость элемента на странице.""" + logger.info(f"Check that {self.type_of} '{self.name}' is present") print(self.locator) expect(self.locator).to_be_visible(visible=True, timeout=12000), msg def is_present(self, timeout: int = 5000) -> bool: + """Проверяет наличие элемента в течение заданного времени.""" + logger.info(f"Check that {self.type_of} '{self.name}' is present") try: self.locator.wait_for(timeout=timeout) @@ -54,6 +86,8 @@ class BaseElement: return True def is_not_present(self, timeout: int = 5000) -> bool: + """Проверяет отсутствие элемента в течение заданного времени.""" + logger.info(f"Check that {self.type_of} '{self.name}' is missing") try: self.locator.wait_for(timeout=timeout) diff --git a/elements/button_element.py b/elements/button_element.py index 39434a7..8569d1b 100644 --- a/elements/button_element.py +++ b/elements/button_element.py @@ -1,3 +1,9 @@ +"""Модуль button_element содержит класс для работы с кнопками на странице. + +Класс Button наследует базовый функционал BaseElement и предоставляет +специфичные методы для работы с элементами типа 'кнопка'. +""" + from tools.logger import get_logger from elements.base_element import BaseElement @@ -5,18 +11,20 @@ logger = get_logger("BUTTON") class Button(BaseElement): - """Класс для работы с элементами типа 'кнопка' на странице. + """Класс для работы с кнопками на странице. - Наследует функциональность базового элемента и добавляет специфичные для кнопок методы. + Наследует функциональность BaseElement и добавляет специфичные + для кнопок методы и проверки. """ @property def type_of(self) -> str: - """Возвращает тип элемента - 'кнопка'. + """Возвращает тип элемента ('кнопка'). Returns: - Строка с типом элемента. + str: Тип элемента - 'кнопка'. """ + return "button" # Действия: diff --git a/elements/checkbox_element.py b/elements/checkbox_element.py index a1ad559..8811d1d 100644 --- a/elements/checkbox_element.py +++ b/elements/checkbox_element.py @@ -1,3 +1,9 @@ +"""Модуль checkbox_element содержит класс для работы с чекбоксами. + +Класс Checkbox наследует базовый функционал BaseElement и добавляет +специфичные методы для работы с элементами типа 'чекбокс'. +""" + from tools.logger import get_logger from elements.base_element import BaseElement @@ -5,28 +11,32 @@ logger = get_logger("CHECKBOX") class Checkbox(BaseElement): - """Класс для работы с элементами типа 'чекбокс' на странице. + """Класс для работы с чекбоксами на странице. - Наследует функциональность базового элемента и добавляет специфичные для чекбоксов методы. + Наследует функциональность BaseElement и добавляет методы для + взаимодействия с чекбоксами и проверки их состояния. """ @property def type_of(self) -> str: - """Возвращает тип элемента - 'чекбокс'. + """Возвращает тип элемента ('чекбокс'). Returns: - Строка с типом элемента. + str: Тип элемента - 'чекбокс'. """ + return "checkbox" # Действия: def check(self) -> None: - """Устанавливает чекбокс в отмеченное состояние.""" + """Отмечает чекбокс (устанавливает галочку).""" + logger.info(f"Checking checkbox '{self.name}'") self.locator.check() def uncheck(self) -> None: - """Снимает отметку с чекбокса.""" + """Снимает отметку с чекбокса (убирает галочку).""" + logger.info(f"Unchecking checkbox '{self.name}'") self.locator.uncheck() @@ -35,7 +45,8 @@ class Checkbox(BaseElement): """Проверяет, отмечен ли чекбокс. Returns: - True, если чекбокс отмечен, иначе False. + bool: True если отмечен, False если нет. """ + logger.info(f"Checking if checkbox '{self.name}' is checked") return self.locator.is_checked() diff --git a/elements/dropdown_list_element.py b/elements/dropdown_list_element.py index 4de02e3..1a98c4a 100644 --- a/elements/dropdown_list_element.py +++ b/elements/dropdown_list_element.py @@ -1,43 +1,53 @@ +"""Модуль dropdown_list_element содержит класс для работы с выпадающими списками. + +Класс DropdownList наследует базовый функционал BaseElement и добавляет +методы для взаимодействия с выпадающими списками на странице. +""" + from tools.logger import get_logger from elements.base_element import BaseElement logger = get_logger("DROPDOWN_LIST") class DropdownList(BaseElement): - """Класс для работы с выпадающими списками на странице. + """Класс для работы с выпадающими списками. - Наследует функциональность базового элемента и добавляет специфичные для dropdown-списков методы. + Наследует функциональность BaseElement и добавляет специфичные + методы для выбора и проверки элементов списка. """ @property def type_of(self) -> str: - """Возвращает тип элемента - 'выпадающий список'. + """Возвращает тип элемента ('выпадающий список'). Returns: - Строка с типом элемента. + str: Тип элемента - 'выпадающий список'. """ + return "dropdown list" # Действия: def click_item_with_text(self, text: str) -> None: - """Кликает на элемент списка с указанным текстом. + """Выбирает элемент списка по указанному тексту. Args: - text: Текст элемента, который нужно выбрать. + text (str): Текст элемента для выбора. """ + logger.info(f'Selecting item with text "{text}" from dropdown "{self.name}"') self.page.get_by_role("listitem").filter(has_text=text).click() # Проверки: def check_item_with_text(self, text: str) -> None: - """Проверяет наличие и доступность элемента с указанным текстом. + """Проверяет наличие и доступность элемента списка. Args: - text: Текст элемента, который нужно проверить. + text (str): Текст элемента для проверки. Raises: AssertionError: Если элемент отсутствует или недоступен. """ + logger.info(f'Checking item with text "{text}" in dropdown "{self.name}"') enabled = self.page.get_by_role("listitem").filter(has_text=text).is_enabled() if not enabled: diff --git a/elements/text_element.py b/elements/text_element.py index 99f0de9..f20a1ce 100644 --- a/elements/text_element.py +++ b/elements/text_element.py @@ -1,21 +1,29 @@ +"""Модуль text_element содержит класс для работы с текстовыми элементами. + +Класс Text наследует базовый функционал BaseElement и предоставляет +методы для работы с текстовыми элементами на странице. +""" + from tools.logger import get_logger from elements.base_element import BaseElement logger = get_logger("TEXT") class Text(BaseElement): - """Класс для работы с текстовыми элементами на странице. + """Класс для работы с текстовыми элементами страницы. - Наследует функциональность базового элемента и добавляет специфичные для текста методы. + Наследует функциональность BaseElement и добавляет специфичные + методы для взаимодействия с текстовыми элементами. """ @property def type_of(self) -> str: - """Возвращает тип элемента - 'текст'. + """Возвращает тип элемента ('текст'). Returns: - Строка с типом элемента. + str: Тип элемента - 'текст'. """ + return "text" # Действия: diff --git a/elements/text_input_element.py b/elements/text_input_element.py index ea1ced7..10a3720 100644 --- a/elements/text_input_element.py +++ b/elements/text_input_element.py @@ -1,3 +1,9 @@ +"""Модуль text_input_element содержит класс для работы с текстовыми полями ввода. + +Класс TextInput наследует базовый функционал BaseElement и предоставляет +методы для взаимодействия с текстовыми полями ввода на странице. +""" + from playwright.sync_api import expect from tools.logger import get_logger from elements.base_element import BaseElement @@ -6,54 +12,60 @@ logger = get_logger("TEXT_INPUT") class TextInput(BaseElement): - """Класс для работы с текстовыми полями ввода на странице. + """Класс для работы с текстовыми полями ввода. - Наследует функциональность базового элемента и добавляет специфичные для текстовых полей методы. + Наследует функциональность BaseElement и добавляет методы + для ввода, очистки и проверки текстовых полей. """ @property def type_of(self) -> str: - """Возвращает тип элемента - 'текстовое поле ввода'. + """Возвращает тип элемента ('текстовое поле ввода'). Returns: - Строка с типом элемента. + str: Тип элемента - 'текстовое поле ввода'. """ + return "text input" # Действия: def get_input_value(self) -> str: - """Получает текущее значение текстового поля. + """Возвращает текущее значение поля ввода. Returns: - Текущее значение поля ввода. + str: Текущее значение в поле. """ + logger.info(f'Getting value from text input "{self.name}"') return self.locator.input_value() def input_value(self, value: str) -> None: - """Вводит указанное значение в текстовое поле. + """Вводит указанное значение в поле. Args: - value: Значение для ввода. + value (str): Значение для ввода. """ + logger.info(f'Inputting value "{value}" to text input "{self.name}"') self.locator.fill(value) def clear(self) -> None: """Очищает содержимое текстового поля.""" + logger.info(f'Clearing text input "{self.name}"') self.locator.press('Control+A') self.locator.press('Backspace') # Проверки: def check_empty_input(self, msg: str) -> None: - """Проверяет, что текстовое поле пустое. + """Проверяет, что поле ввода пустое. Args: - msg: Сообщение об ошибке при неудачной проверке. + msg (str): Сообщение об ошибке при неудачной проверке. Raises: AssertionError: Если поле не пустое. """ + logger.info(f'Checking that text input "{self.name}" is empty') expect(self.locator).to_be_empty(), msg diff --git a/elements/tooltip_button_element.py b/elements/tooltip_button_element.py index 45f148a..935ad54 100644 --- a/elements/tooltip_button_element.py +++ b/elements/tooltip_button_element.py @@ -1,33 +1,42 @@ +"""Модуль tooltip_button_element содержит класс для работы с кнопками с подсказками. + +Класс TooltipButton наследует базовый функционал BaseElement и добавляет +методы для проверки всплывающих подсказок у кнопок. +""" + from tools.logger import get_logger from elements.base_element import BaseElement logger = get_logger("TOOLTIP_BUTTON") class TooltipButton(BaseElement): - """Класс элемента кнопки с всплывающей подсказкой. + """Класс для работы с кнопками, имеющими всплывающие подсказки. - Наследует функциональность базового элемента и добавляет методы для работы с подсказками. + Наследует функциональность BaseElement и добавляет методы + для взаимодействия с подсказками кнопок. """ @property def type_of(self) -> str: - """Возвращает тип элемента. + """Возвращает тип элемента ('tooltip_button'). Returns: - str: Тип элемента ('tooltip_button') + str: Тип элемента - кнопка с подсказкой. """ + return "tooltip_button" def check_tooltip_with_text(self, tooltip_locator: str, expected_text: str) -> None: - """Проверяет текст всплывающей подсказки. + """Проверяет соответствие текста всплывающей подсказки. Args: - tooltip_locator (str): Локатор элемента подсказки - expected_text (str): Ожидаемый текст подсказки + tooltip_locator (str): Локатор элемента подсказки. + expected_text (str): Ожидаемый текст подсказки. Raises: - AssertionError: Если текст подсказки не соответствует ожидаемому + AssertionError: Если текст подсказки не соответствует ожидаемому. """ + # Наведение на элемент для отображения подсказки self.locator.hover() diff --git a/fixtures/pages.py b/fixtures/pages.py index af326ac..050c6ca 100644 --- a/fixtures/pages.py +++ b/fixtures/pages.py @@ -1,8 +1,10 @@ -"""Модуль для работы с Playwright в тестах pytest. +"""Модуль pages содержит фикстуры и функции для работы с Playwright. -Содержит фикстуры и вспомогательные функции для управления браузером. +Предоставляет инструменты для управления браузером, контекстами и страницами +в тестах pytest, включая настройку параметров запуска. """ + import pytest from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright, Playwright from _pytest.config.argparsing import Parser @@ -10,19 +12,20 @@ from _pytest.fixtures import FixtureRequest def pytest_addoption(parser: Parser): - """Добавляет пользовательские опции командной строки для настройки браузера. + """Добавляет опции командной строки для настройки браузера. Args: parser: Парсер pytest для добавления опций. - Доступные опции: - --bn: Выбор браузера (chrome, remote_chrome или firefox) + Опции: + --bn: Браузер (chrome, remote_chrome, firefox) --h: Режим headless (True/False) - --s: Размер окна в формате {'width': int, 'height': int} - --slow: Задержка между действиями (slow_mo) + --s: Размер окна {'width': int, 'height': int} + --slow: Задержка между действиями (мс) --t: Таймаут по умолчанию (мс) --l: Локаль браузера """ + parser.addoption('--bn', action='store', default="chrome", help="Choose browser: chrome, remote_chrome or firefox") parser.addoption('--h', action='store', default=False, @@ -48,7 +51,7 @@ def pytest_addoption(parser: Parser): @pytest.fixture(scope='class') def browser(request: FixtureRequest) -> Page: - """Фикстура для создания и управления экземпляром браузера. + """Фикстура для управления экземпляром браузера. Args: request: Объект запроса pytest для доступа к конфигурации. @@ -57,11 +60,12 @@ def browser(request: FixtureRequest) -> Page: Page: Экземпляр страницы браузера. Yields: - Page: Экземпляр страницы для использования в тестах. + Page: Страница для использования в тестах. Note: - Автоматически закрывает браузер и контексты после завершения тестов. + Автоматически закрывает браузер после тестов. """ + playwright = sync_playwright().start() # Выбор браузера на основе параметра командной строки @@ -96,11 +100,12 @@ def get_firefox_browser(playwright: Playwright, request: FixtureRequest) -> Brow Args: playwright: Экземпляр Playwright. - request: Объект запроса pytest для доступа к конфигурации. + request: Объект запроса pytest. Returns: - Browser: Экземпляр Firefox браузера. + Browser: Экземпляр Firefox. """ + return playwright.firefox.launch( headless=request.config.getoption("h"), slow_mo=request.config.getoption("slow"), @@ -112,11 +117,12 @@ def get_chrome_browser(playwright: Playwright, request: FixtureRequest) -> Brows Args: playwright: Экземпляр Playwright. - request: Объект запроса pytest для доступа к конфигурации. + request: Объект запроса pytest. Returns: - Browser: Экземпляр Chrome браузера. + Browser: Экземпляр Chrome. """ + return playwright.chromium.launch( headless=request.config.getoption("h"), slow_mo=request.config.getoption("slow"), @@ -125,15 +131,16 @@ def get_chrome_browser(playwright: Playwright, request: FixtureRequest) -> Brows def get_remote_chrome(playwright: Playwright, request: FixtureRequest) -> Browser: - """Создает и возвращает экземпляр Chrome браузера для удаленного запуска. + """Создает экземпляр Chrome для удаленного запуска. Args: playwright: Экземпляр Playwright. - request: Объект запроса pytest для доступа к конфигурации. + request: Объект запроса pytest. Returns: - Browser: Экземпляр Chrome браузера в режиме headless. + Browser: Экземпляр Chrome в headless режиме. """ + return playwright.chromium.launch( headless=True, slow_mo=request.config.getoption("slow") @@ -145,12 +152,13 @@ def get_context(browser: Browser, request: FixtureRequest, start: str) -> Browse Args: browser: Экземпляр браузера. - request: Объект запроса pytest для доступа к конфигурации. + request: Объект запроса pytest. start: Тип запуска ('local' или 'remote'). Returns: - BrowserContext: Настроенный контекст браузера. + BrowserContext: Настроенный контекст. """ + if start == 'local': context = browser.new_context( # no_viewport=True, @@ -179,9 +187,10 @@ def get_context(browser: Browser, request: FixtureRequest, start: str) -> Browse @pytest.fixture(scope="function") def return_back(browser: Page): - """Фикстура для возврата на предыдущую страницу в браузере. + """Фикстура для возврата на предыдущую страницу. Args: browser: Экземпляр страницы браузера. """ + browser.go_back() diff --git a/locators/button_locators.py b/locators/button_locators.py index f20d313..c2b6040 100644 --- a/locators/button_locators.py +++ b/locators/button_locators.py @@ -1,4 +1,18 @@ +"""Модуль button_locators содержит локаторы для кнопок и элементов интерфейса. + +Класс ButtonLocators хранит XPath и CSS локаторы для взаимодействия +с кнопками и всплывающими подсказками в тестах. +""" + class ButtonLocators: + """Класс для хранения локаторов кнопок и связанных элементов. + + Содержит локаторы в формате XPath и CSS для поиска элементов: + - Кнопка обновления лицензии + - Всплывающая подсказка + - Кнопка удаления сессии + """ + BUTTON_LICENSE_UPDATE = "//div[@class='scrollarea__footer']//button" TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" BUTTON_DELETE_SESSION = "button.v-btn--icon svg[fill='#4caf50']" diff --git a/locators/confirm_locators.py b/locators/confirm_locators.py index 7f6783e..75c0e2f 100644 --- a/locators/confirm_locators.py +++ b/locators/confirm_locators.py @@ -1,12 +1,19 @@ +"""Модуль confirm_locators содержит локаторы элементов диалогов подтверждения. + +Класс ConfirmLocators предоставляет XPath локаторы для взаимодействия +с диалоговыми окнами подтверждения действий в тестах. +""" + class ConfirmLocators: """Локаторы элементов диалогов подтверждения. - Атрибуты: - CONFIRM (str): XPath локатор активного диалогового окна. - TITLE (str): XPath локатор заголовка диалогового окна. - BUTTON_CLOSE (str): XPath локатор кнопки закрытия диалога. - TEXT (str): XPath локатор текстового содержимого диалога (формируется динамически). + Содержит XPath локаторы для: + CONFIRM (str): активного диалогового окна. + TITLE (str): заголовка диалогового окна. + BUTTON_CLOSE (str): кнопки закрытия диалога. + TEXT (str): текстового содержимого диалога (формируется динамически). """ + CONFIRM = "//div[contains(@class, 'v-dialog--active')]" TITLE = "//div[@class='v-card__title']/h3" BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']" diff --git a/locators/event_panel_locators.py b/locators/event_panel_locators.py index 4483d86..7ff31d2 100644 --- a/locators/event_panel_locators.py +++ b/locators/event_panel_locators.py @@ -1,8 +1,15 @@ +"""Модуль event_panel_locators содержит локаторы элементов панели событий. + +Класс EventPanelLocators предоставляет XPath локаторы для взаимодействия +с элементами панели событий в тестах. +""" + class EventPanelLocators: """Локаторы элементов панели событий. - Атрибуты: - BUTTONS_BLOCK (str): XPath локатор блока кнопок в панели инструментов. - Находится во втором блоке элементов toolbar'а внутри контентной области. + + Содержит XPath локаторы для: + BUTTONS_BLOCK (str): блока кнопок в панели инструментов """ + BUTTONS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]" diff --git a/locators/input_locators.py b/locators/input_locators.py index 658335c..d2d9e15 100644 --- a/locators/input_locators.py +++ b/locators/input_locators.py @@ -1,12 +1,19 @@ +"""Модуль input_locators содержит локаторы полей ввода на странице. + +Класс InputLocators предоставляет XPath локаторы для взаимодействия +с текстовыми полями ввода в тестах. +""" + class InputLocators: """Локаторы для полей ввода на странице. - Атрибуты: - LICENSE_ID_UPDATE (str): XPath локатор текстового поля для ввода/обновления - идентификатора лицензии, расположенного в подвале страницы. - Состоит из нескольких частей: - - Блок подвала (scrollarea__footer) - - Контейнер поля ввода (v-input__control) - - Непосредственно текстовое поле (textarea) + + Содержит XPath локаторы для: + LICENSE_ID_UPDATE (str): поля ввода идентификатора лицензии в подвале + + + + """ + LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea" diff --git a/locators/json_container_locators.py b/locators/json_container_locators.py index e4630b8..0f073bb 100644 --- a/locators/json_container_locators.py +++ b/locators/json_container_locators.py @@ -1,11 +1,16 @@ +"""Модуль json_container_locators содержит локаторы контейнеров JSON-данных. + +Класс JsonContainerLocators предоставляет XPath локаторы для работы +с контейнерами JSON-данных на странице. +""" + class JsonContainerLocators: """Локаторы для контейнеров JSON-данных на странице. - Атрибуты: - CONTAINER (str): XPath локатор основного контейнера JSON-данных. - Ищет div с классом, содержащим 'jv-container'. - SCROLL_CONTAINER (str): XPath локатор прокручиваемой области контейнера. - Ищет div с классом, содержащим 'scrollarea__body'. + Содержит XPath локаторы для: + CONTAINER (str): основного контейнера JSON-данных + SCROLL_CONTAINER (str): прокручиваемой области контейнера """ + CONTAINER = "//div[contains(@class,'jv-container')]" SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]" diff --git a/locators/modal_window_locators.py b/locators/modal_window_locators.py index 441da20..ddba607 100644 --- a/locators/modal_window_locators.py +++ b/locators/modal_window_locators.py @@ -1,16 +1,23 @@ +"""Модуль modal_window_locators содержит локаторы элементов модальных окон. + +Класс ModalWindowLocators предоставляет XPath локаторы для взаимодействия +с элементами модальных окон в тестах. +""" + class ModalWindowLocators: """Локаторы для элементов модальных окон. - Атрибуты: - MODAL_WINDOW (str): XPath локатор активного модального окна. - INPUT_FORM_USER_DATA (str): XPath локатор формы для ввода пользовательских данных. - TEXT_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath текстового поля ввода - внутри формы пользовательских данных. - ROLES_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath поля выбора ролей - внутри формы пользовательских данных. - ROLES_MENU_INPUT_FORM_USER_DATA (str): XPath локатор активного меню выбора ролей. - LABEL_INPUT_FORM_USER_DATA (str): XPath локатор метки поля ввода в форме. + Содержит XPath локаторы для: + MODAL_WINDOW (str): активного модального окна + INPUT_FORM_USER_DATA (str): формы ввода пользовательских данных + TEXT_FIELD_INPUT_FORM_USER_DATA (str): текстового поля ввода + + ROLES_FIELD_INPUT_FORM_USER_DATA (str): поля выбора ролей + + ROLES_MENU_INPUT_FORM_USER_DATA (str): меню выбора ролей + LABEL_INPUT_FORM_USER_DATA (str): метки поля ввода """ + MODAL_WINDOW = "//div[contains(@class, 'v-dialog--active')]" INPUT_FORM_USER_DATA = "//form[@class='v-form']" diff --git a/locators/navigation_panel_locators.py b/locators/navigation_panel_locators.py index 39956d2..a299cea 100644 --- a/locators/navigation_panel_locators.py +++ b/locators/navigation_panel_locators.py @@ -1,17 +1,19 @@ +"""Модуль navigation_panel_locators содержит локаторы навигационной панели. + +Класс NavigationPanelLocators предоставляет XPath локаторы для работы +с элементами навигационной панели в тестах. +""" + class NavigationPanelLocators: """Локаторы элементов навигационной панели. - Атрибуты: - PANEL_MAIN (str): XPath локатор основной панели навигации. - Ищет элемент ul с классом, содержащим 'v-expansion-panel'. - PANEL_SCROLL_CONTAINER (str): XPath локатор контейнера с прокруткой, - содержащего навигационную панель. Ищет div с классом 'scrollarea__body', - внутри которого находится панель навигации. - NODE_ROOT (str): XPath локатор корневого узла дерева навигации. - Ищет div с классом, содержащим 'v-treeview-node__root'. - NODE_CHILDREN (str): XPath локатор дочерних элементов узла дерева. - Ищет div с классом, содержащим 'v-treeview-node__children'. + Содержит XPath локаторы для: + PANEL_MAIN (str): основной панели навигации + PANEL_SCROLL_CONTAINER (str): контейнера с прокруткой + NODE_ROOT (str): корневого узла дерева + NODE_CHILDREN (str): дочерних элементов узла """ + PANEL_MAIN = "//ul[contains(@class, 'v-expansion-panel')]" PANEL_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//ul[contains(@class, 'v-expansion-panel')]]" diff --git a/locators/table_locators.py b/locators/table_locators.py index 01c27c7..1320232 100644 --- a/locators/table_locators.py +++ b/locators/table_locators.py @@ -1,13 +1,16 @@ +"""Модуль table_locators содержит локаторы табличных элементов. + +Класс TableLocators предоставляет XPath локаторы для работы +с таблицами в рабочей области приложения. +""" + class TableLocators: """Локаторы для табличных элементов в рабочей области. - Атрибуты: - TABLE_WORK_AREA (str): XPath локатор основной таблицы в рабочей области. - Ищет элемент table, находящийся по пути: - scrollarea__body -> div -> div -> div -> table - TABLE_SCROLL_CONTAINER (str): XPath локатор контейнера с прокруткой таблицы. - Ищет tbody внутри div с классом scrollarea__body, - содержащего таблицу с классом scrolltable__container. + Содержит XPath локаторы для: + TABLE_WORK_AREA (str): основной таблицы в рабочей области + TABLE_SCROLL_CONTAINER (str): контейнера с прокруткой таблицы """ + TABLE_WORK_AREA = "//div[@class='scrollarea__body']/div/div/div/table" TABLE_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//table[@class='scrolltable__container']]//tbody" diff --git a/locators/text_locators.py b/locators/text_locators.py index 28a3bd9..15d0d5d 100644 --- a/locators/text_locators.py +++ b/locators/text_locators.py @@ -1,11 +1,16 @@ +"""Модуль text_locators содержит локаторы текстовых элементов. + +Класс TextLocators предоставляет XPath локаторы для работы +с текстовыми элементами на страницах приложения. +""" + class TextLocators: """Локаторы для текстовых элементов на странице. - Атрибуты: - TITLE_LICENSE_INPUT_FORM (str): XPath локатор заголовка формы ввода лицензии. - Ищет span с классом 'title'. - LICENSE_ID (str): XPath локатор отображаемого идентификатора лицензии. - Ищет span с классами 'title' и 'text_select' (выделяемый текст). + Содержит XPath локаторы для: + TITLE_LICENSE_INPUT_FORM (str): заголовка формы ввода лицензии + LICENSE_ID (str): отображаемого ID лицензии (выделяемый текст) """ + TITLE_LICENSE_INPUT_FORM = "//span[@class='title']" LICENSE_ID = "//span[@class='title text_select']" diff --git a/locators/toolbar_locators.py b/locators/toolbar_locators.py index a16603d..1897fe8 100644 --- a/locators/toolbar_locators.py +++ b/locators/toolbar_locators.py @@ -1,15 +1,16 @@ -class ToolbarLocators: - """Локаторы элементов тулбара (панели инструментов). +"""Модуль toolbar_locators содержит локаторы элементов панели инструментов. - Атрибуты: - TITLE (str): XPath локатор заголовка тулбара. - Находится в навигационной панели (nav) внутри элемента с классом, - содержащим 'v-toolbar__title'. - - TOOLTIP (str): XPath локатор активного всплывающего подсказывающего элемента. - Ищет div с классами, содержащими: - - 'v-tooltip__content' (основа тултипа) - - 'menuable__content__active' (показанное состояние) +Класс ToolbarLocators предоставляет XPath локаторы для взаимодействия +с элементами тулбара и всплывающими подсказками. +""" + +class ToolbarLocators: + """Локаторы элементов панели инструментов (тулбара). + + Содержит XPath локаторы для: + TITLE (str): заголовка тулбара + TOOLTIP (str): активной всплывающей подсказки """ + TITLE = "//nav//div[contains(@class, 'v-toolbar__title')]" TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]" diff --git a/modal_windows/modal_add_user.py b/modal_windows/modal_add_user.py index 97c8b5d..0bbeecf 100644 --- a/modal_windows/modal_add_user.py +++ b/modal_windows/modal_add_user.py @@ -1,3 +1,9 @@ +"""Модуль modal_add_user содержит класс для работы с модальным окном добавления пользователя. + +Класс AddUserModalWindow наследует базовый функционал ModalWindowComponent +и реализует специфичные методы для работы с формами добавления пользователей. +""" + import re from playwright.sync_api import Page from tools.logger import get_logger @@ -15,20 +21,18 @@ logger = get_logger("ADD_USER_MODAL_WINDOW") class AddUserModalWindow(ModalWindowComponent): - """Класс модального окна добавления нового пользователя. + """Модальное окно добавления нового пользователя. - Наследует функциональность базового модального окна и добавляет специфичные элементы: - - Поля ввода данных пользователя - - Чекбоксы + Наследует ModalWindowComponent и добавляет элементы формы: + - Поля ввода (имя, пароль, email и др.) + - Чекбоксы (Active Directory, Push-уведомления) - Выпадающий список ролей - Кнопки действий - - Args: - page (Page): Экземпляр страницы Playwright """ def __init__(self, page: Page): - """Инициализация компонентов модального окна добавления пользователя.""" + """Инициализирует элементы формы добавления пользователя.""" + super().__init__(page) # Локаторы элементов формы @@ -117,19 +121,9 @@ class AddUserModalWindow(ModalWindowComponent): """Заполняет форму и добавляет нового пользователя. Args: - user_data (dict): Словарь с данными пользователя. Может содержать ключи: - - active_directory_checked (bool): Состояние чекбокса Active Directory - - name (str): Имя пользователя - - role (str): Роль пользователя - - password (str): Пароль пользователя - - commentary (str): Комментарий - - email (str): Email - - phone_number (str): Номер телефона - - push_notification_checked (bool): Состояние чекбокса Push-уведомлений - - Raises: - AssertionError: Если подтверждающее окно не отображается + user_data (dict): Данные пользователя (имя, роль, пароль и др.) """ + fields = user_data.keys() if "active_directory_checked" in fields: @@ -187,20 +181,19 @@ class AddUserModalWindow(ModalWindowComponent): self.new_user_confirm.click_allow_button() def close_window(self): - """Закрывает модальное окно с помощью кнопки 'Закрыть'.""" + """Закрывает модальное окно через кнопку 'Закрыть'.""" + 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): - """Проверяет наличие и корректность всех элементов модального окна. + """Проверяет наличие и корректность всех элементов формы.""" - Raises: - AssertionError: Если какой-либо элемент отсутствует или содержит некорректные данные - """ self.check_by_window_title() self.check_toolbar_button_presence("close") diff --git a/modal_windows/modal_edit_user.py b/modal_windows/modal_edit_user.py index 07ab645..549c743 100644 --- a/modal_windows/modal_edit_user.py +++ b/modal_windows/modal_edit_user.py @@ -1,3 +1,9 @@ +"""Модуль modal_edit_user содержит класс для работы с окном редактирования пользователя. + +Класс EditUserModalWindow наследует базовый функционал ModalWindowComponent +и реализует методы для редактирования данных пользователя. +""" + import re from playwright.sync_api import Page from tools.logger import get_logger @@ -13,21 +19,18 @@ logger = get_logger("EDIT_USER_MODAL_WINDOW") class EditUserModalWindow(ModalWindowComponent): - """Класс модального окна редактирования пользователя. + """Модальное окно редактирования пользователя. - Наследует функциональность базового модального окна и добавляет: - - Поля редактирования данных пользователя + Наследует ModalWindowComponent и добавляет: + - Поля редактирования данных - Чекбоксы настроек - Выпадающий список ролей - - Кнопки действий (Сохранить, Удалить, Сбросить пароль) - - Args: - page (Page): Экземпляр страницы Playwright - user_name (str): Имя редактируемого пользователя (используется в заголовке) + - Кнопки действий (Сохранить, Удалить и др.) """ def __init__(self, page: Page, user_name: str): - """Инициализация компонентов модального окна редактирования пользователя.""" + """Инициализирует элементы формы редактирования пользователя.""" + super().__init__(page) # Локаторы элементов формы @@ -104,20 +107,19 @@ class EditUserModalWindow(ModalWindowComponent): self.delete_user_confirm = ConfirmComponent(page, " Отмена ", " Удалить ") def close_window(self): - """Закрывает модальное окно с помощью кнопки 'Закрыть'.""" + """Закрывает окно через кнопку 'Закрыть'.""" + close_button = self.get_button_by_name("close") close_button.click() def close_window_by_toolbar_button(self): - """Закрывает модальное окно с помощью кнопки закрытия в тулбаре.""" + """Закрывает окно через кнопку в тулбаре.""" + self.click_toolbar_close_button() def delete_user(self): - """Удаляет пользователя с подтверждением действия. + """Удаляет пользователя с подтверждением.""" - Raises: - AssertionError: Если окно подтверждения не отображается - """ delete_button = self.get_button_by_name("delete") delete_button.click() @@ -132,14 +134,9 @@ class EditUserModalWindow(ModalWindowComponent): """Редактирует данные пользователя. Args: - user_data (dict): Словарь с обновляемыми данными пользователя. Может содержать: - - name (str): Имя пользователя - - role (str): Роль пользователя - - commentary (str): Комментарий - - email (str): Email - - phone_number (str): Номер телефона - - push_notification_checked (bool): Состояние чекбокса уведомлений + user_data (dict): Данные для обновления (имя, роль и др.) """ + fields = user_data.keys() if "name" in fields: @@ -185,19 +182,18 @@ class EditUserModalWindow(ModalWindowComponent): def reset_password(self): """Инициирует сброс пароля пользователя.""" + reset_password_button = self.get_button_by_name("reset_password") reset_password_button.click() def check_content(self, user_name, role): - """Проверяет наличие и корректность всех элементов окна. + """Проверяет наличие и корректность элементов окна. Args: user_name (str): Ожидаемое имя пользователя role (str): Ожидаемая роль пользователя - - Raises: - AssertionError: Если какой-либо элемент отсутствует или содержит некорректные данные """ + self.check_by_window_title() self.check_toolbar_button_presence("close") self.check_toolbar_button_tooltip("close", "Закрыть") diff --git a/pages/base_page.py b/pages/base_page.py index f64b8c1..017b370 100644 --- a/pages/base_page.py +++ b/pages/base_page.py @@ -1,4 +1,8 @@ -"""Базовый класс страницы для работы с Playwright.""" +"""Модуль base_page содержит базовый класс для работы со страницами. + +Класс BasePage предоставляет общие методы для взаимодействия +со страницами через Playwright и выполнения API-запросов. +""" import json from typing import Any, Dict, List, Optional @@ -10,28 +14,56 @@ logger = get_logger("BASE_PAGE") class BasePage: - """Базовый класс для работы со страницами через Playwright.""" + """Базовый класс для работы со страницами через Playwright. + + Содержит общие методы для: + - Навигации по страницам + - Выполнения API-запросов + - Проверок состояния страницы + """ def __init__(self, page: Page) -> None: + """Инициализирует базовую страницу. + + Args: + page: Экземпляр страницы Playwright + """ + self.page = page # Действия: def current_url(self) -> str: + """Возвращает текущий URL страницы.""" + return self.page.url def open(self, uri: str) -> Optional[Response]: + """Открывает указанный URI на базовом URL.""" + return self.page.goto(f"{host.get_base_url()}{uri}", wait_until='domcontentloaded') def page_reload(self) -> None: + """Перезагружает текущую страницу.""" + self.page.reload() def wait_for_timeout(self, timeout: int) -> None: + """Ожидает указанное количество миллисекунд.""" + self.page.wait_for_timeout(timeout) def get_api_request_context(self) -> APIRequestContext: + """Возвращает контекст для выполнения API-запросов.""" + return self.page.context.request def send_get_api_request(self, uri: str) -> Response: + """Отправляет GET-запрос к API. + + Args: + uri: URI для запроса + """ + api_request_context = self.get_api_request_context() token = host.get_access_token() headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"} @@ -42,6 +74,13 @@ class BasePage: return response def send_post_api_request(self, uri: str, payload: Dict[str, Any]) -> Response: + """Отправляет POST-запрос к API. + + Args: + uri: URI для запроса + payload: Тело запроса + """ + api_request_context = self.get_api_request_context() token = host.get_access_token() headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"} @@ -53,6 +92,12 @@ class BasePage: return response def get_response_body(self, response: Response) -> Optional[Dict[str, Any]]: + """Возвращает тело ответа в формате JSON. + + Args: + response: Объект ответа + """ + try: response_body = response.json() except json.JSONDecodeError: @@ -62,15 +107,38 @@ class BasePage: # Проверки: def check_URL(self, uri: str, msg: str) -> None: + """Проверяет соответствие текущего URL ожидаемому. + + Args: + uri: Ожидаемый URI + msg: Сообщение об ошибке + """ + expect(self.page).to_have_url( f"{host.get_base_url()}{uri}", timeout=60000 ), msg def check_equals(self, actual: Any, expected: Any, msg: str) -> None: + """Проверяет равенство фактического и ожидаемого значений. + + Args: + actual: Фактическое значение + expected: Ожидаемое значение + msg: Сообщение об ошибке + """ + assert actual == expected, msg def check_lists_equals(self, actual: List[Any], expected: List[Any], msg: str) -> None: + """Рекурсивно проверяет равенство двух списков. + + Args: + actual: Фактический список + expected: Ожидаемый список + msg: Сообщение об ошибке + """ + def compare_lists(list1: List[Any], list2: List[Any]) -> bool: if len(list1) != len(list2): return False diff --git a/pages/license_tab.py b/pages/license_tab.py index 0d21a06..739bb6a 100644 --- a/pages/license_tab.py +++ b/pages/license_tab.py @@ -1,3 +1,9 @@ +"""Модуль license_tab содержит класс для работы с вкладкой 'Лицензии'. + +Класс LicenseTab наследует BasePage и реализует методы для взаимодействия +с элементами вкладки лицензий и проверки их состояния. +""" + from playwright.sync_api import Page from locators.text_locators import TextLocators from locators.input_locators import InputLocators @@ -14,23 +20,19 @@ from pages.base_page import BasePage class LicenseTab(BasePage): """Класс для работы с вкладкой 'Лицензии'. - Атрибуты: - page (Page): Экземпляр страницы Playwright. - toolbar (ToolbarComponent): Компонент панели инструментов. - json_container (JsonContainerComponent): Компонент контейнера с JSON-данными. - input_form_title (Text): Заголовок формы ввода. - license_id (Text): Текстовый элемент с идентификатором лицензии. - license_id_input (TextInput): Поле ввода идентификатора лицензии. - update_button (Button): Кнопка обновления лицензии. - error_alert (AlertComponent): Компонент алерта с ошибкой. + Содержит методы для: + - Взаимодействия с формой ввода лицензии + - Проверки содержимого JSON-контейнера + - Работы с элементами управления """ def __init__(self, page: Page) -> None: """Инициализирует элементы вкладки 'Лицензии'. Args: - page: Экземпляр страницы Playwright. + page: Экземпляр страницы Playwright """ + super().__init__(page) self.toolbar = ToolbarComponent(page, "Лицензии") @@ -45,37 +47,42 @@ class LicenseTab(BasePage): # Действия: def fill_license_input_form(self, value: str) -> None: - """Заполняет форму ввода идентификатора лицензии и нажимает кнопку обновления. + """Заполняет форму ввода лицензии указанным значением. Args: - value: Значение для ввода в поле идентификатора лицензии. + value: Значение для ввода """ + self.license_id_input.clear() self.license_id_input.input_value(value) self.update_button.click() def scroll_json_container_up(self) -> None: """Прокручивает JSON-контейнер вверх.""" + loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first self.json_container.scroll_up(loc) def scroll_json_container_down(self) -> None: """Прокручивает JSON-контейнер вниз.""" + loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first self.json_container.scroll_down(loc) # Проверки: def check_json_container_verticall_scrolling(self) -> bool: - """Проверяет возможность вертикальной прокрутки JSON-контейнера. + """Проверяет возможность вертикальной прокрутки контейнера. Returns: - bool: True если контейнер можно прокручивать, иначе False. + bool: Доступность прокрутки """ + loc = self.page.locator(JsonContainerLocators.SCROLL_CONTAINER).first return self.json_container.is_scrollable_vertically(loc) def check_content(self) -> None: - """Проверяет наличие всех основных элементов на вкладке.""" + """Проверяет наличие всех основных элементов вкладки.""" + self.should_be_toolbar() self.should_be_json_container() self.should_be_input_form_title() @@ -83,27 +90,31 @@ class LicenseTab(BasePage): self.should_be_update_button() def should_be_error_alert_window_with_text(self, text: str) -> None: - """Проверяет наличие и отсутствие алерта с указанным текстом. + """Проверяет наличие/отсутствие алерта с указанным текстом. Args: - text: Текст для проверки в алерте. + text: Текст для проверки """ + self.error_alert.check_alert_presence(text) self.error_alert.check_alert_absence(text) def should_be_toolbar(self) -> None: """Проверяет наличие панели инструментов.""" + self.toolbar.check_toolbar_presence("Toolbar is missing") def should_be_json_container(self) -> None: - """Проверяет наличие JSON-контейнера с информацией о лицензии.""" + """Проверяет наличие JSON-контейнера.""" + self.json_container.check_presence( JsonContainerLocators.CONTAINER, "Json container with license info is missing" ) def should_be_input_form_title(self) -> None: - """Проверяет заголовок формы ввода и соответствие ID лицензии.""" + """Проверяет заголовок формы и соответствие ID лицензии.""" + self.input_form_title.check_have_text( "Идентификатор:", "Input lisence id form title 'Идентификатор:' is missing" @@ -122,11 +133,13 @@ class LicenseTab(BasePage): ) def should_be_empty_input_form(self) -> None: - """Проверяет, что форма ввода идентификатора лицензии пуста.""" + """Проверяет пустоту формы ввода лицензии.""" + self.license_id_input.check_empty_input("Input lisence id form is missing or not empty") def should_be_update_button(self) -> None: - """Проверяет наличие кнопки обновления лицензии с правильным текстом.""" + """Проверяет наличие кнопки обновления лицензии.""" + button_text = "Обновить лицензию" self.update_button.check_have_text( button_text, @@ -134,7 +147,8 @@ class LicenseTab(BasePage): ) def verify_json_container_content(self) -> None: - """Проверяет соответствие содержимого JSON-контейнера данным из API.""" + """Проверяет соответствие данных контейнера данным из API.""" + actual_data = self.json_container.read_data(JsonContainerLocators.CONTAINER) # send request to backend to get license info diff --git a/pages/login_page.py b/pages/login_page.py index 536a770..81b9622 100644 --- a/pages/login_page.py +++ b/pages/login_page.py @@ -1,3 +1,9 @@ +"""Модуль страницы авторизации. + +Содержит класс LoginPage для работы с элементами страницы входа в систему. +Использует Playwright для взаимодействия с UI и обработки ответов сервера. +""" + from playwright.sync_api import Page from elements.text_input_element import TextInput from elements.button_element import Button @@ -10,11 +16,11 @@ class LoginPage(BasePage): """Класс для работы со страницей авторизации. Атрибуты: - page (Page): Экземпляр страницы Playwright. - login_input (TextInput): Поле ввода логина. - password_input (TextInput): Поле ввода пароля. - login_button (Button): Кнопка входа. - error_alert (AlertComponent): Компонент алерта с ошибкой. + page: Экземпляр страницы Playwright. + login_input: Поле ввода логина. + password_input: Поле ввода пароля. + login_button: Кнопка входа. + error_alert: Компонент алерта с ошибкой. """ def __init__(self, page: Page) -> None: @@ -23,6 +29,7 @@ class LoginPage(BasePage): Args: page: Экземпляр страницы Playwright. """ + super().__init__(page) self.login_input = TextInput(page, page.get_by_label("Имя пользователя"), "login input") @@ -34,16 +41,17 @@ class LoginPage(BasePage): def do_login(self, username: str = None, password: str = None) -> None: """Выполняет вход в систему. - Если username/password не указаны, использует значения из Constants. + Использует переданные учетные данные или значения из Constants. Обрабатывает ответ сервера для получения токена доступа. Args: - username: Логин пользователя. Если None, используется значение из Constants. - password: Пароль пользователя. Если None, используется значение из Constants. + username: Логин пользователя. По умолчанию из Constants. + password: Пароль пользователя. По умолчанию из Constants. Raises: AssertionError: Если после входа открылась неожиданная страница. """ + def handle_response(response): if "login" in response.url: response_body = self.get_response_body(response) @@ -72,13 +80,14 @@ class LoginPage(BasePage): def do_unsuccessful_login(self, username: str = "someuser", password: str = "password") -> None: """Выполняет попытку входа с неверными учетными данными. - Можно передать свои неверные данные или использовать значения по умолчанию. - Проверяет наличие сообщения об ошибке. + Проверяет наличие сообщения об ошибке. Можно передать свои данные + или использовать значения по умолчанию. Args: - username: Неверный логин пользователя. По умолчанию "someuser". - password: Неверный пароль пользователя. По умолчанию "password". + username: Неверный логин. По умолчанию "someuser". + password: Неверный пароль. По умолчанию "password". """ + self.open("") self.login_input.clear() diff --git a/pages/main_page.py b/pages/main_page.py index ec5ae82..eda1406 100644 --- a/pages/main_page.py +++ b/pages/main_page.py @@ -1,3 +1,9 @@ +"""Модуль главной страницы приложения. + +Содержит класс MainPage для работы с элементами главной страницы. +Включает взаимодействие с панелью навигации, кнопками и карточкой пользователя. +""" + from playwright.sync_api import Page from locators.navigation_panel_locators import NavigationPanelLocators from locators.event_panel_locators import EventPanelLocators @@ -10,10 +16,10 @@ class MainPage(BasePage): """Класс для работы с главной страницей приложения. Атрибуты: - page (Page): Экземпляр страницы Playwright. - navigation_panel (NavigationPanelComponent): Компонент панели навигации. - user_button (Button): Кнопка пользователя. - user_card (CardComponent): Карточка пользователя. + page: Экземпляр страницы Playwright. + navigation_panel: Компонент панели навигации. + user_button: Кнопка пользователя. + user_card: Карточка пользователя. """ def __init__(self, page: Page) -> None: @@ -22,6 +28,7 @@ class MainPage(BasePage): Args: page: Экземпляр страницы Playwright. """ + super().__init__(page) self.navigation_panel = NavigationPanelComponent(page) @@ -39,45 +46,53 @@ class MainPage(BasePage): Args: item_name: Название элемента для клика. """ + self.navigation_panel.click_item(NavigationPanelLocators.PANEL_MAIN, item_name) def click_configuration_navigation_panel_item(self, item_name: str) -> None: - """Кликает по элементу подраздела 'Конфигурация' в панели навигации. + """Кликает по элементу подраздела 'Конфигурация'. Args: item_name: Название элемента для клика. """ + self.navigation_panel.click_sub_item(NavigationPanelLocators.PANEL_MAIN, 1, item_name) def click_maintenance_navigation_panel_item(self, item_name: str) -> None: - """Кликает по элементу подраздела 'Обслуживание' в панели навигации. + """Кликает по элементу подраздела 'Обслуживание'. Args: item_name: Название элемента для клика. """ + self.navigation_panel.click_sub_item(NavigationPanelLocators.PANEL_MAIN, 2, item_name) def click_user_button(self) -> None: """Кликает по кнопке пользователя.""" + self.user_button.click() def do_logout(self) -> None: """Выполняет выход из системы.""" + self.should_be_user_button() self.click_user_button() self.user_card.click_logout_button() def scroll_navigation_panel_up(self) -> None: """Прокручивает панель навигации вверх.""" + self.navigation_panel.scroll_up(NavigationPanelLocators.PANEL_SCROLL_CONTAINER) def scroll_navigation_panel_down(self) -> None: """Прокручивает панель навигации вниз.""" + self.navigation_panel.scroll_down(NavigationPanelLocators.PANEL_SCROLL_CONTAINER) # Проверки: def should_be_navigation_panel(self) -> None: """Проверяет наличие панели навигации.""" + self.navigation_panel.check_presence( NavigationPanelLocators.PANEL_MAIN, "Navigation panel is missing" @@ -85,14 +100,16 @@ class MainPage(BasePage): def should_be_user_button(self) -> None: """Проверяет наличие кнопки пользователя.""" + self.user_button.check_presence("User button is missing on event panel") def check_navigation_panel_verticall_scrolling(self) -> bool: - """Проверяет возможность вертикальной прокрутки панели навигации. + """Проверяет возможность вертикальной прокрутки панели. Returns: - bool: True если панель можно прокручивать, иначе False. + bool: True если прокрутка возможна, иначе False. """ + return self.navigation_panel.is_scrollable_vertically( NavigationPanelLocators.PANEL_SCROLL_CONTAINER ) @@ -103,6 +120,7 @@ class MainPage(BasePage): Args: item_name: Название элемента для проверки. """ + self.navigation_panel.check_item_visibility( NavigationPanelLocators.PANEL_MAIN, item_name diff --git a/pages/service_status_tab.py b/pages/service_status_tab.py index 08650dc..bcf9bca 100644 --- a/pages/service_status_tab.py +++ b/pages/service_status_tab.py @@ -1,3 +1,9 @@ +"""Модуль вкладки 'Статус обслуживания'. + +Содержит класс ServiceStatusTab для работы с таблицей сервисов. +Позволяет проверять состояние и взаимодействовать с элементами вкладки. +""" + from playwright.sync_api import Page from locators.table_locators import TableLocators from components.toolbar_component import ToolbarComponent @@ -8,21 +14,23 @@ from pages.base_page import BasePage class ServiceStatusTab(BasePage): """Класс для работы с вкладкой 'Статус обслуживания'. - Предоставляет методы для взаимодействия с таблицей сервисов и проверки её состояния. + Предоставляет методы для взаимодействия с таблицей сервисов и проверки + её состояния. Args: - page (Page): Экземпляр страницы Playwright. + page: Экземпляр страницы Playwright. """ def __init__(self, page: Page) -> None: - """Инициализация компонентов вкладки 'Статус обслуживания'.""" + """Инициализирует компоненты вкладки 'Статус обслуживания'.""" + super().__init__(page) self.toolbar = ToolbarComponent(page, "Статус обслуживания") self.services_table = TableComponent(page) def get_rows_count(self) -> int: - """Возвращает количество строк в таблице сервисов (без учёта заголовка). + """Возвращает количество строк в таблице (без заголовка). Returns: int: Количество строк с данными. @@ -30,6 +38,7 @@ class ServiceStatusTab(BasePage): Raises: AssertionError: Если таблица пуста. """ + table_content = self.services_table.read(TableLocators.TABLE_WORK_AREA) rows_count = len(table_content) @@ -40,23 +49,26 @@ class ServiceStatusTab(BasePage): def scroll_services_table_up(self) -> None: """Прокручивает таблицу сервисов вверх.""" + self.services_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER) def scroll_services_table_down(self) -> None: """Прокручивает таблицу сервисов вниз.""" + self.services_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER) def check_services_table_content(self) -> None: """Проверяет содержимое таблицы сервисов. - Проверяет: - - Наличие заголовков таблицы - - Соответствие заголовков ожидаемым значениям - - Наличие хотя бы одной строки с данными + Проверяет заголовки и наличие данных в таблице. + + + Raises: - AssertionError: Если таблица пуста или заголовки не соответствуют ожидаемым. + AssertionError: Если таблица пуста или заголовки неверны. """ + expected_headers = [ 'Контейнер', 'Время создания', @@ -88,6 +100,7 @@ class ServiceStatusTab(BasePage): Returns: bool: True если прокрутка возможна, иначе False. """ + return self.services_table.is_scrollable_vertically( TableLocators.TABLE_SCROLL_CONTAINER ) @@ -96,27 +109,30 @@ class ServiceStatusTab(BasePage): """Проверяет видимость первой строки таблицы. Raises: - AssertionError: Если первая строка не видна. + AssertionError: Если строка не видна. """ + self.services_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA) def check_services_table_last_row_visibility(self) -> None: """Проверяет видимость последней строки таблицы. Raises: - AssertionError: Если последняя строка не видна. + AssertionError: Если строка не видна. """ + self.services_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA) def check_services_table_row_highlighting(self, row_index: int) -> None: """Проверяет выделение указанной строки таблицы. Args: - row_index (int): Индекс проверяемой строки. + row_index: Индекс проверяемой строки. Raises: AssertionError: Если строка не выделена. """ + self.services_table.check_row_highlighting( TableLocators.TABLE_WORK_AREA, row_index @@ -128,6 +144,7 @@ class ServiceStatusTab(BasePage): Raises: AssertionError: Если тулбар отсутствует. """ + self.toolbar.check_toolbar_presence("Toolbar is missing") def should_be_services_table(self) -> None: @@ -136,6 +153,7 @@ class ServiceStatusTab(BasePage): Raises: AssertionError: Если таблица отсутствует. """ + self.services_table.check_presence( TableLocators.TABLE_WORK_AREA, "Service statuses table is missing" diff --git a/pages/session_tab.py b/pages/session_tab.py index c10e9e2..85e7794 100644 --- a/pages/session_tab.py +++ b/pages/session_tab.py @@ -1,3 +1,9 @@ +"""Модуль вкладки 'Сессия'. + +Содержит класс SessionsTab для работы с таблицей сессий. +Позволяет проверять состояние и взаимодействовать с элементами вкладки. +""" + from playwright.sync_api import Page, Locator from locators.table_locators import TableLocators from locators.button_locators import ButtonLocators @@ -10,21 +16,23 @@ from pages.base_page import BasePage class SessionsTab(BasePage): """Класс для работы с вкладкой 'Сессия'. - Предоставляет методы для взаимодействия с таблицей сессий и проверки её состояния. + Предоставляет методы для взаимодействия с таблицей сессий и проверки + её состояния. Args: - page (Page): Экземпляр страницы Playwright. + page: Экземпляр страницы Playwright. """ def __init__(self, page: Page) -> None: - """Инициализация компонентов вкладки 'Сессия'.""" + """Инициализирует компоненты вкладки 'Сессия'.""" + super().__init__(page) self.toolbar = ToolbarComponent(page, "Сессия") self.sessions_table = TableComponent(page) def get_rows_count(self) -> int: - """Возвращает количество строк в таблице сессий (без учёта заголовка). + """Возвращает количество строк в таблице (без заголовка). Returns: int: Количество строк с данными. @@ -32,6 +40,7 @@ class SessionsTab(BasePage): Raises: AssertionError: Если таблица пуста. """ + table_content = self.sessions_table.read(TableLocators.TABLE_WORK_AREA) rows_count = len(table_content) @@ -44,14 +53,15 @@ class SessionsTab(BasePage): """Возвращает кнопку удаления сессии для указанной строки. Args: - row_index (int): Индекс строки в таблице + row_index: Индекс строки в таблице. Returns: - TooltipButton: Экземпляр кнопки с подсказкой + TooltipButton: Кнопка с подсказкой. Raises: AssertionError: Если строка не найдена. """ + row_locator = self.sessions_table.get_row_locator( TableLocators.TABLE_WORK_AREA, row_index @@ -63,21 +73,24 @@ class SessionsTab(BasePage): def scroll_sessions_table_up(self) -> None: """Прокручивает таблицу сессий вверх.""" + self.sessions_table.scroll_up(TableLocators.TABLE_SCROLL_CONTAINER) def scroll_sessions_table_down(self) -> None: """Прокручивает таблицу сессий вниз.""" + self.sessions_table.scroll_down(TableLocators.TABLE_SCROLL_CONTAINER) def check_sessions_table_content(self, verify: bool = False) -> None: """Проверяет содержимое таблицы сессий. Args: - verify (bool, optional): Проверять соответствие данных из БД. По умолчанию False. + verify: Проверять соответствие данных из БД. По умолчанию False. Raises: - AssertionError: Если таблица пуста или заголовки не соответствуют. + AssertionError: Если таблица пуста или заголовки неверны. """ + expected_headers = [ 'ID сессии', 'ID пользователя', @@ -115,6 +128,7 @@ class SessionsTab(BasePage): Returns: bool: True если прокрутка возможна, иначе False. """ + return self.sessions_table.is_scrollable_vertically( TableLocators.TABLE_SCROLL_CONTAINER ) @@ -123,27 +137,30 @@ class SessionsTab(BasePage): """Проверяет видимость первой строки таблицы. Raises: - AssertionError: Если первая строка не видна. + AssertionError: Если строка не видна. """ + self.sessions_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA) def check_sessions_table_last_row_visibility(self) -> None: """Проверяет видимость последней строки таблицы. Raises: - AssertionError: Если последняя строка не видна. + AssertionError: Если строка не видна. """ + self.sessions_table.check_last_row_visibility(TableLocators.TABLE_WORK_AREA) def check_sessions_table_row_highlighting(self, row_index: int) -> None: """Проверяет выделение указанной строки таблицы. Args: - row_index (int): Индекс проверяемой строки. + row_index: Индекс проверяемой строки. Raises: AssertionError: Если строка не выделена. """ + self.sessions_table.check_row_highlighting( TableLocators.TABLE_WORK_AREA, row_index @@ -155,6 +172,7 @@ class SessionsTab(BasePage): Raises: AssertionError: Если тулбар отсутствует. """ + self.toolbar.check_toolbar_presence("Toolbar is missing") def should_be_sessions_table(self) -> None: @@ -163,6 +181,7 @@ class SessionsTab(BasePage): Raises: AssertionError: Если таблица отсутствует. """ + self.sessions_table.check_presence( TableLocators.TABLE_WORK_AREA, "Sessions table is missing" @@ -176,12 +195,13 @@ class SessionsTab(BasePage): """Проверяет наличие кнопки удаления в строке таблицы. Args: - row_index (int): Индекс проверяемой строки - tooltip (str): Ожидаемый текст подсказки + row_index: Индекс проверяемой строки. + tooltip: Ожидаемый текст подсказки. Raises: - AssertionError: Если кнопка отсутствует или подсказка не соответствует. + AssertionError: Если кнопка отсутствует или подсказка неверна. """ + delete_button = self.get_delete_session_button_from_row(row_index) delete_button.check_presence( f"Delete session button is missing on {row_index} row" @@ -192,11 +212,12 @@ class SessionsTab(BasePage): """Сверяет данные таблицы с данными из БД. Args: - sessions_table (list): Данные из таблицы на странице + sessions_table: Данные из таблицы на странице. Raises: AssertionError: Если данные не соответствуют. """ + expected_sessions_list = [] # Отправка запроса к бэкенду для получения информации о сессиях diff --git a/pages/users_tab.py b/pages/users_tab.py index 7d688da..2fc0dae 100644 --- a/pages/users_tab.py +++ b/pages/users_tab.py @@ -1,3 +1,9 @@ +"""Модуль вкладки 'Пользователи'. + +Содержит класс UsersTab для работы с таблицей пользователей. +Позволяет управлять пользователями через модальные окна и проверять их состояние. +""" + import re from playwright.sync_api import Page from modal_windows.modal_edit_user import EditUserModalWindow @@ -13,14 +19,15 @@ class UsersTab(BasePage): """Класс для работы с вкладкой 'Пользователи'. Предоставляет методы для взаимодействия с таблицей пользователей, - модальными окнами добавления/редактирования и проверки состояния элементов. + модальными окнами и проверки состояния элементов. Args: - page (Page): Экземпляр страницы Playwright. + page: Экземпляр страницы Playwright. """ def __init__(self, page: Page) -> None: - """Инициализация компонентов вкладки 'Пользователи'.""" + """Инициализирует компоненты вкладки 'Пользователи'.""" + super().__init__(page) locator_button_1 = self.page.get_by_role("navigation").filter( @@ -40,15 +47,16 @@ class UsersTab(BasePage): self.success_alert = AlertComponent(page, "success") def add_modal_window(self, window_type: str, title: str) -> None: - """Добавляет модальное окно в коллекцию окон. + """Добавляет модальное окно в коллекцию. Args: - window_type (str): Тип окна ('add_user' или 'edit_user') - title (str): Заголовок окна (имя пользователя для редактирования) + window_type: Тип окна ('add_user' или 'edit_user'). + title: Заголовок окна. Raises: - AssertionError: Если указан неподдерживаемый тип окна. + AssertionError: Если тип окна не поддерживается. """ + if window_type == "add_user": self.modal_windows["add_user"] = AddUserModalWindow(self.page) elif window_type == "edit_user": @@ -60,14 +68,15 @@ class UsersTab(BasePage): """Возвращает модальное окно по заголовку. Args: - title (str): Заголовок окна + title: Заголовок окна. Returns: - ModalWindowComponent: Экземпляр модального окна + 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" @@ -77,11 +86,12 @@ class UsersTab(BasePage): """Удаляет модальное окно из коллекции. Args: - title (str): Заголовок окна + 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 @@ -90,8 +100,9 @@ class UsersTab(BasePage): """Закрывает модальное окно через кнопку в тулбаре. Args: - title (str): Заголовок окна + title: Заголовок окна. """ + modal_window = self.get_modal_window(title) modal_window.close_window_by_toolbar_button() self.delete_modal_window(title) @@ -100,45 +111,51 @@ class UsersTab(BasePage): """Закрывает модальное окно через кнопку закрытия. Args: - title (str): Заголовок окна + title: Заголовок окна. """ + modal_window = self.get_modal_window(title) modal_window.close_window() self.delete_modal_window(title) def close_add_user_window_by_toolbar_button(self) -> None: - """Закрывает окно добавления пользователя через кнопку в тулбаре.""" + """Закрывает окно добавления пользователя через тулбар.""" + self.close_modal_window_by_toolbar_button("add_user") def close_add_user_window(self) -> None: """Закрывает окно добавления пользователя.""" + self.close_modal_window("add_user") def close_edit_user_window_by_toolbar_button(self, title: str) -> None: - """Закрывает окно редактирования пользователя через кнопку в тулбаре. + """Закрывает окно редактирования через кнопку в тулбаре. Args: - title (str): Имя пользователя (заголовок окна) + title: Имя пользователя (заголовок окна). """ + self.close_modal_window_by_toolbar_button(title) def close_edit_user_window(self, title: str) -> None: """Закрывает окно редактирования пользователя. Args: - title (str): Имя пользователя (заголовок окна) + title: Имя пользователя (заголовок окна). """ + self.close_modal_window(title) def add_new_user(self, user_data: dict) -> None: """Добавляет нового пользователя. Args: - user_data (dict): Данные пользователя + user_data: Данные пользователя. Raises: - AssertionError: Если не отображается сообщение об успешном добавлении. + AssertionError: Если нет сообщения об успешном добавлении. """ + self.get_modal_window("add_user").new_user(user_data) self.success_alert.check_alert_presence(' Новый пользователь \n успешно добавлен! ') self.success_alert.check_alert_absence(' Новый пользователь \n успешно добавлен! ') @@ -147,11 +164,12 @@ class UsersTab(BasePage): """Удаляет пользователя. Args: - user_name (str): Имя пользователя + user_name: Имя пользователя. Raises: - AssertionError: Если не отображается сообщение об успешном удалении. + AssertionError: Если нет сообщения об успешном удалении. """ + self.get_modal_window(user_name).delete_user() self.success_alert.check_alert_presence('\nПользователь удалён\n') self.success_alert.check_alert_absence('\nПользователь удалён\n') @@ -160,12 +178,13 @@ class UsersTab(BasePage): """Редактирует данные пользователя. Args: - user_name (str): Имя пользователя - user_data (dict): Новые данные пользователя + user_name: Имя пользователя. + user_data: Новые данные пользователя. Raises: - AssertionError: Если не отображается сообщение об успешном обновлении. + AssertionError: Если нет сообщения об успешном обновлении. """ + self.get_modal_window(user_name).edit_user(user_data) self.success_alert.check_alert_presence('\nОбновление успешно\n') self.success_alert.check_alert_absence('\nОбновление успешно\n') @@ -174,11 +193,12 @@ class UsersTab(BasePage): """Сбрасывает пароль пользователя. Args: - user_name (str): Имя пользователя + user_name: Имя пользователя. Returns: - str: Новый пароль (если получен) + str: Новый пароль (если получен). """ + new_password = "" self.get_modal_window(user_name).reset_password() @@ -193,15 +213,16 @@ class UsersTab(BasePage): """Ищет пользователя в таблице. Args: - name (str): Имя пользователя - role (str): Роль пользователя + name: Имя пользователя. + role: Роль пользователя. Returns: - int: Индекс строки или -1 если не найден + int: Индекс строки или -1 если не найден. Raises: AssertionError: Если таблица пуста. """ + table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) if len(table_content) == 0: assert False, "The contents of the table are missing" @@ -219,6 +240,7 @@ class UsersTab(BasePage): Raises: AssertionError: Если кнопки недоступны или окно не открылось. """ + if self.toolbar.is_button_not_present("close"): self.toolbar.check_button_presence("edit") self.toolbar.click_button("edit") @@ -232,14 +254,15 @@ class UsersTab(BasePage): """Открывает окно редактирования по индексу строки. Args: - row_index (int): Индекс строки в таблице + row_index: Индекс строки в таблице. Returns: - tuple: (имя пользователя, роль) + tuple: (имя пользователя, роль). Raises: AssertionError: Если таблица пуста или индекс вне диапазона. """ + tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"} table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) @@ -265,15 +288,16 @@ class UsersTab(BasePage): return user_name, role def open_edit_user_page_by_user(self, user_name: str, role: str) -> None: - """Открывает окно редактирования по имени пользователя и роли. + """Открывает окно редактирования по имени и роли. Args: - user_name (str): Имя пользователя - role (str): Роль пользователя + user_name: Имя пользователя. + role: Роль пользователя. Raises: AssertionError: Если пользователь не найден. """ + row_index = self.find_user_in_table(user_name, role) if row_index == -1: assert False, f"User with name {user_name} and role {role} has not been found" @@ -286,11 +310,12 @@ class UsersTab(BasePage): """Проверяет содержимое таблицы пользователей. Args: - verify (bool, optional): Проверять соответствие данных из БД. По умолчанию False. + verify: Проверять соответствие данных из БД. По умолчанию False. Raises: - AssertionError: Если таблица пуста или заголовки не соответствуют. + AssertionError: Если таблица пуста или заголовки неверны. """ + expected_headers = ['Имя пользователя', 'Тип авторизации', 'Роль', 'E-mail', 'Номер для СМС'] table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA) @@ -312,15 +337,17 @@ class UsersTab(BasePage): def check_add_user_window_content(self) -> None: """Проверяет содержимое окна добавления пользователя.""" + self.get_modal_window("add_user").check_content() def check_edit_user_window_content(self, user_name: str, role: str) -> None: - """Проверяет содержимое окна редактирования пользователя. + """Проверяет содержимое окна редактирования. Args: - user_name (str): Имя пользователя - role (str): Роль пользователя + user_name: Имя пользователя. + role: Роль пользователя. """ + edit_user_window = self.get_modal_window(user_name) edit_user_window.check_content(user_name, role) @@ -330,6 +357,7 @@ class UsersTab(BasePage): Raises: AssertionError: Если тулбар или кнопка редактирования отсутствуют. """ + self.toolbar.check_toolbar_presence("Toolbar is missing") self.toolbar.check_button_presence("edit") @@ -337,8 +365,9 @@ class UsersTab(BasePage): """Проверяет наличие и функциональность кнопок тулбара. Raises: - AssertionError: Если кнопки недоступны или имеют некорректные подсказки. + AssertionError: Если кнопки недоступны или подсказки неверны. """ + self.toolbar.check_button_presence("edit") self.toolbar.check_button_tooltip("edit", "Редактировать") @@ -357,6 +386,7 @@ class UsersTab(BasePage): Raises: AssertionError: Если таблица отсутствует. """ + self.users_table.check_presence( TableLocators.TABLE_WORK_AREA, "Users table is missing" @@ -366,12 +396,13 @@ class UsersTab(BasePage): """Проверяет наличие пользователя в таблице. Args: - name (str): Имя пользователя - role (str): Роль пользователя + name: Имя пользователя. + role: Роль пользователя. Raises: AssertionError: Если пользователь не найден. """ + found = self.find_user_in_table(name, role) if found == -1: assert False, f"User with name {name} and role {role} has not been found" @@ -380,12 +411,13 @@ class UsersTab(BasePage): """Проверяет отсутствие пользователя в таблице. Args: - name (str): Имя пользователя - role (str): Роль пользователя + name: Имя пользователя. + role: Роль пользователя. Raises: AssertionError: Если пользователь найден. """ + found = self.find_user_in_table(name, role) if found != -1: assert False, f"User with name {name} and role {role} has been found" @@ -394,11 +426,12 @@ class UsersTab(BasePage): """Сверяет данные таблицы с данными из БД. Args: - users_table (list): Данные из таблицы на странице + users_table: Данные из таблицы на странице. Raises: AssertionError: Если данные не соответствуют. """ + expected_users_list = [] tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"} diff --git a/site/components/alert_component/index.html b/site/components/alert_component/index.html index ca63575..a2dcc42 100644 --- a/site/components/alert_component/index.html +++ b/site/components/alert_component/index.html @@ -492,18 +492,18 @@
Модуль для работы с компонентом alert-окна в Playwright.
+Содержит класс AlertComponent для взаимодействия с различными типами +alert-окон (error, success, info, warning) и проверки их состояния.
+ @@ -1837,17 +1841,11 @@ Bases:BaseComponent
- Компонент для работы с alert-окнами.
-Поддерживает различные типы alert-окон: error, success, info, warning.
+Компонент для работы с alert-окнами Playwright.
+Поддерживает типы: error, success, info, warning. +Позволяет проверять наличие, отсутствие и текст сообщений.
-page: экземпляр страницы Playwright -alert_type: тип alert-окна (error/success/info/warning) -text: текстовый элемент сообщения alert-окна
-components\alert_component.py11 -12 -13 -14 -15 + |
-
экземпляр страницы Playwright +Экземпляр страницы Playwright. |
@@ -2077,7 +2077,7 @@ text: текстовый элемент сообщения alert-окна |
-
тип alert-окна (error/success/info/warning) +Тип alert-окна (error/success/info/warning). |
@@ -2103,7 +2103,7 @@ text: текстовый элемент сообщения alert-окна |
-
если передан неподдерживаемый тип alert-окна +Если передан неподдерживаемый тип alert-окна. |
| |
check_absence(text, timeout=30000)
+check_alert_absence(text, timeout=30000)
Проверка отсутствия alert-окна с заданным текстом.
+Проверяет отсутствие alert-окна с заданным текстом.
Parameters:
@@ -2184,10 +2186,11 @@ text: текстовый элемент сообщения alert-окнаtext
str
текст для проверки
+Текст для проверки.
timeout
int
время ожидания исчезновения (в миллисекундах)
+Время ожидания исчезновения (мс).
если alert-окно не исчезает в течение заданного времени
+Если окно не исчезает в течение заданного времени.
components\alert_component.py