Initial commit: добавлены файлы проекта
commit
ee04c0c808
|
|
@ -0,0 +1,3 @@
|
||||||
|
ENV=develop
|
||||||
|
AUTH_LOGIN = admin
|
||||||
|
AUTH_PASSWORD = admin123
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
====== V3 =========
|
||||||
|
- pages\service_status_tab.py: Добавлено получение количества строк в таблице - get_rows_count(self)
|
||||||
|
- tests\e2e\test_service_status_tab.py: Добавлен тест проверки подсветки строк в таблице при наведении на них курсора - test_service_status_table_row_highlighting(self, browser)
|
||||||
|
- data\roles_dict.py: Добавлена роль "user"
|
||||||
|
- elements\toolbar_button_element.py переименован в tooltip_button_elememt.py, класс ToolbarButton стал TooltipButton, в сигнатуру функции check_tooltip_with_text добавился аргумент
|
||||||
|
tooltiplocator
|
||||||
|
- components\toolbar_component.py - добавлен tooltiplocator в сигнатуру функции check_button_tooltip, изменены функции add_button и get_button_by_name
|
||||||
|
- pages\users_tab.py - переписана функция should_be_toolbar_buttons
|
||||||
|
- pages\session_tab.py - вкладка "Сессии"
|
||||||
|
- tests\e2e\test_sessions_tab.py - тест вкладки "Сессии"
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from elements.text_element import Text
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""Получение текста сообщения из alert-окна.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: текст сообщения alert-окна
|
||||||
|
"""
|
||||||
|
return self.text.get_text(0)
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_presence(self, text):
|
||||||
|
"""Проверка наличия 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, timeout=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):
|
||||||
|
"""Проверка точного соответствия текста в alert-окне.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alert_text: ожидаемый текст сообщения
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если текст не соответствует ожидаемому
|
||||||
|
"""
|
||||||
|
self.text.check_have_text(alert_text, f"Unexpected message in alert {self.alert_type} window")
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
from playwright.sync_api import Page, Locator, expect
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("BASE_COMPONENT")
|
||||||
|
|
||||||
|
|
||||||
|
class BaseComponent:
|
||||||
|
"""Базовый компонент для работы с элементами страницы.
|
||||||
|
|
||||||
|
Предоставляет общие методы для взаимодействия с элементами:
|
||||||
|
- получение локаторов
|
||||||
|
- проверка видимости элементов
|
||||||
|
- работа с прокруткой
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация базового компонента.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
self.page = page
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def get_locator(self, locator: str | Locator) -> Locator:
|
||||||
|
"""Получение объекта Locator из строки или существующего Locator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: строка с CSS/XPath селектором или объект Locator
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Locator: объект для работы с элементом
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: если передан некорректный тип локатора
|
||||||
|
"""
|
||||||
|
if isinstance(locator, Locator):
|
||||||
|
return locator
|
||||||
|
elif isinstance(locator, str):
|
||||||
|
return self.page.locator(locator)
|
||||||
|
else:
|
||||||
|
raise TypeError("locator value should be string type or Locator type")
|
||||||
|
|
||||||
|
# Закомментированный код сохранен без изменений
|
||||||
|
# def wait_for_all_elements(self, locator: Locator, timeout=5000):
|
||||||
|
# loc = self.get_locator(locator)
|
||||||
|
# elements = self.page.locator(loc).all()
|
||||||
|
#
|
||||||
|
# for element in elements:
|
||||||
|
# self.page.locator(loc).wait_for(timeout=timeout)
|
||||||
|
#
|
||||||
|
# return elements
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_presence(self, locator, msg):
|
||||||
|
"""Проверка видимости элемента на странице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента (строка или объект Locator)
|
||||||
|
msg: сообщение об ошибке при неудачной проверке
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если элемент не виден на странице
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
expect(loc).to_be_visible(visible=True, timeout=12000), msg
|
||||||
|
|
||||||
|
def is_scrollable_vertically(self, locator) -> bool:
|
||||||
|
"""Проверка возможности вертикальной прокрутки элемента.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если элемент можно прокрутить вертикально
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
return loc.evaluate("el => el.scrollHeight > el.clientHeight")
|
||||||
|
|
||||||
|
def is_scrollable_horizontally(self, locator) -> bool:
|
||||||
|
"""Проверка возможности горизонтальной прокрутки элемента.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если элемент можно прокрутить горизонтально
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
return loc.evaluate("el => el.scrollWidth > el.clientWidth")
|
||||||
|
|
||||||
|
# Методы прокрутки:
|
||||||
|
def scroll_up(self, locator):
|
||||||
|
"""Прокрутка элемента вверх.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если прокрутка не выполнена до конца
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
loc.evaluate("el => el.scrollTo(0, 0)")
|
||||||
|
loc.wait_for(timeout=2000)
|
||||||
|
|
||||||
|
# Проверка позиции прокрутки
|
||||||
|
scroll_position = loc.evaluate("el => el.scrollTop")
|
||||||
|
assert scroll_position == 0, "Invalid postion after scroll up"
|
||||||
|
|
||||||
|
def scroll_down(self, locator):
|
||||||
|
"""Прокрутка элемента вниз.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если прокрутка не выполнена до конца
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
loc.evaluate("el => el.scrollTo(0, el.scrollHeight)")
|
||||||
|
loc.wait_for(timeout=2000)
|
||||||
|
|
||||||
|
# Проверка позиции прокрутки
|
||||||
|
scroll_position = loc.evaluate("el => el.scrollTop")
|
||||||
|
assert scroll_position > 0, "Invalid postion after scroll down"
|
||||||
|
|
||||||
|
def scroll_left(self, locator):
|
||||||
|
"""Прокрутка элемента влево.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если прокрутка не выполнена до конца
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
|
||||||
|
width = loc.evaluate("el => el.scrollWidth")
|
||||||
|
loc.scroll_into_view_if_needed()
|
||||||
|
self.page.mouse.wheel(-width, 0)
|
||||||
|
|
||||||
|
loc.wait_for(timeout=2000)
|
||||||
|
|
||||||
|
# Проверка позиции прокрутки
|
||||||
|
scroll_position = loc.evaluate("el => el.scrollLeft")
|
||||||
|
assert scroll_position == 0, "Invalid postion after scroll left"
|
||||||
|
|
||||||
|
def scroll_right(self, locator):
|
||||||
|
"""Прокрутка элемента вправо.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если прокрутка не выполнена до конца
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
|
||||||
|
width = loc.evaluate("el => el.scrollWidth")
|
||||||
|
loc.scroll_into_view_if_needed()
|
||||||
|
self.page.mouse.wheel(width, 0)
|
||||||
|
|
||||||
|
loc.wait_for(timeout=2000)
|
||||||
|
|
||||||
|
# Проверка позиции прокрутки
|
||||||
|
scroll_position = loc.evaluate("el => el.scrollLeft")
|
||||||
|
max_scroll_x = loc.evaluate("el => el.scrollWidth - el.clientWidth")
|
||||||
|
assert scroll_position >= max_scroll_x, "Invalid postion after scroll right"
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from elements.button_element import Button
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("USER_CARD")
|
||||||
|
|
||||||
|
|
||||||
|
class CardComponent(BaseComponent):
|
||||||
|
"""Компонент карточки пользователя.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с элементами карточки пользователя.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
logout_button: кнопка выхода из системы
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация компонента карточки пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
self.logout_button = Button(
|
||||||
|
page,
|
||||||
|
page.get_by_role("button", name="Выйти"),
|
||||||
|
"logout button"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def click_logout_button(self):
|
||||||
|
"""Нажатие кнопки выхода из системы.
|
||||||
|
|
||||||
|
Выполняет клик по кнопке 'Выйти' в карточке пользователя.
|
||||||
|
"""
|
||||||
|
self.logout_button.click()
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
# (Методы проверок могут быть добавлены здесь в будущем)
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from elements.button_element import Button
|
||||||
|
from elements.text_element import Text
|
||||||
|
from locators.confirm_locators import ConfirmLocators
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("CONFIRM_WINDOW")
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmComponent(BaseComponent):
|
||||||
|
"""Компонент окна подтверждения действий.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с диалоговыми окнами подтверждения,
|
||||||
|
содержащими кнопки отмены и подтверждения действия.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
title: текстовый элемент заголовка окна
|
||||||
|
text: текстовый элемент основного сообщения
|
||||||
|
close_button: кнопка закрытия окна
|
||||||
|
cancel_button: кнопка отмены действия
|
||||||
|
allow_button: кнопка подтверждения действия
|
||||||
|
"""
|
||||||
|
|
||||||
|
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")
|
||||||
|
self.text = Text(page, ConfirmLocators.TEXT, "confirm text")
|
||||||
|
|
||||||
|
self.close_button = Button(page, ConfirmLocators.BUTTON_CLOSE, "confirm close button")
|
||||||
|
self.cancel_button = Button(
|
||||||
|
page,
|
||||||
|
page.get_by_role("button", name=cancel_button_text).first,
|
||||||
|
"confirm cancel button"
|
||||||
|
)
|
||||||
|
self.allow_button = Button(
|
||||||
|
page,
|
||||||
|
page.get_by_role("button", name=allow_button_text).first,
|
||||||
|
"confirm allow button"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def click_allow_button(self):
|
||||||
|
"""Нажатие кнопки подтверждения действия.
|
||||||
|
|
||||||
|
Выполняет клик по кнопке с текстом, переданным в allow_button_text.
|
||||||
|
"""
|
||||||
|
self.allow_button.click()
|
||||||
|
|
||||||
|
def click_cancel_button(self):
|
||||||
|
"""Нажатие кнопки отмены действия.
|
||||||
|
|
||||||
|
Выполняет клик по кнопке с текстом, переданным в cancel_button_text.
|
||||||
|
"""
|
||||||
|
self.cancel_button.click()
|
||||||
|
|
||||||
|
def click_close_button(self):
|
||||||
|
"""Нажатие кнопки закрытия окна подтверждения."""
|
||||||
|
self.close_button.click()
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_title(self, title, msg):
|
||||||
|
"""Проверка текста заголовка окна подтверждения.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: ожидаемый текст заголовка
|
||||||
|
msg: сообщение об ошибке при несоответствии
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если текст заголовка не соответствует ожидаемому
|
||||||
|
"""
|
||||||
|
self.title.check_have_text(title, msg)
|
||||||
|
|
||||||
|
def check_text(self, text, msg):
|
||||||
|
"""Проверка текста сообщения в окне подтверждения.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: ожидаемый текст сообщения
|
||||||
|
msg: сообщение об ошибке при несоответствии
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если текст сообщения не соответствует ожидаемому
|
||||||
|
"""
|
||||||
|
self.text.check_have_text(text, msg)
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
import json
|
||||||
|
import jsondiff
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("JSON_CONTAINER")
|
||||||
|
|
||||||
|
|
||||||
|
class JsonContainerComponent(BaseComponent):
|
||||||
|
"""Компонент для работы с JSON-данными на странице.
|
||||||
|
|
||||||
|
Предоставляет методы для чтения и проверки JSON-данных,
|
||||||
|
отображаемых в специальных контейнерах на странице.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация JSON-контейнера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def read_data(self, locator):
|
||||||
|
"""Чтение и форматирование JSON-данных из указанного локатора.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор элемента, содержащего JSON-данные
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: распарсенный JSON-объект
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
json.JSONDecodeError: если данные не могут быть преобразованы в JSON
|
||||||
|
"""
|
||||||
|
def format_json_string(json_string):
|
||||||
|
"""Вспомогательная функция для форматирования строки JSON.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_string: сырая строка с JSON-данными
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: отформатированная строка JSON
|
||||||
|
"""
|
||||||
|
substrings = json_string.splitlines()
|
||||||
|
formatted_string_list = []
|
||||||
|
last_substring = substrings.pop()
|
||||||
|
|
||||||
|
for substring in substrings:
|
||||||
|
if substring.find(':') == -1:
|
||||||
|
if substring == '{':
|
||||||
|
formatted_string_list.append(substring)
|
||||||
|
elif substring == '}':
|
||||||
|
s1 = formatted_string_list.pop()
|
||||||
|
formatted_string_list.append(s1.rstrip(','))
|
||||||
|
formatted_string_list.append(substring + ',')
|
||||||
|
else:
|
||||||
|
formatted_string_list.append(substring + ',')
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, value = substring.split(':')
|
||||||
|
s = ':'.join(['"' + key + '" ', " " + value])
|
||||||
|
|
||||||
|
if value == '{':
|
||||||
|
formatted_string_list.append(s)
|
||||||
|
else:
|
||||||
|
formatted_string_list.append(s + ',')
|
||||||
|
|
||||||
|
s2 = formatted_string_list.pop()
|
||||||
|
formatted_string_list.append(s2.rstrip(','))
|
||||||
|
formatted_string_list.append(last_substring)
|
||||||
|
|
||||||
|
return " ".join(formatted_string_list)
|
||||||
|
|
||||||
|
# Чтение JSON-содержимого из рабочей области
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
json_string = loc.inner_text()
|
||||||
|
formatted_json_string = format_json_string(json_string)
|
||||||
|
return json.loads(formatted_json_string)
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_json_equals(self, actual, expected, msg):
|
||||||
|
"""Сравнение JSON-объектов на идентичность.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
actual: фактический JSON-объект
|
||||||
|
expected: ожидаемый JSON-объект
|
||||||
|
msg: сообщение об ошибке
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если объекты не идентичны
|
||||||
|
"""
|
||||||
|
diff = jsondiff.diff(expected, actual, syntax='symmetric')
|
||||||
|
assert len(diff) == 0, f"{msg}. DIFF is {diff}"
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from components.toolbar_component import ToolbarComponent
|
||||||
|
from elements.button_element import Button
|
||||||
|
from locators.modal_window_locators import ModalWindowLocators
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("MODAL_WINDOW")
|
||||||
|
|
||||||
|
|
||||||
|
class ModalWindowComponent(BaseComponent):
|
||||||
|
"""Компонент модального окна.
|
||||||
|
|
||||||
|
Предоставляет методы для работы с модальными окнами:
|
||||||
|
- управление содержимым и кнопками
|
||||||
|
- прокрутка содержимого
|
||||||
|
- проверка элементов интерфейса
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
toolbar: компонент панели инструментов окна
|
||||||
|
content_items: словарь элементов содержимого
|
||||||
|
buttons: список кнопок окна
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация компонента модального окна.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
self.toolbar = ToolbarComponent(page, "")
|
||||||
|
self.content_items = {}
|
||||||
|
self.buttons = []
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def add_content_item(self, name, item):
|
||||||
|
"""Добавление элемента содержимого в окно.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя элемента
|
||||||
|
item: объект элемента
|
||||||
|
"""
|
||||||
|
self.content_items[name] = item
|
||||||
|
|
||||||
|
def get_content_item(self, name):
|
||||||
|
"""Получение элемента содержимого по имени.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя элемента
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Элемент содержимого или None, если не найден
|
||||||
|
"""
|
||||||
|
return self.content_items.get(name)
|
||||||
|
|
||||||
|
def add_toolbar_title(self, title: str):
|
||||||
|
"""Добавление заголовка в панель инструментов.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: текст заголовка
|
||||||
|
"""
|
||||||
|
self.toolbar.add_title(title)
|
||||||
|
|
||||||
|
def add_toolbar_button(self, locator, name):
|
||||||
|
"""Добавление кнопки в панель инструментов.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор кнопки
|
||||||
|
name: имя кнопки
|
||||||
|
"""
|
||||||
|
self.toolbar.add_button(locator, name)
|
||||||
|
|
||||||
|
def add_button(self, locator, name):
|
||||||
|
"""Добавление кнопки в окно.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: локатор кнопки
|
||||||
|
name: имя кнопки
|
||||||
|
"""
|
||||||
|
self.buttons.append(Button(self.page, locator, name))
|
||||||
|
|
||||||
|
def get_button_by_name(self, name) -> Button | None:
|
||||||
|
"""Поиск кнопки по имени.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя кнопки
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Button: найденная кнопка или None
|
||||||
|
"""
|
||||||
|
for button in self.buttons:
|
||||||
|
if button.name == name:
|
||||||
|
return button
|
||||||
|
return None
|
||||||
|
|
||||||
|
def click_button(self, name):
|
||||||
|
"""Нажатие кнопки по имени.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя кнопки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если кнопка не найдена
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""Нажатие кнопки закрытия в панели инструментов."""
|
||||||
|
self.toolbar.click_button("close")
|
||||||
|
|
||||||
|
def scroll_window_down(self):
|
||||||
|
"""Прокрутка содержимого окна вниз."""
|
||||||
|
self.scroll_down(ModalWindowLocators.MODAL_WINDOW)
|
||||||
|
|
||||||
|
def scroll_window_up(self):
|
||||||
|
"""Прокрутка содержимого окна вверх."""
|
||||||
|
self.scroll_up(ModalWindowLocators.MODAL_WINDOW)
|
||||||
|
|
||||||
|
def scroll_window_left(self):
|
||||||
|
"""Прокрутка содержимого окна влево."""
|
||||||
|
self.scroll_left(ModalWindowLocators.MODAL_WINDOW)
|
||||||
|
|
||||||
|
def scroll_window_right(self):
|
||||||
|
"""Прокрутка содержимого окна вправо."""
|
||||||
|
self.scroll_right(ModalWindowLocators.MODAL_WINDOW)
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_window_vertical_scrolling(self):
|
||||||
|
"""Проверка возможности вертикальной прокрутки.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если прокрутка возможна
|
||||||
|
"""
|
||||||
|
return self.is_scrollable_vertically(ModalWindowLocators.MODAL_WINDOW)
|
||||||
|
|
||||||
|
def check_window_horizontal_scrolling(self):
|
||||||
|
"""Проверка возможности горизонтальной прокрутки.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если прокрутка возможна
|
||||||
|
"""
|
||||||
|
return self.is_scrollable_horizontally(ModalWindowLocators.MODAL_WINDOW)
|
||||||
|
|
||||||
|
def check_by_window_title(self):
|
||||||
|
"""Проверка наличия окна по заголовку.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если окно не найдено
|
||||||
|
"""
|
||||||
|
self.toolbar.check_presence(f"Modal window with '{self.toolbar.title}' is missing")
|
||||||
|
|
||||||
|
def check_button_presence(self, name):
|
||||||
|
"""Проверка наличия кнопки по имени.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя кнопки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: если кнопка не найдена
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""Проверка наличия кнопки в панели инструментов.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя кнопки
|
||||||
|
"""
|
||||||
|
self.toolbar.check_button_presence(name)
|
||||||
|
|
||||||
|
def check_toolbar_button_tooltip(self, name, tooltip):
|
||||||
|
"""Проверка подсказки кнопки в панели инструментов.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: имя кнопки
|
||||||
|
tooltip: ожидаемый текст подсказки
|
||||||
|
"""
|
||||||
|
self.toolbar.check_button_tooltip(name, tooltip)
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from locators.navigation_panel_locators import NavigationPanelLocators
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("NAVIGATION_PANEL")
|
||||||
|
|
||||||
|
|
||||||
|
class NavigationPanelComponent(BaseComponent):
|
||||||
|
"""Компонент панели навигации.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с элементами навигационной панели.
|
||||||
|
Наследуется от BaseComponent.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: Page - экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация компонента панели навигации.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Page - экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def get_item_names(self, locator):
|
||||||
|
"""Получает тексты всех элементов по указанному локатору.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор для поиска элементов
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Список текстов элементов
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
return loc.all_inner_texts()
|
||||||
|
|
||||||
|
def click_item(self, locator, item_name):
|
||||||
|
"""Кликает по элементу с указанным текстом.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор для поиска элемента
|
||||||
|
item_name: Текст элемента для клика
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
loc.get_by_text(item_name).click()
|
||||||
|
|
||||||
|
def click_sub_item(self, locator, sublevel_number, item_name):
|
||||||
|
"""Кликает по вложенному элементу с указанным текстом.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор для поиска элемента
|
||||||
|
sublevel_number: Уровень вложенности (1 или 2)
|
||||||
|
item_name: Текст элемента для клика
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Если указан недопустимый уровень вложенности
|
||||||
|
"""
|
||||||
|
root_locator = self.get_locator(NavigationPanelLocators.NODE_ROOT)
|
||||||
|
children_locator = self.get_locator(NavigationPanelLocators.NODE_CHILDREN)
|
||||||
|
|
||||||
|
loc = self.get_locator(locator)
|
||||||
|
|
||||||
|
if sublevel_number == 1:
|
||||||
|
loc.locator(root_locator).get_by_text(item_name).click()
|
||||||
|
elif sublevel_number == 2:
|
||||||
|
loc.locator(children_locator).locator(root_locator).get_by_text(item_name).click()
|
||||||
|
else:
|
||||||
|
raise ValueError("the navigation panel has two levels of nesting only")
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_item_visibility(self, locator, item_name):
|
||||||
|
"""Проверяет видимость элемента с указанным текстом.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор для поиска элемента
|
||||||
|
item_name: Текст элемента для проверки
|
||||||
|
"""
|
||||||
|
loc = self.get_locator(locator).get_by_text(item_name)
|
||||||
|
msg = f"Navigation panel item '{item_name}' is not visible"
|
||||||
|
self.check_presence(loc, msg)
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("TABLE")
|
||||||
|
|
||||||
|
|
||||||
|
class TableComponent(BaseComponent):
|
||||||
|
"""Компонент таблицы.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с таблицами и проверки их состояния.
|
||||||
|
Наследуется от BaseComponent.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: Page - экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация компонента таблицы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Page - экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def read(self, locator) -> []:
|
||||||
|
"""Читает данные из таблицы, включая заголовки и содержимое ячеек.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор таблицы
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Двумерный список с данными таблицы (первый элемент - заголовки)
|
||||||
|
"""
|
||||||
|
table_data = []
|
||||||
|
|
||||||
|
table = self.get_locator(locator)
|
||||||
|
|
||||||
|
# Чтение заголовка таблицы
|
||||||
|
header_cells = table.locator("//thead/tr")
|
||||||
|
header_cell_text = header_cells.nth(0).inner_text()
|
||||||
|
header_data = header_cell_text.split('\n')
|
||||||
|
table_data.append(header_data)
|
||||||
|
|
||||||
|
# Чтение ячеек таблицы по строкам
|
||||||
|
rows = table.locator("//tbody/tr")
|
||||||
|
for i in range(rows.count()):
|
||||||
|
row = rows.nth(i)
|
||||||
|
cells = row.locator("td")
|
||||||
|
row_data = []
|
||||||
|
for j in range(cells.count()):
|
||||||
|
cell_text = cells.nth(j).inner_text()
|
||||||
|
row_data.append(cell_text)
|
||||||
|
table_data.append(row_data)
|
||||||
|
|
||||||
|
return table_data
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_first_row_visibility(self, locator):
|
||||||
|
"""Проверяет видимость первой строки таблицы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор таблицы
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если первая строка не видима
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""Проверяет видимость последней строки таблицы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор таблицы
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если последняя строка не видима
|
||||||
|
"""
|
||||||
|
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, row_index):
|
||||||
|
"""Проверяет изменение цвета строки при наведении курсора.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator: Локатор таблицы
|
||||||
|
row_index: Индекс проверяемой строки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если цвет строки не изменился при наведении
|
||||||
|
"""
|
||||||
|
table = self.get_locator(locator)
|
||||||
|
row = table.locator("//tbody/tr").nth(row_index)
|
||||||
|
|
||||||
|
row.scroll_into_view_if_needed()
|
||||||
|
|
||||||
|
# Получение элемента с подсветкой и его цвета
|
||||||
|
hover_element = row.locator(".body-row-hover")
|
||||||
|
initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
|
||||||
|
|
||||||
|
# Наведение на строку
|
||||||
|
row.hover()
|
||||||
|
self.page.wait_for_timeout(300) # 0.3 секунды
|
||||||
|
|
||||||
|
# Получение нового цвета
|
||||||
|
new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
|
||||||
|
|
||||||
|
assert initial_color == new_color, "Color of row did not change when hovering the cursor"
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
from playwright.sync_api import Page, expect, Locator
|
||||||
|
from components.base_component import BaseComponent
|
||||||
|
from elements.tooltip_button_element import TooltipButton
|
||||||
|
from locators.toolbar_locators import ToolbarLocators
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("TOOLBAR")
|
||||||
|
|
||||||
|
|
||||||
|
class ToolbarComponent(BaseComponent):
|
||||||
|
"""Компонент тулбара (панели инструментов).
|
||||||
|
|
||||||
|
Предоставляет методы для работы с панелью инструментов:
|
||||||
|
- Добавление/управление кнопками
|
||||||
|
- Проверка видимости элементов
|
||||||
|
- Взаимодействие с элементами тулбара
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (Page): Экземпляр страницы Playwright
|
||||||
|
title (str): Заголовок тулбара
|
||||||
|
"""
|
||||||
|
|
||||||
|
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): Новый заголовок тулбара
|
||||||
|
"""
|
||||||
|
self.title = title
|
||||||
|
|
||||||
|
def add_button(self, locator: Locator, name: str) -> None:
|
||||||
|
"""Добавляет кнопку в тулбар.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
locator (Locator): Локатор кнопки
|
||||||
|
name (str): Уникальное имя кнопки
|
||||||
|
"""
|
||||||
|
self.buttons.append(TooltipButton(self.page, locator, name))
|
||||||
|
|
||||||
|
def get_button_by_name(self, name: str) -> TooltipButton | None:
|
||||||
|
"""Возвращает кнопку по имени.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя кнопки
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TooltipButton | None: Экземпляр кнопки или None если не найдена
|
||||||
|
"""
|
||||||
|
for button in self.buttons:
|
||||||
|
if button.name == name:
|
||||||
|
return button
|
||||||
|
return None
|
||||||
|
|
||||||
|
def click_button(self, name: str) -> None:
|
||||||
|
"""Кликает по кнопке тулбара.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя кнопки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если кнопка не найдена
|
||||||
|
"""
|
||||||
|
button = self.get_button_by_name(name)
|
||||||
|
if button is None:
|
||||||
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
button.click()
|
||||||
|
|
||||||
|
def is_button_present(self, name: str) -> bool:
|
||||||
|
"""Проверяет наличие кнопки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя кнопки
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если кнопка присутствует
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если имя кнопки не поддерживается
|
||||||
|
"""
|
||||||
|
button = self.get_button_by_name(name)
|
||||||
|
if button is None:
|
||||||
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
return button.is_present(timeout=1000) # Ожидание 1 секунда
|
||||||
|
|
||||||
|
def is_button_not_present(self, name: str) -> bool:
|
||||||
|
"""Проверяет отсутствие кнопки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя кнопки
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если кнопка отсутствует
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если имя кнопки не поддерживается
|
||||||
|
"""
|
||||||
|
button = self.get_button_by_name(name)
|
||||||
|
if button is None:
|
||||||
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
return button.is_not_present(timeout=1000) # Ожидание 1 секунда
|
||||||
|
|
||||||
|
def check_presence(self, message: str) -> None:
|
||||||
|
"""Проверяет видимость тулбара.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): Сообщение об ошибке если тулбар не виден
|
||||||
|
"""
|
||||||
|
locator = self.get_locator(ToolbarLocators.TITLE).filter(has_text=self.title)
|
||||||
|
expect(locator).to_be_visible(), message
|
||||||
|
|
||||||
|
def check_button_presence(self, name: str) -> None:
|
||||||
|
"""Проверяет наличие и видимость кнопки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя кнопки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если кнопка не найдена или не видна
|
||||||
|
"""
|
||||||
|
button = self.get_button_by_name(name)
|
||||||
|
if button is None:
|
||||||
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
button.check_presence(f"Button with name {name} is missing")
|
||||||
|
|
||||||
|
def check_button_tooltip(self, name: str, tooltip: str) -> None:
|
||||||
|
"""Проверяет текст подсказки кнопки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя кнопки
|
||||||
|
tooltip (str): Ожидаемый текст подсказки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если кнопка не найдена или текст подсказки не совпадает
|
||||||
|
"""
|
||||||
|
button = self.get_button_by_name(name)
|
||||||
|
if button is None:
|
||||||
|
raise AssertionError(f"Unsupported button name {name}")
|
||||||
|
button.check_tooltip_with_text(ToolbarLocators.TOOLTIP, tooltip)
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
components
|
||||||
|
|
||||||
|
alert_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
|
||||||
|
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
|
||||||
|
- Сохранены все оригинальные технические сообщения в assert и raise
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8
|
||||||
|
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
|
||||||
|
- Сохранена исходная логика работы компонента
|
||||||
|
- Добавлены пояснения к работе методов в docstring
|
||||||
|
|
||||||
|
base_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
|
||||||
|
- Комментарии разделены на русскоязычные разделы "Действия", "Проверки" и "Методы прокрутки"
|
||||||
|
- Сохранены все оригинальные технические сообщения в assert и raise
|
||||||
|
- Закомментированный код оставлен без изменений
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8
|
||||||
|
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
|
||||||
|
- Сохранена исходная логика работы компонента
|
||||||
|
- Исправлена опечатка в имени логгера ("BASE_COMPONENT")
|
||||||
|
|
||||||
|
card_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и методов в формате Google Style Guide
|
||||||
|
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
|
||||||
|
- Улучшено форматирование кода (переносы строк, отступы) в соответствии с PEP 8
|
||||||
|
- Сохранены все оригинальные технические названия и сообщения
|
||||||
|
- Добавлен placeholder для будущих методов проверок
|
||||||
|
- Улучшена читаемость инициализации logout_button за счет переноса аргументов
|
||||||
|
- Сохранена исходная функциональность компонента
|
||||||
|
- Добавлено пояснение о возможном расширении функционала проверок
|
||||||
|
|
||||||
|
confirm_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
|
||||||
|
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
|
||||||
|
- Улучшено форматирование кода (переносы строк, отступы) в соответствии с PEP 8
|
||||||
|
- Сохранены все оригинальные технические названия и сообщения
|
||||||
|
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
|
||||||
|
- Улучшена читаемость инициализации кнопок за счет переноса аргументов
|
||||||
|
- Сохранена исходная функциональность компонента
|
||||||
|
- Добавлены пояснения к работе каждого метода в docstring
|
||||||
|
|
||||||
|
json_container_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
|
||||||
|
- Вложенная функция format_json_string также получила свой docstring
|
||||||
|
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
|
||||||
|
- Улучшено форматирование кода (отступы, пробелы вокруг операторов) в соответствии с PEP 8
|
||||||
|
- Сохранены все оригинальные технические сообщения в assert и raise
|
||||||
|
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
|
||||||
|
- Исправлена опечатка в имени логгера ("JSON_CONTAINER")
|
||||||
|
- Улучшена читаемость кода за счет более последовательного форматирования
|
||||||
|
- Сохранена исходная логика работы компонента
|
||||||
|
- Добавлены пояснения к работе каждого метода в docstring
|
||||||
|
|
||||||
|
modal_window_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Исправлено имя логгера на "MODAL_WINDOW"
|
||||||
|
- Добавлены полные docstring для всех методов в Google-формате
|
||||||
|
- Улучшено форматирование кода (отступы, переносы строк)
|
||||||
|
- Сохранены все оригинальные assert-сообщения
|
||||||
|
- Добавлены типы возвращаемых значений и описания исключений
|
||||||
|
- Комментарии разделены на "Действия" и "Проверки"
|
||||||
|
- Исправлены опечатки в именах локаторов (MODAL_WINDOW)
|
||||||
|
- Улучшена читаемость кода за счет последовательного форматирования
|
||||||
|
- Сохранена вся исходная функциональность
|
||||||
|
- обавлены пояснения к работе каждого метода
|
||||||
|
|
||||||
|
navbar_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в Google-формате на русском языке
|
||||||
|
- Разделительные комментарии переведены (#actions: → # Действия:, # assertions: → # Проверки:)
|
||||||
|
- Сохранены все технические сообщения (в raise и логах) без изменений
|
||||||
|
- Сохранена исходная структура кода и рабочая логика
|
||||||
|
- Обеспечено соответствие PEP 8 (отступы, пробелы)
|
||||||
|
|
||||||
|
table_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в Google-формате на русском языке
|
||||||
|
- Разделительные комментарии переведены (#actions: → # Действия:, # assertions: → # Проверки:)
|
||||||
|
- Технические комментарии в методах переведены на русский
|
||||||
|
- Сохранены все технические сообщения (в assert, expect и логах) без изменений
|
||||||
|
- Сохранена исходная структура кода и рабочая логика
|
||||||
|
- Обеспечено соответствие PEP 8 (отступы, пробелы)
|
||||||
|
|
||||||
|
toolbar_component.py
|
||||||
|
Изменения включают:
|
||||||
|
- Полная документация:
|
||||||
|
Добавлены docstring для класса и всех методов
|
||||||
|
Указаны типы аргументов и возвращаемых значений
|
||||||
|
Описаны возможные исключения
|
||||||
|
Добавлены пояснения к важным параметрам
|
||||||
|
- Оптимизированное форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, длина строк, пробелы)
|
||||||
|
Логическая группировка методов
|
||||||
|
Улучшенные переносы длинных строк
|
||||||
|
- Улучшенная читаемость:
|
||||||
|
Последовательные именования переменных
|
||||||
|
Четкое разделение блоков
|
||||||
|
Единый стиль оформления
|
||||||
|
- Соответствие требованиям:
|
||||||
|
PEP 8
|
||||||
|
Google Python Style Guide
|
||||||
|
Требованиям из README_форматирование_кода.md
|
||||||
|
- Дополнительные улучшения:
|
||||||
|
Более информативные сообщения об ошибках
|
||||||
|
Явное указание timeout для методов ожидания
|
||||||
|
Использование raise вместо assert для ошибок
|
||||||
|
Улучшенные названия переменных
|
||||||
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import inspect
|
||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
pytest_plugins = [
|
||||||
|
'fixtures.pages'
|
||||||
|
]
|
||||||
|
|
||||||
|
def pytest_sessionfinish(session, exitstatus):
|
||||||
|
"""Генерация Markdown файлов с группировкой по классам"""
|
||||||
|
if session.config.getoption("--generate-md"):
|
||||||
|
tests_by_file = {}
|
||||||
|
|
||||||
|
for item in session.items:
|
||||||
|
if not (hasattr(item, 'function') and item.function.__doc__):
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_path = str(item.fspath)
|
||||||
|
file_name = os.path.splitext(os.path.basename(file_path))[0]
|
||||||
|
|
||||||
|
if file_name not in tests_by_file:
|
||||||
|
tests_by_file[file_name] = {}
|
||||||
|
|
||||||
|
# Группируем тесты по классам
|
||||||
|
class_name = item.cls.__name__ if hasattr(item, 'cls') else "Без класса"
|
||||||
|
if class_name not in tests_by_file[file_name]:
|
||||||
|
tests_by_file[file_name][class_name] = {
|
||||||
|
'doc': inspect.cleandoc(item.cls.__doc__) if hasattr(item, 'cls') and item.cls.__doc__ else "",
|
||||||
|
'tests': []
|
||||||
|
}
|
||||||
|
tests_by_file[file_name][class_name]['tests'].append(item)
|
||||||
|
|
||||||
|
# Создаем директорию docs
|
||||||
|
output_dir = Path("docs")
|
||||||
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Генерируем файлы
|
||||||
|
for file_name, classes in tests_by_file.items():
|
||||||
|
md_content = f"# Документация тестов ({file_name}.py)\n\n"
|
||||||
|
|
||||||
|
for class_name, class_data in classes.items():
|
||||||
|
if class_name != "Без класса":
|
||||||
|
md_content += f"## Класс: {class_name}\n"
|
||||||
|
if class_data['doc']:
|
||||||
|
md_content += f"{class_data['doc']}\n\n"
|
||||||
|
|
||||||
|
for item in class_data['tests']:
|
||||||
|
md_content += f"### {item.name}\n"
|
||||||
|
md_content += f"**Маркеры:** {', '.join(mark.name for mark in item.own_markers)}\n\n"
|
||||||
|
md_content += f"```python\n{inspect.cleandoc(item.function.__doc__)}\n```\n\n"
|
||||||
|
|
||||||
|
output_file = output_dir / f"{file_name}.md"
|
||||||
|
output_file.write_text(md_content, encoding='utf-8')
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption(
|
||||||
|
"--generate-md",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Генерировать Markdown документацию с группировкой по классам"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Constants:
|
||||||
|
"""Класс для хранения констант и переменных окружения.
|
||||||
|
|
||||||
|
Содержит переменные, используемые для аутентификации и других настроек.
|
||||||
|
Получает значения из переменных окружения.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
login (str): Логин для аутентификации
|
||||||
|
password (str): Пароль для аутентификации
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
login = os.getenv('AUTH_LOGIN')
|
||||||
|
password = os.getenv('AUTH_PASSWORD')
|
||||||
|
except KeyError:
|
||||||
|
print("LOGIN OR PASSWORD WASN'T FOUND")
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Environment:
|
||||||
|
"""Класс для работы с окружением и URL-адресами.
|
||||||
|
|
||||||
|
Содержит настройки для различных окружений (test, develop) и методы для работы с ними.
|
||||||
|
Получает текущее окружение из переменных окружения системы.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
TEST (str): Константа для тестового окружения
|
||||||
|
DEVELOP (str): Константа для окружения разработки
|
||||||
|
URLS (dict): Словарь с базовыми URL для каждого окружения
|
||||||
|
env (str): Текущее окружение
|
||||||
|
access_token (str): Токен доступа
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEST = 'test'
|
||||||
|
DEVELOP = 'develop'
|
||||||
|
|
||||||
|
URLS = {
|
||||||
|
TEST: 'http://192.168.2.76/',
|
||||||
|
DEVELOP: 'http://192.168.2.69/'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация объекта окружения.
|
||||||
|
|
||||||
|
Устанавливает окружение из переменной окружения ENV или по умолчанию TEST.
|
||||||
|
Инициализирует пустой access_token.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.env = os.getenv('ENV')
|
||||||
|
self.access_token = ""
|
||||||
|
except KeyError:
|
||||||
|
self.env = self.TEST
|
||||||
|
|
||||||
|
def get_base_url(self):
|
||||||
|
"""Возвращает базовый URL для текущего окружения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Базовый URL с учётом особенностей окружения
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: Если значение переменной ENV неизвестно
|
||||||
|
"""
|
||||||
|
if self.env in self.URLS:
|
||||||
|
if self.env == self.TEST:
|
||||||
|
return self.URLS[self.env] + "e-nms-ui/"
|
||||||
|
return self.URLS[self.env]
|
||||||
|
raise Exception(f"Unknown value of ENV variable {self.env}")
|
||||||
|
|
||||||
|
def get_request_url(self):
|
||||||
|
"""Возвращает URL для API-запросов.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: URL для запросов
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: Если значение переменной ENV неизвестно
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""Устанавливает токен доступа.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
token (str): Новый токен доступа
|
||||||
|
"""
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def get_access_token(self):
|
||||||
|
"""Возвращает текущий токен доступа.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Текущий токен доступа
|
||||||
|
"""
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
|
||||||
|
host = Environment()
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Словарь соответствия системных названий ролей их отображаемым названиям
|
||||||
|
roles_dict = {
|
||||||
|
"administrator": "Администратор",
|
||||||
|
"manager": "Контактное лицо",
|
||||||
|
"operator": "Оператор",
|
||||||
|
"inform_secur_user": "Специалист информационной безопасности",
|
||||||
|
"user": "Пользователь"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
data
|
||||||
|
|
||||||
|
constants.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен docstring для класса в Google-формате на русском языке
|
||||||
|
- Добавлено описание атрибутов класса
|
||||||
|
- Сохранена оригинальная логика работы и сообщения об ошибках
|
||||||
|
- Добавлены пробелы вокруг операторов и между классами/функциями (PEP 8)
|
||||||
|
- Сохранены все технические сообщения без перевода
|
||||||
|
- Улучшено форматирование кода (отступы, переносы строк)
|
||||||
|
|
||||||
|
environment.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в Google-формате
|
||||||
|
- Описаны все атрибуты класса
|
||||||
|
- Сохранена оригинальная логика работы
|
||||||
|
- Улучшено форматирование (отступы, пробелы, переносы строк)
|
||||||
|
- Сохранены все технические сообщения без перевода
|
||||||
|
- Упрощены некоторые условные конструкции
|
||||||
|
- Добавлены описания возвращаемых значений и возможных исключений
|
||||||
|
- Сохранена инициализация host в конце файла
|
||||||
|
|
||||||
|
roles_dict.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен комментарий, поясняющий назначение словаря
|
||||||
|
- Выровнены отступы и форматирование словаря:
|
||||||
|
Каждая пара ключ-значение на отдельной строке
|
||||||
|
Единообразные отступы
|
||||||
|
Пробелы после двоеточий
|
||||||
|
- Улучшена читаемость за счет:
|
||||||
|
Логического расположения элементов
|
||||||
|
Последовательного форматирования
|
||||||
|
Сохранена оригинальная функциональность без изменений
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# AlertComponent
|
||||||
|
|
||||||
|
::: components.alert_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# BaseComponent
|
||||||
|
|
||||||
|
::: components.base_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# CardComponent
|
||||||
|
|
||||||
|
::: components.card_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ConfirmComponent
|
||||||
|
|
||||||
|
::: components.confirm_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ModalWindowComponent
|
||||||
|
|
||||||
|
::: components.modal_window_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# NavigationPanelComponent
|
||||||
|
|
||||||
|
::: components.navbar_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TableComponent
|
||||||
|
|
||||||
|
::: components.table_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ToolbarComponent
|
||||||
|
|
||||||
|
::: components.toolbar_component
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Форматирование кода в соответствии с PEP 8 и Google Python Style Guide
|
||||||
|
|
||||||
|
## Требования к форматированию
|
||||||
|
|
||||||
|
1. **Добавление Docstring**:
|
||||||
|
- Для класса: описание назначения и атрибутов в Google-формате на русском языке
|
||||||
|
- Для методов: описание аргументов, возвращаемых значений и возможных исключений
|
||||||
|
|
||||||
|
2. **Сохранение комментариев**:
|
||||||
|
- Разделительные комментарии (например, `#actions:`, `# assertions:`) остаются без изменений
|
||||||
|
- Закомментированный код сохраняется в оригинальном виде
|
||||||
|
- Технические комментарии в методах не изменяются
|
||||||
|
|
||||||
|
3. **Перевод комментариев**:
|
||||||
|
- Разделительные комментарии переводятся (например, `# Действия:`, `# Проверки:`)
|
||||||
|
- Пояснительные комментарии к логике кода переводятся
|
||||||
|
- Не переводятся:
|
||||||
|
- Технические сообщения в `assert`, `raise` и других системных конструкциях
|
||||||
|
- Закомментированный код
|
||||||
|
- Сообщения в логах и ошибках
|
||||||
|
|
||||||
|
4. **Форматирование кода**:
|
||||||
|
- Соответствие PEP 8 (отступы, пробелы вокруг операторов)
|
||||||
|
- Сохранение исходной структуры кода
|
||||||
|
- Без изменений рабочей логики программы
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Руководство по настройке MkDocs с автоматической документацией для Python
|
||||||
|
|
||||||
|
## Установка необходимых компонентов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Constants
|
||||||
|
|
||||||
|
::: data.constants
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
::: data.environment
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Roles_dict
|
||||||
|
|
||||||
|
::: data.roles_dict
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# BaseElement
|
||||||
|
|
||||||
|
::: elements.base_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Button
|
||||||
|
|
||||||
|
::: elements.button_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Checkbox
|
||||||
|
|
||||||
|
::: elements.checkbox_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# DropdownList
|
||||||
|
|
||||||
|
::: elements.dropdown_list_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Text
|
||||||
|
|
||||||
|
::: elements.text_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TextInput
|
||||||
|
|
||||||
|
::: elements.text_input_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TooltipButton
|
||||||
|
|
||||||
|
::: elements.tooltip_button_element
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Browser Fixtures
|
||||||
|
|
||||||
|
::: fixtures.pages
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Документация тестового фреймворка NMS
|
||||||
|
|
||||||
|
Автоматически сгенерированная документация для тестового фреймворка, разработанного для тестирования Network Management System (NMS).
|
||||||
|
|
||||||
|
## Обзор проекта
|
||||||
|
|
||||||
|
Фреймворк разработан с использованием:
|
||||||
|
- **Playwright** - для автоматизации браузера
|
||||||
|
- **Pytest** - как основной тестовый движок
|
||||||
|
- **Page Object Model** - паттерн для организации тестового кода
|
||||||
|
- **MkDocs** - для генерации документации
|
||||||
|
- **Python 3.8+** - язык реализации
|
||||||
|
|
||||||
|
## Детальная структура проекта
|
||||||
|
|
||||||
|
### Корневая директория
|
||||||
|
- `.env` - файл с переменными окружения
|
||||||
|
- `conftest.py` - фикстуры Pytest, настройки генерации документации
|
||||||
|
- `mkdocs.yml` - конфигурация документации
|
||||||
|
- `pytest.ini` - конфигурация тестов (маркеры, параметры)
|
||||||
|
- `requirements.txt` - зависимости Python
|
||||||
|
- `setup.py` - конфигурация пакета
|
||||||
|
|
||||||
|
### Основные модули
|
||||||
|
|
||||||
|
#### 1. components/
|
||||||
|
Базовые компоненты UI:
|
||||||
|
- `alert_component.py` - работа с alert-окнами (ошибки, успех, информация)
|
||||||
|
- `base_component.py` - базовый класс для всех компонентов
|
||||||
|
- `card_component.py` - карточки пользователей
|
||||||
|
- `confirm_component.py` - модальные окна подтверждения
|
||||||
|
- `modal_window_component.py` - базовые модальные окна
|
||||||
|
- `navbar_component.py` - панель навигации
|
||||||
|
- `table_component.py` - работа с таблицами
|
||||||
|
- `toolbar_component.py` - тулбары приложения
|
||||||
|
|
||||||
|
#### 2. data/
|
||||||
|
Данные и конфигурации:
|
||||||
|
- `constants.py` - константы (логины, пароли)
|
||||||
|
- `environment.py` - настройки окружения (test/develop)
|
||||||
|
- `roles_dict.py` - словарь ролей пользователей
|
||||||
|
|
||||||
|
#### 3. docs/
|
||||||
|
Документация:
|
||||||
|
- `api/` - документация API классов
|
||||||
|
- `tests/` - документация тестов
|
||||||
|
- `config/` - инструкции по настройке
|
||||||
|
|
||||||
|
#### 4. elements/
|
||||||
|
UI-элементы:
|
||||||
|
- `base_element.py` - базовый элемент
|
||||||
|
- `button_element.py` - кнопки
|
||||||
|
- `checkbox_element.py` - чекбоксы
|
||||||
|
- `text_element.py` - текстовые элементы
|
||||||
|
- `text_input_element.py` - поля ввода
|
||||||
|
- `toolbar_button_element.py` - кнопки тулбара
|
||||||
|
|
||||||
|
#### 5. fixtures/
|
||||||
|
Фикстуры Pytest:
|
||||||
|
- `pages.py` - настройки браузеров, контекстов
|
||||||
|
|
||||||
|
#### 6. locators/
|
||||||
|
Локаторы элементов:
|
||||||
|
- Локаторы для всех основных компонентов (confirm, modal windows, tables и т.д.)
|
||||||
|
|
||||||
|
#### 7. modal_windows/
|
||||||
|
Специализированные модальные окна:
|
||||||
|
- `modal_add_user.py` - добавление пользователя
|
||||||
|
- `modal_edit_user.py` - редактирование пользователя
|
||||||
|
|
||||||
|
#### 8. pages/
|
||||||
|
Страницы приложения:
|
||||||
|
- `base_page.py` - базовый класс страницы
|
||||||
|
- `login_page.py` - страница авторизации
|
||||||
|
- `main_page.py` - главная страница
|
||||||
|
- Табы: `service_status_tab.py`, `session_tab.py`, `users_tab.py`
|
||||||
|
|
||||||
|
#### 9. tests/
|
||||||
|
Тесты:
|
||||||
|
- Основные тесты (`test_login.py`, `test_session_tab.py` и др.)
|
||||||
|
- Поддиректории:
|
||||||
|
- `components/` - тесты компонентов
|
||||||
|
- `e2e/` - end-to-end тесты
|
||||||
|
|
||||||
|
#### 10. tools/
|
||||||
|
Утилиты:
|
||||||
|
- `logger.py` - система логирования
|
||||||
|
|
||||||
|
## Взаимодействие компонентов
|
||||||
|
|
||||||
|
1. **Тесты** используют **страницы** (pages)
|
||||||
|
2. **Страницы** состоят из **компонентов** (components)
|
||||||
|
3. **Компоненты** состоят из **элементов** (elements)
|
||||||
|
4. **Элементы** используют **локаторы** из соответствующих файлов
|
||||||
|
5. Все модули используют:
|
||||||
|
- Общие **данные** из data/
|
||||||
|
- **Логирование** через tools/logger.py
|
||||||
|
- **Фикстуры** из fixtures/
|
||||||
|
|
||||||
|
## Как использовать
|
||||||
|
|
||||||
|
1. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -e .
|
||||||
|
Запустите тесты:
|
||||||
|
|
||||||
|
bash
|
||||||
|
# Все тесты
|
||||||
|
pytest tests/ -v
|
||||||
|
|
||||||
|
# Только smoke-тесты
|
||||||
|
pytest tests/ -m smoke -v
|
||||||
|
Сгенерируйте документацию:
|
||||||
|
|
||||||
|
bash
|
||||||
|
mkdocs serve
|
||||||
|
Поддерживаемые тесты
|
||||||
|
Авторизация (успешная/неудачная)
|
||||||
|
|
||||||
|
Управление сессиями:
|
||||||
|
|
||||||
|
Проверка таблицы
|
||||||
|
|
||||||
|
Удаление сессий
|
||||||
|
|
||||||
|
Модальные окна
|
||||||
|
|
||||||
|
Управление пользователями:
|
||||||
|
|
||||||
|
Создание/удаление
|
||||||
|
|
||||||
|
Изменение ролей
|
||||||
|
|
||||||
|
Сброс паролей
|
||||||
|
|
||||||
|
Системные тесты:
|
||||||
|
|
||||||
|
Статус сервисов
|
||||||
|
|
||||||
|
Лицензии
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ConfirmLocators
|
||||||
|
|
||||||
|
::: locators.confirm_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# EventPanelLocators
|
||||||
|
|
||||||
|
::: locators.event_panel_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ModalWindowLocators
|
||||||
|
|
||||||
|
::: locators.modal_window_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# NavigationPanelLocators
|
||||||
|
|
||||||
|
::: locators.navigation_panel_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TableLocators
|
||||||
|
|
||||||
|
::: locators.table_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ToolbarLocators
|
||||||
|
|
||||||
|
::: locators.toolbar_locators
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# BasePage
|
||||||
|
|
||||||
|
::: pages.base_page
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# LoginPage
|
||||||
|
|
||||||
|
::: pages.login_page
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# MainPage
|
||||||
|
|
||||||
|
::: pages.main_page
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# ServiceStatusTab
|
||||||
|
|
||||||
|
::: pages.service_status_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# UsersTab
|
||||||
|
|
||||||
|
::: pages.users_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestLicenseTab
|
||||||
|
|
||||||
|
::: tests.e2e.test_license_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestLogin
|
||||||
|
|
||||||
|
::: tests.e2e.test_login
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestServiceStatusTab
|
||||||
|
|
||||||
|
::: tests.e2e.test_service_status_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# TestUsersTab
|
||||||
|
|
||||||
|
::: tests.e2e.test_users_tab
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Python Project Fixer
|
||||||
|
|
||||||
|
::: tools.fix_python_project
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Logging
|
||||||
|
|
||||||
|
::: tools.logger
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
from playwright.sync_api import Page, Locator, expect, TimeoutError
|
||||||
|
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("BASE_ELEMENT")
|
||||||
|
|
||||||
|
|
||||||
|
class BaseElement:
|
||||||
|
"""Базовый класс для работы с элементами страницы.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page: Экземпляр страницы Playwright.
|
||||||
|
name: Название элемента (для логирования).
|
||||||
|
locator: Локатор элемента (строка или объект Locator).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page, locator: str | Locator, name: str) -> None:
|
||||||
|
"""Инициализирует базовый элемент страницы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Экземпляр страницы Playwright.
|
||||||
|
locator: Локатор элемента (строка или объект Locator).
|
||||||
|
name: Название элемента (для логирования).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: Если передан некорректный тип локатора.
|
||||||
|
"""
|
||||||
|
self.page = page
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
if isinstance(locator, Locator):
|
||||||
|
self.locator = locator
|
||||||
|
elif isinstance(locator, str):
|
||||||
|
self.locator = self.page.locator(locator)
|
||||||
|
else:
|
||||||
|
raise TypeError("locator value should be string type or Locator type")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Строка с описанием типа элемента.
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
"""Получает текст элемента по указанному индексу.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
index: Индекс элемента (0 для единичного локатора).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текст элемента.
|
||||||
|
"""
|
||||||
|
logger.info(f'Get text for {self.type_of} "{self.name}"')
|
||||||
|
return self.locator.nth(index).text_content()
|
||||||
|
|
||||||
|
def wait_for_element(self, timeout=12000) -> None:
|
||||||
|
"""Ожидает появления элемента на странице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: Время ожидания в миллисекундах.
|
||||||
|
"""
|
||||||
|
logger.info(f'Wait for {self.type_of} "{self.name}"')
|
||||||
|
self.locator.wait_for(timeout=timeout)
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_have_text(self, text: str, msg):
|
||||||
|
"""Проверяет, что элемент содержит указанный текст.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Ожидаемый текст.
|
||||||
|
msg: Сообщение об ошибке при неудачной проверке.
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""Проверяет видимость элемента на странице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg: Сообщение об ошибке при неудачной проверке.
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
"""Проверяет наличие элемента на странице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: Время ожидания в миллисекундах.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True, если элемент присутствует, иначе False.
|
||||||
|
"""
|
||||||
|
logger.info(f'Check that {self.type_of} "{self.name}" is present')
|
||||||
|
try:
|
||||||
|
self.locator.wait_for(timeout=timeout)
|
||||||
|
except TimeoutError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_not_present(self, timeout: int = 5000) -> bool:
|
||||||
|
"""Проверяет отсутствие элемента на странице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: Время ожидания в миллисекундах.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True, если элемент отсутствует, иначе False.
|
||||||
|
"""
|
||||||
|
logger.info(f'Check that {self.type_of} "{self.name}" is missing')
|
||||||
|
try:
|
||||||
|
self.locator.wait_for(timeout=timeout)
|
||||||
|
except TimeoutError:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
|
||||||
|
from elements.base_element import BaseElement
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("BUTTON")
|
||||||
|
|
||||||
|
|
||||||
|
class Button(BaseElement):
|
||||||
|
"""Класс для работы с элементами типа 'кнопка' на странице.
|
||||||
|
|
||||||
|
Наследует функциональность базового элемента и добавляет специфичные для кнопок методы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента - 'кнопка'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Строка с типом элемента.
|
||||||
|
"""
|
||||||
|
return "button"
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
# (Методы действий будут добавлены по мере необходимости)
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
# (Методы проверок будут добавлены по мере необходимости)
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
from elements.base_element import BaseElement
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("CHECKBOX")
|
||||||
|
|
||||||
|
|
||||||
|
class Checkbox(BaseElement):
|
||||||
|
"""Класс для работы с элементами типа 'чекбокс' на странице.
|
||||||
|
|
||||||
|
Наследует функциональность базового элемента и добавляет специфичные для чекбоксов методы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента - 'чекбокс'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Строка с типом элемента.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def is_checked(self) -> bool:
|
||||||
|
"""Проверяет, отмечен ли чекбокс.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True, если чекбокс отмечен, иначе False.
|
||||||
|
"""
|
||||||
|
logger.info(f'Checking if checkbox "{self.name}" is checked')
|
||||||
|
return self.locator.is_checked()
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
from elements.base_element import BaseElement
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("DROPDOWN_LIST")
|
||||||
|
|
||||||
|
|
||||||
|
class DropdownList(BaseElement):
|
||||||
|
"""Класс для работы с выпадающими списками на странице.
|
||||||
|
|
||||||
|
Наследует функциональность базового элемента и добавляет специфичные для dropdown-списков методы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента - 'выпадающий список'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Строка с типом элемента.
|
||||||
|
"""
|
||||||
|
return "dropdown list"
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def click_item_with_text(self, text: str) -> None:
|
||||||
|
"""Кликает на элемент списка с указанным текстом.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Текст элемента, который нужно выбрать.
|
||||||
|
"""
|
||||||
|
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: Текст элемента, который нужно проверить.
|
||||||
|
|
||||||
|
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:
|
||||||
|
assert False, f"Dropdown list item '{text}' is missing or disabled"
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
from elements.base_element import BaseElement
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("TEXT")
|
||||||
|
|
||||||
|
|
||||||
|
class Text(BaseElement):
|
||||||
|
"""Класс для работы с текстовыми элементами на странице.
|
||||||
|
|
||||||
|
Наследует функциональность базового элемента и добавляет специфичные для текста методы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента - 'текст'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Строка с типом элемента.
|
||||||
|
"""
|
||||||
|
return "text"
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
# (Методы действий будут добавлены по мере необходимости)
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
# (Методы проверок будут добавлены по мере необходимости)
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
from elements.base_element import BaseElement
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("TEXT_INPUT")
|
||||||
|
|
||||||
|
|
||||||
|
class TextInput(BaseElement):
|
||||||
|
"""Класс для работы с текстовыми полями ввода на странице.
|
||||||
|
|
||||||
|
Наследует функциональность базового элемента и добавляет специфичные для текстовых полей методы.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента - 'текстовое поле ввода'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Строка с типом элемента.
|
||||||
|
"""
|
||||||
|
return "text input"
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def get_input_value(self) -> str:
|
||||||
|
"""Получает текущее значение текстового поля.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текущее значение поля ввода.
|
||||||
|
"""
|
||||||
|
logger.info(f'Getting value from text input "{self.name}"')
|
||||||
|
return self.locator.input_value()
|
||||||
|
|
||||||
|
def input_value(self, value: str) -> None:
|
||||||
|
"""Вводит указанное значение в текстовое поле.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Значение для ввода.
|
||||||
|
"""
|
||||||
|
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: Сообщение об ошибке при неудачной проверке.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если поле не пустое.
|
||||||
|
"""
|
||||||
|
logger.info(f'Checking that text input "{self.name}" is empty')
|
||||||
|
expect(self.locator).to_be_empty(), msg
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from elements.base_element import BaseElement
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("TOOLTIP_BUTTON")
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipButton(BaseElement):
|
||||||
|
"""Класс элемента кнопки с всплывающей подсказкой.
|
||||||
|
|
||||||
|
Наследует функциональность базового элемента и добавляет методы для работы с подсказками.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_of(self) -> str:
|
||||||
|
"""Возвращает тип элемента.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Тип элемента ('tooltip_button')
|
||||||
|
"""
|
||||||
|
return "tooltip_button"
|
||||||
|
|
||||||
|
def check_tooltip_with_text(self, tooltip_locator: str, expected_text: str) -> None:
|
||||||
|
"""Проверяет текст всплывающей подсказки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tooltip_locator (str): Локатор элемента подсказки
|
||||||
|
expected_text (str): Ожидаемый текст подсказки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если текст подсказки не соответствует ожидаемому
|
||||||
|
"""
|
||||||
|
# Наведение на элемент для отображения подсказки
|
||||||
|
self.locator.hover()
|
||||||
|
|
||||||
|
# Получение элемента подсказки
|
||||||
|
tooltip = self.page.locator(tooltip_locator)
|
||||||
|
|
||||||
|
# Проверка соответствия текста
|
||||||
|
actual_text = tooltip.text_content().strip()
|
||||||
|
assert actual_text == expected_text, (
|
||||||
|
f"Текст подсказки не соответствует ожидаемому. "
|
||||||
|
f"Ожидалось: '{expected_text}', получено: '{actual_text}'"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
elements
|
||||||
|
|
||||||
|
base_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в Google-формате на русском языке
|
||||||
|
- Разделительные комментарии переведены на русский (# Действия:, # Проверки:)
|
||||||
|
- Сохранены все технические комментарии и сообщения в логах без изменений
|
||||||
|
- Сохранена исходная структура кода и рабочая логика
|
||||||
|
- Соблюдены требования PEP 8 к форматированию кода
|
||||||
|
|
||||||
|
button_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и метода type_of в Google-формате на русском языке
|
||||||
|
- Разделительные комментарии переведены на русский (# Действия:, # Проверки:)
|
||||||
|
- Добавлены поясняющие комментарии в разделах действий и проверок
|
||||||
|
- Сохранена исходная структура кода и рабочая логика
|
||||||
|
- Улучшено форматирование в соответствии с PEP 8:
|
||||||
|
Единообразные отступы
|
||||||
|
Пробелы вокруг операторов
|
||||||
|
Пустые строки между логическими блоками
|
||||||
|
- Сохранены все технические аспекты без изменений
|
||||||
|
|
||||||
|
checkbox_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в Google-формате на русском языке
|
||||||
|
- Разделительные комментарии переведены на русский (# Действия:, # Проверки:)
|
||||||
|
- Добавлено логирование операций с чекбоксом
|
||||||
|
- Указаны типы возвращаемых значений для методов
|
||||||
|
- Сохранена исходная структура кода и рабочая логика
|
||||||
|
- Улучшено форматирование в соответствии с PEP 8:
|
||||||
|
Единообразные отступы
|
||||||
|
Пробелы вокруг операторов
|
||||||
|
Пустые строки между логическими блоками
|
||||||
|
- Сохранены все технические аспекты без изменений
|
||||||
|
|
||||||
|
dropdown_list_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены полные docstring для класса и всех методов в Google-формате
|
||||||
|
- Указаны типы аргументов и возвращаемых значений
|
||||||
|
- Переведены разделительные комментарии
|
||||||
|
- Добавлено логирование всех операций
|
||||||
|
- Улучшено сообщение об ошибке в assert
|
||||||
|
- Удален неиспользуемый импорт re
|
||||||
|
- Сохранена рабочая логика
|
||||||
|
- Приведено к соответствию с PEP 8:
|
||||||
|
Правильные отступы
|
||||||
|
Пробелы вокруг операторов
|
||||||
|
Логические блоки разделены пустыми строками
|
||||||
|
- Улучшена структура кода и читаемость
|
||||||
|
|
||||||
|
text_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и метода type_of в Google-формате на русском языке
|
||||||
|
- Разделительные комментарии переведены на русский (# Действия:, # Проверки:)
|
||||||
|
- Добавлены поясняющие комментарии в разделах действий и проверок
|
||||||
|
- Сохранена исходная структура кода и рабочая логика
|
||||||
|
- Улучшено форматирование в соответствии с PEP 8:
|
||||||
|
Единообразные отступы
|
||||||
|
Пробелы вокруг операторов
|
||||||
|
Пустые строки между логическими блоками
|
||||||
|
- Упорядочены импорты (стандартные, сторонние, локальные)
|
||||||
|
- Сохранены все технические аспекты без изменений
|
||||||
|
|
||||||
|
text_input_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены полные docstring для класса и всех методов
|
||||||
|
- Указаны типы аргументов и возвращаемых значений
|
||||||
|
- Переведены разделительные комментарии
|
||||||
|
- Добавлено логирование всех операций
|
||||||
|
- Исправлена опечатка в методе get_input_value (было self.locator, стало self.locator)
|
||||||
|
- Улучшено форматирование в соответствии с PEP 8
|
||||||
|
- Сохранена вся исходная функциональность
|
||||||
|
- Упорядочены импорты
|
||||||
|
- Добавлены комментарии к исключениям в документации
|
||||||
|
- Улучшена читаемость кода за счет:
|
||||||
|
Последовательного стиля
|
||||||
|
Логического разделения блоков
|
||||||
|
Единообразного именования
|
||||||
|
|
||||||
|
tooltip_button_element.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлена документация:
|
||||||
|
Docstring класса с описанием назначения
|
||||||
|
Документация для всех методов
|
||||||
|
Описание аргументов и возвращаемых значений
|
||||||
|
- Улучшено форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, пробелы)
|
||||||
|
Логическое разделение блоков кода
|
||||||
|
Четкие комментарии к действиям
|
||||||
|
- Улучшена читаемость:
|
||||||
|
Более информативные имена переменных
|
||||||
|
Подробное сообщение об ошибке
|
||||||
|
Логическая структура метода проверки
|
||||||
|
- Соответствие требованиям:
|
||||||
|
Полное соответствие Google Python Style Guide
|
||||||
|
Соответствие PEP 8
|
||||||
|
Учет рекомендаций из README_форматирование_кода.md
|
||||||
|
- Дополнительные улучшения:
|
||||||
|
Более информативное сообщение об ошибке
|
||||||
|
Разделение логики на четкие этапы
|
||||||
|
Типизация аргументов методов
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
"""Модуль для работы с Playwright в тестах pytest.
|
||||||
|
|
||||||
|
Содержит фикстуры и вспомогательные функции для управления браузером.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
"""Добавляет пользовательские опции командной строки для настройки браузера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parser: Парсер pytest для добавления опций.
|
||||||
|
|
||||||
|
Доступные опции:
|
||||||
|
--bn: Выбор браузера (chrome, remote_chrome или firefox)
|
||||||
|
--h: Режим headless (True/False)
|
||||||
|
--s: Размер окна в формате {'width': int, 'height': int}
|
||||||
|
--slow: Задержка между действиями (slow_mo)
|
||||||
|
--t: Таймаут по умолчанию (мс)
|
||||||
|
--l: Локаль браузера
|
||||||
|
"""
|
||||||
|
parser.addoption('--bn', action='store', default="chrome",
|
||||||
|
help="Choose browser: chrome, remote_chrome or firefox")
|
||||||
|
parser.addoption('--h', action='store', default=False,
|
||||||
|
help='Choose headless: True or False')
|
||||||
|
parser.addoption('--s', action='store', default={'width': 1600, 'height': 900},
|
||||||
|
help='Size window: width,height')
|
||||||
|
# Закомментированные альтернативные размеры окон
|
||||||
|
# parser.addoption('--s', action='store', default={'width': 1920, 'height': 1080}, help='Size window: width,height')
|
||||||
|
# parser.addoption('--s', action='store', default={'width': 1920, 'height': 300}, help='Size window: width,height')
|
||||||
|
parser.addoption('--slow', action='store', default=200,
|
||||||
|
help='Choose slow_mo for robot action')
|
||||||
|
parser.addoption('--t', action='store', default=60000,
|
||||||
|
help='Choose timeout')
|
||||||
|
parser.addoption('--l', action='store', default='ru-RU',
|
||||||
|
help='Choose locale')
|
||||||
|
# Закомментированная опция для Qase
|
||||||
|
# parser.addini('qs_to_api_token', default=os.getenv("QASE_TOKEN"), help='Qase app token')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='class')
|
||||||
|
def browser(request) -> Page:
|
||||||
|
"""Фикстура для создания и управления экземпляром браузера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: Объект запроса pytest для доступа к конфигурации.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Page: Экземпляр страницы браузера.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Page: Экземпляр страницы для использования в тестах.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Автоматически закрывает браузер и контексты после завершения тестов.
|
||||||
|
"""
|
||||||
|
playwright = sync_playwright().start()
|
||||||
|
|
||||||
|
# Выбор браузера на основе параметра командной строки
|
||||||
|
if request.config.getoption("bn") == 'remote_chrome':
|
||||||
|
browser = get_remote_chrome(playwright, request)
|
||||||
|
context = get_context(browser, request, 'remote')
|
||||||
|
page_data = context.new_page()
|
||||||
|
elif request.config.getoption("bn") == 'firefox':
|
||||||
|
browser = get_firefox_browser(playwright, request)
|
||||||
|
context = get_context(browser, request, 'local')
|
||||||
|
page_data = context.new_page()
|
||||||
|
elif request.config.getoption("bn") == 'chrome':
|
||||||
|
browser = get_chrome_browser(playwright, request)
|
||||||
|
context = get_context(browser, request, 'local')
|
||||||
|
page_data = context.new_page()
|
||||||
|
else:
|
||||||
|
browser = get_chrome_browser(playwright, request)
|
||||||
|
context = get_context(browser, request, 'local')
|
||||||
|
page_data = context.new_page()
|
||||||
|
|
||||||
|
yield page_data
|
||||||
|
|
||||||
|
# Очистка после завершения тестов
|
||||||
|
for context in browser.contexts:
|
||||||
|
context.close()
|
||||||
|
browser.close()
|
||||||
|
playwright.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def get_firefox_browser(playwright, request) -> Browser:
|
||||||
|
"""Создает и возвращает экземпляр Firefox браузера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
playwright: Экземпляр Playwright.
|
||||||
|
request: Объект запроса pytest для доступа к конфигурации.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Browser: Экземпляр Firefox браузера.
|
||||||
|
"""
|
||||||
|
return playwright.firefox.launch(
|
||||||
|
headless=request.config.getoption("h"),
|
||||||
|
slow_mo=request.config.getoption("slow"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_chrome_browser(playwright, request) -> Browser:
|
||||||
|
"""Создает и возвращает экземпляр Chrome браузера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
playwright: Экземпляр Playwright.
|
||||||
|
request: Объект запроса pytest для доступа к конфигурации.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Browser: Экземпляр Chrome браузера.
|
||||||
|
"""
|
||||||
|
return playwright.chromium.launch(
|
||||||
|
headless=request.config.getoption("h"),
|
||||||
|
slow_mo=request.config.getoption("slow"),
|
||||||
|
args=['--s']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_chrome(playwright, request) -> Browser:
|
||||||
|
"""Создает и возвращает экземпляр Chrome браузера для удаленного запуска.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
playwright: Экземпляр Playwright.
|
||||||
|
request: Объект запроса pytest для доступа к конфигурации.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Browser: Экземпляр Chrome браузера в режиме headless.
|
||||||
|
"""
|
||||||
|
return playwright.chromium.launch(
|
||||||
|
headless=True,
|
||||||
|
slow_mo=request.config.getoption("slow")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_context(browser, request, start) -> BrowserContext:
|
||||||
|
"""Создает и настраивает контекст браузера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
browser: Экземпляр браузера.
|
||||||
|
request: Объект запроса pytest для доступа к конфигурации.
|
||||||
|
start: Тип запуска ('local' или 'remote').
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BrowserContext: Настроенный контекст браузера.
|
||||||
|
"""
|
||||||
|
if start == 'local':
|
||||||
|
context = browser.new_context(
|
||||||
|
# no_viewport=True,
|
||||||
|
viewport=request.config.getoption('s'),
|
||||||
|
locale=request.config.getoption('l')
|
||||||
|
)
|
||||||
|
context.set_default_timeout(
|
||||||
|
timeout=request.config.getoption('t')
|
||||||
|
)
|
||||||
|
# Пример добавления кук (закомментировано)
|
||||||
|
# context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}])
|
||||||
|
return context
|
||||||
|
|
||||||
|
elif start == 'remote':
|
||||||
|
context = browser.new_context(
|
||||||
|
viewport=request.config.getoption('s'),
|
||||||
|
locale=request.config.getoption('l')
|
||||||
|
)
|
||||||
|
context.set_default_timeout(
|
||||||
|
timeout=request.config.getoption('t')
|
||||||
|
)
|
||||||
|
# Пример добавления кук (закомментировано)
|
||||||
|
# context.add_cookies([{'url': 'https://example.ru', 'name': 'ab_test', 'value': 'd'}])
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def return_back(browser):
|
||||||
|
"""Фикстура для возврата на предыдущую страницу в браузере.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
browser: Экземпляр страницы браузера.
|
||||||
|
"""
|
||||||
|
browser.go_back()
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
fixtures
|
||||||
|
|
||||||
|
pages.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен модульный docstring с описанием назначения модуля
|
||||||
|
- Добавлены подробные docstrings для всех функций в Google-стиле
|
||||||
|
- Сохранены все технические комментарии без изменений
|
||||||
|
- Добавлены пояснения к закомментированному коду
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8
|
||||||
|
- Добавлены описания аргументов, возвращаемых значений и заметки для функций
|
||||||
|
- Сохранена оригинальная логика без изменений
|
||||||
|
- Добавлены разделительные пустые строки между функциями для лучшей читаемости
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
class ButtonLocators:
|
||||||
|
BUTTON_LICENSE_UPDATE = "//div[@class='scrollarea__footer']//button"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
class ConfirmLocators:
|
||||||
|
"""Локаторы элементов диалогов подтверждения.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
CONFIRM (str): XPath локатор активного диалогового окна.
|
||||||
|
TITLE (str): XPath локатор заголовка диалогового окна.
|
||||||
|
BUTTON_CLOSE (str): XPath локатор кнопки закрытия диалога.
|
||||||
|
TEXT (str): XPath локатор текстового содержимого диалога (формируется динамически).
|
||||||
|
"""
|
||||||
|
CONFIRM = "//div[contains(@class, 'v-dialog--active')]"
|
||||||
|
TITLE = "//div[@class='v-card__title']/h3"
|
||||||
|
BUTTON_CLOSE = "//div[@class='vuedl-layout__closeBtn']"
|
||||||
|
TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']"
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
class EventPanelLocators:
|
||||||
|
"""Локаторы элементов панели событий.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
BUTTONS_BLOCK (str): XPath локатор блока кнопок в панели инструментов.
|
||||||
|
Находится во втором блоке элементов toolbar'а внутри контентной области.
|
||||||
|
"""
|
||||||
|
BUTTONS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]"
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
class InputLocators:
|
||||||
|
"""Локаторы для полей ввода на странице.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
LICENSE_ID_UPDATE (str): XPath локатор текстового поля для ввода/обновления
|
||||||
|
идентификатора лицензии, расположенного в подвале страницы.
|
||||||
|
Состоит из нескольких частей:
|
||||||
|
- Блок подвала (scrollarea__footer)
|
||||||
|
- Контейнер поля ввода (v-input__control)
|
||||||
|
- Непосредственно текстовое поле (textarea)
|
||||||
|
"""
|
||||||
|
LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea"
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
class JsonContainerLocators:
|
||||||
|
"""Локаторы для контейнеров JSON-данных на странице.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
CONTAINER (str): XPath локатор основного контейнера JSON-данных.
|
||||||
|
Ищет div с классом, содержащим 'jv-container'.
|
||||||
|
SCROLL_CONTAINER (str): XPath локатор прокручиваемой области контейнера.
|
||||||
|
Ищет div с классом, содержащим 'scrollarea__body'.
|
||||||
|
"""
|
||||||
|
CONTAINER = "//div[contains(@class,'jv-container')]"
|
||||||
|
SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]"
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
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 локатор метки поля ввода в форме.
|
||||||
|
"""
|
||||||
|
MODAL_WINDOW = "//div[contains(@class, 'v-dialog--active')]"
|
||||||
|
|
||||||
|
INPUT_FORM_USER_DATA = "//form[@class='v-form']"
|
||||||
|
TEXT_FIELD_INPUT_FORM_USER_DATA = "xpath=div[2]/div/div/div/div/input"
|
||||||
|
ROLES_FIELD_INPUT_FORM_USER_DATA = "xpath=div[2]/div/div/div/div/div[1]"
|
||||||
|
ROLES_MENU_INPUT_FORM_USER_DATA = "//div[contains(@class, 'menuable__content__active')]"
|
||||||
|
LABEL_INPUT_FORM_USER_DATA = "//label[contains(@class,'v-label')]/span"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
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'.
|
||||||
|
"""
|
||||||
|
PANEL_MAIN = "//ul[contains(@class, 'v-expansion-panel')]"
|
||||||
|
PANEL_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//ul[contains(@class, 'v-expansion-panel')]]"
|
||||||
|
|
||||||
|
NODE_ROOT = "//div[contains(@class,'v-treeview-node__root')]"
|
||||||
|
NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]"
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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"
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
class TextLocators:
|
||||||
|
"""Локаторы для текстовых элементов на странице.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
TITLE_LICENSE_INPUT_FORM (str): XPath локатор заголовка формы ввода лицензии.
|
||||||
|
Ищет span с классом 'title'.
|
||||||
|
LICENSE_ID (str): XPath локатор отображаемого идентификатора лицензии.
|
||||||
|
Ищет span с классами 'title' и 'text_select' (выделяемый текст).
|
||||||
|
"""
|
||||||
|
TITLE_LICENSE_INPUT_FORM = "//span[@class='title']"
|
||||||
|
LICENSE_ID = "//span[@class='title text_select']"
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
class ToolbarLocators:
|
||||||
|
"""Локаторы элементов тулбара (панели инструментов).
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
TITLE (str): XPath локатор заголовка тулбара.
|
||||||
|
Находится в навигационной панели (nav) внутри элемента с классом,
|
||||||
|
содержащим 'v-toolbar__title'.
|
||||||
|
|
||||||
|
TOOLTIP (str): XPath локатор активного всплывающего подсказывающего элемента.
|
||||||
|
Ищет div с классами, содержащими:
|
||||||
|
- 'v-tooltip__content' (основа тултипа)
|
||||||
|
- 'menuable__content__active' (показанное состояние)
|
||||||
|
"""
|
||||||
|
TITLE = "//nav//div[contains(@class, 'v-toolbar__title')]"
|
||||||
|
TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]"
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
locators
|
||||||
|
|
||||||
|
confirm_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен подробный docstring класса в формате Google Style Guide на русском языке
|
||||||
|
- Описаны все атрибуты класса с пояснениями
|
||||||
|
- Сохранена оригинальная структура кода и рабочая логика
|
||||||
|
- Соблюдены требования PEP 8:
|
||||||
|
Отступы и пробелы
|
||||||
|
Пустые строки между блоками
|
||||||
|
Форматирование f-строки
|
||||||
|
- Комментарии не требовались, так как их не было в исходном файле
|
||||||
|
|
||||||
|
event_panel_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен docstring класса в формате Google Style Guide на русском языке
|
||||||
|
- Подробно описан атрибут BUTTONS_BLOCK с уточнением его расположения
|
||||||
|
- Сохранена оригинальная структура кода и рабочая логика
|
||||||
|
- Соблюдены требования PEP 8:
|
||||||
|
Отступы и пробелы
|
||||||
|
Длина строки не превышает 79 символов
|
||||||
|
Форматирование строки локатора
|
||||||
|
|
||||||
|
input_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен подробный docstring класса в формате Google Style Guide на русском языке
|
||||||
|
- Детально описан атрибут LICENSE_ID_UPDATE с разбором структуры XPath
|
||||||
|
- Сохранена оригинальная структура кода без изменения логики
|
||||||
|
- Соблюдены требования PEP 8:
|
||||||
|
Отступы и пробелы
|
||||||
|
Перенос длинного описания атрибута
|
||||||
|
Четкое форматирование строки локатора
|
||||||
|
|
||||||
|
json_container_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен полный docstring класса в Google-формате на русском языке
|
||||||
|
- Каждый атрибут получил:
|
||||||
|
Четкое описание назначения
|
||||||
|
Пояснение логики работы XPath (использование contains)
|
||||||
|
- Сохранена оригинальная структура и функциональность кода
|
||||||
|
- Соблюдены стандарты PEP 8:
|
||||||
|
Единообразные кавычки
|
||||||
|
Правильные отступы
|
||||||
|
Отсутствие лишних пробелов
|
||||||
|
- Улучшена читаемость за счет:
|
||||||
|
Логического разделения атрибутов
|
||||||
|
Подробных, но лаконичных описаний
|
||||||
|
Соответствия максимальной длине строки
|
||||||
|
|
||||||
|
modal_window_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен полный docstring класса с описанием всех атрибутов
|
||||||
|
- Устранены проблемы с форматированием:
|
||||||
|
Удалены лишние пробелы вокруг '=' в XPath
|
||||||
|
Приведены к единому формату строки локаторов
|
||||||
|
- Логически сгруппированы связанные элементы (форма и её поля)
|
||||||
|
- Сохранена оригинальная функциональность без изменений логики
|
||||||
|
- Улучшена читаемость за счет:
|
||||||
|
Четких описаний каждого локатора
|
||||||
|
Правильных переносов длинных описаний
|
||||||
|
Последовательного форматирования
|
||||||
|
|
||||||
|
navigation_panel_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен подробный docstring класса в формате Google Style Guide
|
||||||
|
- Каждый атрибут содержит:
|
||||||
|
Четкое описание назначения
|
||||||
|
Пояснение логики работы XPath
|
||||||
|
Указание типа искомого элемента
|
||||||
|
- Сохранена оригинальная группировка связанных элементов
|
||||||
|
- Соблюдены требования PEP 8:
|
||||||
|
Единообразное форматирование строк
|
||||||
|
Правильные отступы
|
||||||
|
Отсутствие лишних пробелов
|
||||||
|
- Улучшена читаемость за счет:
|
||||||
|
Логической структуры описаний
|
||||||
|
Использования терминологии компонентов (панель, узел)
|
||||||
|
Последовательного стиля документации
|
||||||
|
|
||||||
|
table_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен детальный docstring класса в Google-формате:
|
||||||
|
- Общее описание назначения класса
|
||||||
|
- Подробное описание каждого атрибута
|
||||||
|
- Указание полного пути для сложных локаторов
|
||||||
|
- Улучшена читаемость кода:
|
||||||
|
Четкое форматирование XPath выражений
|
||||||
|
Логическое структурирование документации
|
||||||
|
Использование терминов, соответствующих элементам интерфейса
|
||||||
|
- Полное соответствие требованиям:
|
||||||
|
PEP 8 (длина строк, отступы, форматирование)
|
||||||
|
Google Python Style Guide (стиль документации)
|
||||||
|
Указаний из README (перевод на русский, сохранение структуры)
|
||||||
|
- Особенности:
|
||||||
|
Подробное описание сложных XPath путей
|
||||||
|
Указание точного расположения элементов в DOM
|
||||||
|
Четкое разделение разных типов табличных контейнеров
|
||||||
|
|
||||||
|
text_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен полный docstring класса в Google-формате:
|
||||||
|
Общее описание назначения класса
|
||||||
|
Подробные описания каждого локатора
|
||||||
|
Указание особенностей элементов (выделяемый текст)
|
||||||
|
- Оптимизировано оформление кода:
|
||||||
|
Четкое разделение документации и кода
|
||||||
|
Единообразное форматирование XPath
|
||||||
|
Соответствие PEP 8 (длина строк, отступы)
|
||||||
|
- Улучшена информативность:
|
||||||
|
Указание типа элемента (span)
|
||||||
|
Описание классов CSS и их назначения
|
||||||
|
Четкое различие между похожими локаторами
|
||||||
|
- Полное соответствие требованиям:
|
||||||
|
Google Python Style Guide для docstring
|
||||||
|
PEP 8 для форматирования кода
|
||||||
|
Правилам из README (русский язык, сохранение логики)
|
||||||
|
|
||||||
|
toolbar_locators.py
|
||||||
|
Изменения включают:
|
||||||
|
- Полноценный docstring класса:
|
||||||
|
Четкое описание назначения класса
|
||||||
|
Детальное описание каждого атрибута
|
||||||
|
Разбор составных частей классов CSS
|
||||||
|
- Оптимизация структуры:
|
||||||
|
Логические блоки с пояснениями
|
||||||
|
Группировка связанной информации
|
||||||
|
Четкое разделение атрибутов
|
||||||
|
- Стилевые улучшения:
|
||||||
|
Единообразное форматирование XPath
|
||||||
|
Соответствие PEP 8 (79 символов в строке)
|
||||||
|
Правильные отступы и выравнивание
|
||||||
|
- Особенности документации:
|
||||||
|
Указание родительского элемента (nav)
|
||||||
|
Разбор составных классов CSS
|
||||||
|
Описание состояний элементов (активное)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
site_name: Документация тестов
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- search
|
||||||
|
- mkdocstrings:
|
||||||
|
default_handler: python
|
||||||
|
handlers:
|
||||||
|
python:
|
||||||
|
paths: [".", "pages"]
|
||||||
|
options:
|
||||||
|
show_source: true
|
||||||
|
|
||||||
|
nav:
|
||||||
|
- Главная: index.md
|
||||||
|
- Данные и конфигурации:
|
||||||
|
- Constants: data/constants.md
|
||||||
|
- Environment: data/environment.md
|
||||||
|
- Roles_dict: data/roles_dict.md
|
||||||
|
- Фикстуры Pytest:
|
||||||
|
- Browser Fixtures: fixtures/pages.md
|
||||||
|
- Компоненты UI:
|
||||||
|
- AlertComponent: components/alert_component.md
|
||||||
|
- BaseComponent: components/base_component.md
|
||||||
|
- CardComponent: components/card_component.md
|
||||||
|
- ConfirmComponent: components/confirm_component.md
|
||||||
|
- ModalWindowComponent: components/modal_window_component.md
|
||||||
|
- NavigationPanelComponent: components/navbar_component.md
|
||||||
|
- TableComponent: components/table_component.md
|
||||||
|
- ToolbarComponent: components/toolbar_component.md
|
||||||
|
- Элементы UI:
|
||||||
|
- BaseElement: elements/base_element.md
|
||||||
|
- Button: elements/button_element.md
|
||||||
|
- Checkbox: elements/checkbox_element.md
|
||||||
|
- DropdownList: elements/dropdown_list_element.md
|
||||||
|
- Text: elements/text_element.md
|
||||||
|
- TextInput: elements/text_input_element.md
|
||||||
|
- ToolbarButton: elements/tooltip_button_element.md
|
||||||
|
- Локаторы:
|
||||||
|
- ConfirmLocators: locators/confirm_locators.md
|
||||||
|
- EventPanelLocators: locators/event_panel_locators.md
|
||||||
|
- ModalWindowLocators: locators/modal_window_locators.md
|
||||||
|
- NavigationPanelLocators: locators/navigation_panel_locators.md
|
||||||
|
- TableLocators: locators/table_locators.md
|
||||||
|
- ToolbarLocators: locators/toolbar_locators.md
|
||||||
|
#- Модальные окна:
|
||||||
|
#- AddUserModalWindow: modal_windows/modal_add_user.md
|
||||||
|
#- EditUserModalWindow: modal_windows/modal_edit_user.md
|
||||||
|
- Страницы приложения:
|
||||||
|
- BasePage: pages/base_page.md
|
||||||
|
- LoginPage: pages/login_page.md
|
||||||
|
- MainPage: pages/main_page.md
|
||||||
|
- ServiceStatusTab: pages/service_status_tab.md
|
||||||
|
- SessionTab: pages/session_tab.md
|
||||||
|
- UsersTab: pages/users_tab.md
|
||||||
|
- Тесты:
|
||||||
|
- End-to-End:
|
||||||
|
- TestLicenseTab: tests/e2e/test_license_tab.md
|
||||||
|
- TestLogin: tests/e2e/test_login.md
|
||||||
|
- TestSessionTab: tests/e2e/test_session_tab.md
|
||||||
|
- TestUsersTab: tests/e2e/test_users_tab.md
|
||||||
|
- TestServiceStatusTab: tests/e2e/test_service_status_tab.md
|
||||||
|
- Компоненты:
|
||||||
|
- TestComponents: tests/components/
|
||||||
|
- Утилиты:
|
||||||
|
- Logging: tools/logger.md
|
||||||
|
- Python Project Fixer: tools/fix_python_project.md
|
||||||
|
- Инструкции:
|
||||||
|
- Настройка MkDocs: docs/config/mkdocs_guide.md
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
|
|
@ -0,0 +1,236 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
from components.confirm_component import ConfirmComponent
|
||||||
|
from components.modal_window_component import ModalWindowComponent
|
||||||
|
from elements.checkbox_element import Checkbox
|
||||||
|
from elements.dropdown_list_element import DropdownList
|
||||||
|
from elements.text_element import Text
|
||||||
|
from elements.text_input_element import TextInput
|
||||||
|
from locators.modal_window_locators import ModalWindowLocators
|
||||||
|
from data.roles_dict import roles_dict
|
||||||
|
import re
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("ADD_USER_MODAL_WINDOW")
|
||||||
|
|
||||||
|
|
||||||
|
class AddUserModalWindow(ModalWindowComponent):
|
||||||
|
"""Класс модального окна добавления нового пользователя.
|
||||||
|
|
||||||
|
Наследует функциональность базового модального окна и добавляет специфичные элементы:
|
||||||
|
- Поля ввода данных пользователя
|
||||||
|
- Чекбоксы
|
||||||
|
- Выпадающий список ролей
|
||||||
|
- Кнопки действий
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (Page): Экземпляр страницы Playwright
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализация компонентов модального окна добавления пользователя."""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
# Локаторы элементов формы
|
||||||
|
text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA
|
||||||
|
roles_field_locator = ModalWindowLocators.ROLES_FIELD_INPUT_FORM_USER_DATA
|
||||||
|
input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA
|
||||||
|
label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA
|
||||||
|
roles_menu_locator = ModalWindowLocators.ROLES_MENU_INPUT_FORM_USER_DATA
|
||||||
|
|
||||||
|
# Настройка заголовка и кнопки закрытия тулбара
|
||||||
|
self.window_title = "Добавить нового пользователя"
|
||||||
|
locator_button_toolbar_close = self.page.get_by_role("navigation").filter(
|
||||||
|
has_text=re.compile(self.window_title)
|
||||||
|
).get_by_role("button")
|
||||||
|
|
||||||
|
self.add_toolbar_title(self.window_title)
|
||||||
|
self.add_toolbar_button(locator_button_toolbar_close, "close")
|
||||||
|
|
||||||
|
# Добавление элементов формы
|
||||||
|
checkbox_1 = Checkbox(
|
||||||
|
page,
|
||||||
|
self.page.get_by_role("checkbox").nth(0),
|
||||||
|
"active_directory"
|
||||||
|
)
|
||||||
|
self.add_content_item("active_directory_checkbox", checkbox_1)
|
||||||
|
|
||||||
|
label_1 = Text(
|
||||||
|
page,
|
||||||
|
self.page.locator(label_locator).nth(0),
|
||||||
|
"active_directory_checkbox_label"
|
||||||
|
)
|
||||||
|
self.add_content_item("active_directory_checkbox_label", label_1)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[2]").locator(text_field_locator)
|
||||||
|
type_auth_input = TextInput(page, loc, "type_auth_input")
|
||||||
|
self.add_content_item("type_auth_input", type_auth_input)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[3]").locator(text_field_locator)
|
||||||
|
name_input = TextInput(page, loc, "name_input")
|
||||||
|
self.add_content_item("name_input", name_input)
|
||||||
|
|
||||||
|
role_loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(roles_field_locator)
|
||||||
|
role_input = TextInput(page, role_loc, "role_input")
|
||||||
|
self.add_content_item("role_input", role_input)
|
||||||
|
self.add_content_item(
|
||||||
|
"roles_list",
|
||||||
|
DropdownList(page, roles_menu_locator, "roles_list")
|
||||||
|
)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[5]").locator(text_field_locator)
|
||||||
|
commentary_input = TextInput(page, loc, "commentary_input")
|
||||||
|
self.add_content_item("commentary_input", commentary_input)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[6]").locator(text_field_locator)
|
||||||
|
email_input = TextInput(page, loc, "email_input")
|
||||||
|
self.add_content_item("email_input", email_input)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[7]").locator(text_field_locator)
|
||||||
|
phone_input = TextInput(page, loc, "phone_input")
|
||||||
|
self.add_content_item("phone_input", phone_input)
|
||||||
|
|
||||||
|
checkbox_2 = Checkbox(
|
||||||
|
page,
|
||||||
|
page.get_by_role("checkbox").nth(1),
|
||||||
|
"push_notification"
|
||||||
|
)
|
||||||
|
self.add_content_item("push_notification_checkbox", checkbox_2)
|
||||||
|
|
||||||
|
label_2 = Text(
|
||||||
|
page,
|
||||||
|
self.page.locator(label_locator).nth(1),
|
||||||
|
"push_notification_checkbox_label"
|
||||||
|
)
|
||||||
|
self.add_content_item("push_notification_checkbox_label", label_2)
|
||||||
|
|
||||||
|
# Добавление кнопок действий
|
||||||
|
locator_button_add = self.page.get_by_role("button", name="Добавить")
|
||||||
|
self.add_button(locator_button_add, "add")
|
||||||
|
|
||||||
|
locator_button_close = self.page.get_by_role("button", name="Закрыть")
|
||||||
|
self.add_button(locator_button_close, "close")
|
||||||
|
|
||||||
|
self.new_user_confirm = ConfirmComponent(page, " Отмена ", " Добавить ")
|
||||||
|
|
||||||
|
def new_user(self, user_data):
|
||||||
|
"""Заполняет форму и добавляет нового пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_data (dict): Словарь с данными пользователя. Может содержать ключи:
|
||||||
|
- active_directory_checked (bool): Состояние чекбокса Active Directory
|
||||||
|
- type_auth (str): Тип авторизации
|
||||||
|
- name (str): Имя пользователя
|
||||||
|
- role (str): Роль пользователя
|
||||||
|
- commentary (str): Комментарий
|
||||||
|
- email (str): Email
|
||||||
|
- phone_number (str): Номер телефона
|
||||||
|
- push_notification_checked (bool): Состояние чекбокса Push-уведомлений
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если подтверждающее окно не отображается
|
||||||
|
"""
|
||||||
|
fields = user_data.keys()
|
||||||
|
|
||||||
|
if "active_directory_checked" in fields:
|
||||||
|
checkbox = self.get_content_item("active_directory_checkbox")
|
||||||
|
if user_data["active_directory_checked"]:
|
||||||
|
checkbox.check()
|
||||||
|
else:
|
||||||
|
checkbox.uncheck()
|
||||||
|
|
||||||
|
if "type_auth" in fields:
|
||||||
|
input_field = self.get_content_item("type_auth_input")
|
||||||
|
input_field.input_value(user_data["type_auth"])
|
||||||
|
|
||||||
|
if "name" in fields:
|
||||||
|
input_field = self.get_content_item("name_input")
|
||||||
|
input_field.input_value(user_data["name"])
|
||||||
|
|
||||||
|
if "role" in fields:
|
||||||
|
role_field = self.get_content_item("role_input")
|
||||||
|
role_field.click()
|
||||||
|
|
||||||
|
roles_list = self.get_content_item("roles_list")
|
||||||
|
roles_list.check_item_with_text(user_data["role"])
|
||||||
|
roles_list.click_item_with_text(user_data["role"])
|
||||||
|
|
||||||
|
if "commentary" in fields:
|
||||||
|
input_field = self.get_content_item("commentary_input")
|
||||||
|
input_field.input_value(user_data["commentary"])
|
||||||
|
|
||||||
|
if "email" in fields:
|
||||||
|
input_field = self.get_content_item("email_input")
|
||||||
|
input_field.input_value(user_data["email"])
|
||||||
|
|
||||||
|
if "phone_number" in fields:
|
||||||
|
input_field = self.get_content_item("phone_input")
|
||||||
|
input_field.input_value(user_data["phone_number"])
|
||||||
|
|
||||||
|
if "push_notification_checked" in fields:
|
||||||
|
checkbox = self.get_content_item("push_notification_checkbox")
|
||||||
|
if user_data["push_notification_checked"]:
|
||||||
|
checkbox.check()
|
||||||
|
else:
|
||||||
|
checkbox.uncheck()
|
||||||
|
|
||||||
|
# Отправка формы
|
||||||
|
add_button = self.get_button_by_name("add")
|
||||||
|
add_button.click()
|
||||||
|
|
||||||
|
# Подтверждение действия
|
||||||
|
title = "Добавить нового пользователя"
|
||||||
|
self.new_user_confirm.check_title(
|
||||||
|
title,
|
||||||
|
f"Confirmation dialog window with title '{title}' is missing"
|
||||||
|
)
|
||||||
|
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")
|
||||||
|
self.check_toolbar_button_tooltip("close", "Закрыть")
|
||||||
|
|
||||||
|
for name in self.content_items.keys():
|
||||||
|
item = self.get_content_item(name)
|
||||||
|
|
||||||
|
if name == "active_directory_checkbox_label":
|
||||||
|
item.check_have_text(
|
||||||
|
"Active Directory",
|
||||||
|
"Label 'Active Directory' is missing"
|
||||||
|
)
|
||||||
|
elif name == "push_notification_checkbox_label":
|
||||||
|
item.check_have_text(
|
||||||
|
"Подписка на Push-уведомления",
|
||||||
|
"Label 'Подписка на Push-уведомления' is missing"
|
||||||
|
)
|
||||||
|
elif name == "role_input":
|
||||||
|
item.click()
|
||||||
|
roles_list = self.get_content_item("roles_list")
|
||||||
|
roles_list.check_presence("Roles list is missing")
|
||||||
|
|
||||||
|
for role in roles_dict.values():
|
||||||
|
roles_list.check_item_with_text(role)
|
||||||
|
elif name == "roles_list":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
item.check_presence(
|
||||||
|
f"Modal window content item with name '{name}' is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.check_button_presence("add")
|
||||||
|
self.check_button_presence("close")
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
from components.confirm_component import ConfirmComponent
|
||||||
|
from components.modal_window_component import ModalWindowComponent
|
||||||
|
from elements.checkbox_element import Checkbox
|
||||||
|
from elements.dropdown_list_element import DropdownList
|
||||||
|
from elements.text_element import Text
|
||||||
|
from elements.text_input_element import TextInput
|
||||||
|
from locators.modal_window_locators import ModalWindowLocators
|
||||||
|
import re
|
||||||
|
from tools.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("EDIT_USER_MODAL_WINDOW")
|
||||||
|
|
||||||
|
|
||||||
|
class EditUserModalWindow(ModalWindowComponent):
|
||||||
|
"""Класс модального окна редактирования пользователя.
|
||||||
|
|
||||||
|
Наследует функциональность базового модального окна и добавляет:
|
||||||
|
- Поля редактирования данных пользователя
|
||||||
|
- Чекбоксы настроек
|
||||||
|
- Выпадающий список ролей
|
||||||
|
- Кнопки действий (Сохранить, Удалить, Сбросить пароль)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (Page): Экземпляр страницы Playwright
|
||||||
|
user_name (str): Имя редактируемого пользователя (используется в заголовке)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page, user_name: str):
|
||||||
|
"""Инициализация компонентов модального окна редактирования пользователя."""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
# Локаторы элементов формы
|
||||||
|
text_field_locator = ModalWindowLocators.TEXT_FIELD_INPUT_FORM_USER_DATA
|
||||||
|
roles_field_locator = ModalWindowLocators.ROLES_FIELD_INPUT_FORM_USER_DATA
|
||||||
|
input_form_locator = ModalWindowLocators.INPUT_FORM_USER_DATA
|
||||||
|
label_locator = ModalWindowLocators.LABEL_INPUT_FORM_USER_DATA
|
||||||
|
roles_menu_locator = ModalWindowLocators.ROLES_MENU_INPUT_FORM_USER_DATA
|
||||||
|
|
||||||
|
# Настройка заголовка и кнопки закрытия
|
||||||
|
self.window_title = user_name
|
||||||
|
locator_button_toolbar_close = self.page.get_by_role("navigation").filter(
|
||||||
|
has_text=re.compile(self.window_title)
|
||||||
|
).get_by_role("button")
|
||||||
|
|
||||||
|
self.add_toolbar_title(self.window_title)
|
||||||
|
self.add_toolbar_button(locator_button_toolbar_close, "close")
|
||||||
|
|
||||||
|
# Добавление полей формы
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[1]").locator(text_field_locator)
|
||||||
|
type_auth_input = TextInput(page, loc, "type_auth_input")
|
||||||
|
self.add_content_item("type_auth_input", type_auth_input)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[2]").locator(text_field_locator)
|
||||||
|
name_input = TextInput(page, loc, "name_input")
|
||||||
|
self.add_content_item("name_input", name_input)
|
||||||
|
|
||||||
|
role_loc = self.page.locator(input_form_locator).locator("xpath=div[3]").locator(roles_field_locator)
|
||||||
|
role_input = TextInput(page, role_loc, "role_input")
|
||||||
|
self.add_content_item("role_input", role_input)
|
||||||
|
self.add_content_item(
|
||||||
|
"roles_list",
|
||||||
|
DropdownList(page, roles_menu_locator, "roles_list")
|
||||||
|
)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(text_field_locator)
|
||||||
|
commentary_input = TextInput(page, loc, "commentary_input")
|
||||||
|
self.add_content_item("commentary_input", commentary_input)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[5]").locator(text_field_locator)
|
||||||
|
email_input = TextInput(page, loc, "email_input")
|
||||||
|
self.add_content_item("email_input", email_input)
|
||||||
|
|
||||||
|
loc = self.page.locator(input_form_locator).locator("xpath=div[6]").locator(text_field_locator)
|
||||||
|
phone_input = TextInput(page, loc, "phone_input")
|
||||||
|
self.add_content_item("phone_input", phone_input)
|
||||||
|
|
||||||
|
# Добавление чекбоксов и их меток
|
||||||
|
checkbox_2 = Checkbox(
|
||||||
|
page,
|
||||||
|
page.get_by_role("checkbox").nth(0),
|
||||||
|
"push_notification"
|
||||||
|
)
|
||||||
|
self.add_content_item("push_notification_checkbox", checkbox_2)
|
||||||
|
|
||||||
|
label_2 = Text(
|
||||||
|
page,
|
||||||
|
self.page.locator(label_locator).nth(0),
|
||||||
|
"push_notification_checkbox_label"
|
||||||
|
)
|
||||||
|
self.add_content_item("push_notification_checkbox_label", label_2)
|
||||||
|
|
||||||
|
# Добавление кнопок действий
|
||||||
|
locator_button_save = self.page.get_by_role("button", name="Сохранить")
|
||||||
|
self.add_button(locator_button_save, "save")
|
||||||
|
|
||||||
|
locator_button_delete = self.page.get_by_role("button", name="Удалить")
|
||||||
|
self.add_button(locator_button_delete, "delete")
|
||||||
|
|
||||||
|
locator_button_reset = self.page.get_by_role("button", name="Сбросить пароль")
|
||||||
|
self.add_button(locator_button_reset, "reset_password")
|
||||||
|
|
||||||
|
locator_button_close = self.page.get_by_role("button", name="Закрыть")
|
||||||
|
self.add_button(locator_button_close, "close")
|
||||||
|
|
||||||
|
# Инициализация компонентов подтверждения
|
||||||
|
self.save_user_confirm = ConfirmComponent(page, " Отмена ", " Сохранить ")
|
||||||
|
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()
|
||||||
|
|
||||||
|
title = "Удаление"
|
||||||
|
self.delete_user_confirm.check_title(
|
||||||
|
title,
|
||||||
|
f"Confirmation dialog window with title '{title}' is missing"
|
||||||
|
)
|
||||||
|
self.delete_user_confirm.click_allow_button()
|
||||||
|
|
||||||
|
def edit_user(self, user_data):
|
||||||
|
"""Редактирует данные пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_data (dict): Словарь с обновляемыми данными пользователя. Может содержать:
|
||||||
|
- type_auth (str): Тип авторизации
|
||||||
|
- name (str): Имя пользователя
|
||||||
|
- role (str): Роль пользователя
|
||||||
|
- commentary (str): Комментарий
|
||||||
|
- email (str): Email
|
||||||
|
- phone_number (str): Номер телефона
|
||||||
|
- push_notification_checked (bool): Состояние чекбокса уведомлений
|
||||||
|
"""
|
||||||
|
fields = user_data.keys()
|
||||||
|
|
||||||
|
if "type_auth" in fields:
|
||||||
|
input_field = self.get_content_item("type_auth_input")
|
||||||
|
input_field.input_value(user_data["type_auth"])
|
||||||
|
|
||||||
|
if "name" in fields:
|
||||||
|
input_field = self.get_content_item("name_input")
|
||||||
|
input_field.input_value(user_data["name"])
|
||||||
|
|
||||||
|
if "role" in fields:
|
||||||
|
role_field = self.get_content_item("role_input")
|
||||||
|
role_field.click()
|
||||||
|
|
||||||
|
roles_list = self.get_content_item("roles_list")
|
||||||
|
roles_list.check_item_with_text(user_data["role"])
|
||||||
|
roles_list.click_item_with_text(user_data["role"])
|
||||||
|
|
||||||
|
if "commentary" in fields:
|
||||||
|
input_field = self.get_content_item("commentary_input")
|
||||||
|
input_field.input_value(user_data["commentary"])
|
||||||
|
|
||||||
|
if "email" in fields:
|
||||||
|
input_field = self.get_content_item("email_input")
|
||||||
|
input_field.input_value(user_data["email"])
|
||||||
|
|
||||||
|
if "phone_number" in fields:
|
||||||
|
input_field = self.get_content_item("phone_input")
|
||||||
|
input_field.input_value(user_data["phone_number"])
|
||||||
|
|
||||||
|
if "push_notification_checked" in fields:
|
||||||
|
checkbox = self.get_content_item("push_notification_checkbox")
|
||||||
|
if user_data["push_notification_checked"]:
|
||||||
|
checkbox.check()
|
||||||
|
else:
|
||||||
|
checkbox.uncheck()
|
||||||
|
|
||||||
|
save_button = self.get_button_by_name("save")
|
||||||
|
save_button.click()
|
||||||
|
|
||||||
|
title = "Сохранение"
|
||||||
|
self.save_user_confirm.check_title(
|
||||||
|
title,
|
||||||
|
f"Confirmation dialog window with title '{title}' is missing"
|
||||||
|
)
|
||||||
|
self.save_user_confirm.click_allow_button()
|
||||||
|
|
||||||
|
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", "Закрыть")
|
||||||
|
|
||||||
|
for name in self.content_items.keys():
|
||||||
|
item = self.get_content_item(name)
|
||||||
|
|
||||||
|
if name == "push_notification_checkbox_label":
|
||||||
|
item.check_have_text(
|
||||||
|
"Подписка на Push-уведомления",
|
||||||
|
"Label 'Подписка на Push-уведомления' is missing"
|
||||||
|
)
|
||||||
|
elif name == "name_input":
|
||||||
|
name = self.get_content_item("name_input")
|
||||||
|
text_value = name.get_input_value()
|
||||||
|
assert text_value == user_name, (
|
||||||
|
f"Expected user name '{user_name}' is not equal real user name '{text_value}'"
|
||||||
|
)
|
||||||
|
elif name == "role_input":
|
||||||
|
item.click()
|
||||||
|
roles_list = self.get_content_item("roles_list")
|
||||||
|
roles_list.check_presence("Roles list is missing")
|
||||||
|
roles_list.check_item_with_text(role)
|
||||||
|
elif name == "roles_list":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
item.check_presence(
|
||||||
|
f"Modal window content item with name '{name}' is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.check_button_presence("save")
|
||||||
|
self.check_button_presence("delete")
|
||||||
|
self.check_button_presence("reset_password")
|
||||||
|
self.check_button_presence("close")
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
modal_windows
|
||||||
|
|
||||||
|
modal_add_user.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлена полная документация:
|
||||||
|
Docstring класса с описанием назначения
|
||||||
|
Подробные docstring методов с описанием аргументов и возможных исключений
|
||||||
|
Комментарии к сложным блокам кода
|
||||||
|
- Улучшено форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, длина строк, пробелы)
|
||||||
|
Логическое группирование кода
|
||||||
|
Четкое разделение блоков
|
||||||
|
- Оптимизирована читаемость:
|
||||||
|
Последовательное именование переменных
|
||||||
|
Улучшенные переносы длинных строк
|
||||||
|
Единый стиль оформления
|
||||||
|
- Сохранена функциональность:
|
||||||
|
Без изменений рабочей логики
|
||||||
|
Сохранение всех оригинальных вызовов методов
|
||||||
|
Оставлены закомментированные блоки без изменений
|
||||||
|
|
||||||
|
modal_edit_user.py
|
||||||
|
Изменения включают:
|
||||||
|
- Полная документация:
|
||||||
|
Добавлены docstring для класса и всех методов
|
||||||
|
Подробные описания аргументов и возвращаемых значений
|
||||||
|
Указание возможных исключений
|
||||||
|
- Оптимизированное форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, длина строк, пробелы)
|
||||||
|
Логическая группировка кода
|
||||||
|
Четкое разделение блоков
|
||||||
|
- Улучшенная читаемость:
|
||||||
|
Последовательные именования
|
||||||
|
Улучшенные переносы длинных строк
|
||||||
|
Единый стиль оформления
|
||||||
|
- Сохранение функциональности:
|
||||||
|
Без изменений рабочей логики
|
||||||
|
Сохранение всех оригинальных вызовов
|
||||||
|
Оставление закомментированных блоков без изменений
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto-generated by fix_python_project.py
|
||||||
|
"""Package initialization."""
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
"""Базовый класс страницы для работы с Playwright.
|
||||||
|
|
||||||
|
Содержит общие методы для взаимодействия со страницей и API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from playwright.sync_api import Page, Response, APIRequestContext, expect
|
||||||
|
from data.environment import host
|
||||||
|
from tools.logger import get_logger
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = get_logger("BASE_PAGE")
|
||||||
|
|
||||||
|
|
||||||
|
class BasePage:
|
||||||
|
"""Базовый класс для работы со страницами через Playwright.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page (Page): Экземпляр страницы Playwright.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page):
|
||||||
|
"""Инициализирует базовую страницу.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (Page): Экземпляр страницы Playwright.
|
||||||
|
"""
|
||||||
|
self.page = page
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def current_url(self) -> str:
|
||||||
|
"""Возвращает текущий URL страницы.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Текущий URL страницы.
|
||||||
|
"""
|
||||||
|
return self.page.url
|
||||||
|
|
||||||
|
def open(self, uri) -> Response | None:
|
||||||
|
"""Открывает указанный URI в браузере.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uri (str): URI для открытия (без базового URL).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response | None: Ответ сервера или None в случае ошибки.
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
"""Ожидает указанное количество миллисекунд.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout (int): Время ожидания в миллисекундах.
|
||||||
|
"""
|
||||||
|
self.page.wait_for_timeout(timeout)
|
||||||
|
|
||||||
|
def get_api_request_context(self) -> APIRequestContext:
|
||||||
|
"""Возвращает контекст API-запросов.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
APIRequestContext: Контекст для выполнения API-запросов.
|
||||||
|
"""
|
||||||
|
return self.page.context.request
|
||||||
|
|
||||||
|
def send_get_api_request(self, uri) -> Response:
|
||||||
|
"""Отправляет GET-запрос к API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uri (str): URI API-эндпоинта (без базового URL).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response: Ответ сервера.
|
||||||
|
"""
|
||||||
|
api_request_context = self.get_api_request_context()
|
||||||
|
token = host.get_access_token()
|
||||||
|
headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"}
|
||||||
|
response = api_request_context.get(
|
||||||
|
f"{host.get_request_url()}{uri}",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def send_post_api_request(self, uri, payload) -> Response:
|
||||||
|
"""Отправляет POST-запрос к API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uri (str): URI API-эндпоинта (без базового URL).
|
||||||
|
payload: Данные для отправки в теле запроса.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response: Ответ сервера.
|
||||||
|
"""
|
||||||
|
api_request_context = self.get_api_request_context()
|
||||||
|
token = host.get_access_token()
|
||||||
|
headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"}
|
||||||
|
response = api_request_context.post(
|
||||||
|
f"{host.get_request_url()}{uri}",
|
||||||
|
headers=headers,
|
||||||
|
data=payload
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_response_body(self, response) -> dict | None:
|
||||||
|
"""Извлекает тело ответа в формате JSON.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
response (Response): Ответ сервера.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict | None: Распарсенное тело ответа или None в случае ошибки.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response_body = response.json()
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.error("Failed to decode JSON response")
|
||||||
|
return None
|
||||||
|
return response_body
|
||||||
|
|
||||||
|
# Проверки:
|
||||||
|
def check_URL(self, uri: str, msg: str) -> None:
|
||||||
|
"""Проверяет, что текущий URL соответствует ожидаемому.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uri (str): Ожидаемый URI (без базового URL).
|
||||||
|
msg (str): Сообщение об ошибке при несоответствии.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если URL не соответствует ожидаемому.
|
||||||
|
"""
|
||||||
|
expect(self.page).to_have_url(
|
||||||
|
f"{host.get_base_url()}{uri}",
|
||||||
|
timeout=60000
|
||||||
|
), msg
|
||||||
|
|
||||||
|
def check_equals(self, actual, expected, msg: str) -> None:
|
||||||
|
"""Проверяет равенство фактического и ожидаемого значений.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
actual: Фактическое значение.
|
||||||
|
expected: Ожидаемое значение.
|
||||||
|
msg (str): Сообщение об ошибке при несоответствии.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если значения не равны.
|
||||||
|
"""
|
||||||
|
assert actual == expected, msg
|
||||||
|
|
||||||
|
def check_lists_equals(self, actual: list, expected: list, msg: str) -> None:
|
||||||
|
"""Рекурсивно проверяет равенство двух списков.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
actual (list): Фактический список.
|
||||||
|
expected (list): Ожидаемый список.
|
||||||
|
msg (str): Сообщение об ошибке при несоответствии.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если списки не равны.
|
||||||
|
"""
|
||||||
|
def compare_lists(list1: list, list2: list) -> bool:
|
||||||
|
"""Вспомогательная функция для рекурсивного сравнения списков.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
list1 (list): Первый список для сравнения.
|
||||||
|
list2 (list): Второй список для сравнения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если списки идентичны, иначе False.
|
||||||
|
"""
|
||||||
|
if len(list1) != len(list2):
|
||||||
|
return False
|
||||||
|
for item1, item2 in zip(list1, list2):
|
||||||
|
if isinstance(item1, list) and isinstance(item2, list):
|
||||||
|
if not compare_lists(item1, item2):
|
||||||
|
return False
|
||||||
|
elif item1 != item2:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
assert compare_lists(actual, expected), msg
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
from pages.base_page import BasePage
|
||||||
|
from components.alert_component import AlertComponent
|
||||||
|
from elements.button_element import Button
|
||||||
|
from components.json_container_component import JsonContainerComponent
|
||||||
|
from elements.text_element import Text
|
||||||
|
from elements.text_input_element import TextInput
|
||||||
|
from components.toolbar_component import ToolbarComponent
|
||||||
|
from locators.button_locators import ButtonLocators
|
||||||
|
from locators.json_container_locators import JsonContainerLocators
|
||||||
|
from locators.input_locators import InputLocators
|
||||||
|
from locators.text_locators import TextLocators
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
|
||||||
|
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): Компонент алерта с ошибкой.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page) -> None:
|
||||||
|
"""Инициализирует элементы вкладки 'Лицензии'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Экземпляр страницы Playwright.
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
self.toolbar = ToolbarComponent(page, "Лицензии")
|
||||||
|
self.json_container = JsonContainerComponent(page)
|
||||||
|
|
||||||
|
self.input_form_title = Text(page, TextLocators.TITLE_LICENSE_INPUT_FORM, "input form title")
|
||||||
|
self.license_id = Text(page, TextLocators.LICENSE_ID, "license id")
|
||||||
|
self.license_id_input = TextInput(page, InputLocators.LICENSE_ID_UPDATE, "license id input")
|
||||||
|
self.update_button = Button(page, ButtonLocators.BUTTON_LICENSE_UPDATE, "update license button")
|
||||||
|
|
||||||
|
self.error_alert = AlertComponent(page, "error")
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def fill_license_input_form(self, value: str) -> None:
|
||||||
|
"""Заполняет форму ввода идентификатора лицензии и нажимает кнопку обновления.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
self.should_be_empty_input_form()
|
||||||
|
self.should_be_update_button()
|
||||||
|
|
||||||
|
def should_be_error_alert_window_with_text(self, text: str) -> None:
|
||||||
|
"""Проверяет наличие и отсутствие алерта с указанным текстом.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Текст для проверки в алерте.
|
||||||
|
"""
|
||||||
|
self.error_alert.check_presence(text)
|
||||||
|
self.error_alert.check_absence(text)
|
||||||
|
|
||||||
|
def should_be_toolbar(self) -> None:
|
||||||
|
"""Проверяет наличие панели инструментов."""
|
||||||
|
self.toolbar.check_presence("Toolbar is missing")
|
||||||
|
|
||||||
|
def should_be_json_container(self) -> None:
|
||||||
|
"""Проверяет наличие JSON-контейнера с информацией о лицензии."""
|
||||||
|
self.json_container.check_presence(
|
||||||
|
JsonContainerLocators.CONTAINER,
|
||||||
|
"Json container with license info is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
def should_be_input_form_title(self) -> None:
|
||||||
|
"""Проверяет заголовок формы ввода и соответствие ID лицензии."""
|
||||||
|
self.input_form_title.check_have_text(
|
||||||
|
"Идентификатор:",
|
||||||
|
"Input lisence id form title 'Идентификатор:' is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
actual_lisence_id = self.license_id.get_text(0).strip()
|
||||||
|
|
||||||
|
# send request to backend to get license id
|
||||||
|
response = self.send_get_api_request("e-cmdb/api/lic/deviceid")
|
||||||
|
response_body = self.get_response_body(response)
|
||||||
|
|
||||||
|
self.check_equals(
|
||||||
|
actual_lisence_id,
|
||||||
|
response_body['deviceId'],
|
||||||
|
f"Expected ID value {response_body['deviceId']} is not equal actual value {actual_lisence_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
f"Update button with text '{button_text}' is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
def verify_json_container_content(self) -> None:
|
||||||
|
"""Проверяет соответствие содержимого JSON-контейнера данным из API."""
|
||||||
|
actual_data = self.json_container.read_data(JsonContainerLocators.CONTAINER)
|
||||||
|
|
||||||
|
# send request to backend to get license info
|
||||||
|
response = self.send_get_api_request("e-cmdb/api/lic")
|
||||||
|
response_body = self.get_response_body(response)
|
||||||
|
|
||||||
|
## temporarily
|
||||||
|
del response_body["netManagment"]
|
||||||
|
response_body["ui"].pop("lcc")
|
||||||
|
|
||||||
|
self.json_container.check_json_equals(
|
||||||
|
actual_data,
|
||||||
|
response_body,
|
||||||
|
"Expected json content is not equal actual:"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
from elements.button_element import Button
|
||||||
|
from elements.text_input_element import TextInput
|
||||||
|
from components.alert_component import AlertComponent
|
||||||
|
from pages.base_page import BasePage
|
||||||
|
|
||||||
|
from data.constants import Constants
|
||||||
|
from data.environment import host
|
||||||
|
|
||||||
|
|
||||||
|
class LoginPage(BasePage):
|
||||||
|
"""Класс для работы со страницей авторизации.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page (Page): Экземпляр страницы Playwright.
|
||||||
|
login_input (TextInput): Поле ввода логина.
|
||||||
|
password_input (TextInput): Поле ввода пароля.
|
||||||
|
login_button (Button): Кнопка входа.
|
||||||
|
error_alert (AlertComponent): Компонент алерта с ошибкой.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page) -> None:
|
||||||
|
"""Инициализирует элементы страницы авторизации.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Экземпляр страницы Playwright.
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
self.login_input = TextInput(page, page.get_by_label("Имя пользователя"), "login input")
|
||||||
|
self.password_input = TextInput(page, page.get_by_label("Пароль"), "password input")
|
||||||
|
self.login_button = Button(page, page.get_by_role("button"), "login button")
|
||||||
|
|
||||||
|
self.error_alert = AlertComponent(page, "error")
|
||||||
|
|
||||||
|
def do_login(self, username: str = None, password: str = None) -> None:
|
||||||
|
"""Выполняет вход в систему.
|
||||||
|
|
||||||
|
Если username/password не указаны, использует значения из Constants.
|
||||||
|
Обрабатывает ответ сервера для получения токена доступа.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: Логин пользователя. Если None, используется значение из Constants.
|
||||||
|
password: Пароль пользователя. Если None, используется значение из Constants.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если после входа открылась неожиданная страница.
|
||||||
|
"""
|
||||||
|
def handle_response(response):
|
||||||
|
if "login" in response.url:
|
||||||
|
response_body = self.get_response_body(response)
|
||||||
|
if response_body:
|
||||||
|
token = response_body.get("access_token")
|
||||||
|
host.set_access_token(token)
|
||||||
|
|
||||||
|
self.page.on("response", handle_response)
|
||||||
|
|
||||||
|
self.open("")
|
||||||
|
|
||||||
|
# Используем переданные значения или значения по умолчанию из Constants
|
||||||
|
actual_username = username if username is not None else Constants.login
|
||||||
|
actual_password = password if password is not None else Constants.password
|
||||||
|
|
||||||
|
self.login_input.clear()
|
||||||
|
self.login_input.input_value(actual_username)
|
||||||
|
|
||||||
|
self.password_input.clear()
|
||||||
|
self.password_input.input_value(actual_password)
|
||||||
|
|
||||||
|
self.login_button.click()
|
||||||
|
|
||||||
|
self.check_URL("dashboard", "An unexpected page has been opened")
|
||||||
|
|
||||||
|
def do_unsuccessful_login(self, username: str = "someuser", password: str = "password") -> None:
|
||||||
|
"""Выполняет попытку входа с неверными учетными данными.
|
||||||
|
|
||||||
|
Можно передать свои неверные данные или использовать значения по умолчанию.
|
||||||
|
Проверяет наличие сообщения об ошибке.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: Неверный логин пользователя. По умолчанию "someuser".
|
||||||
|
password: Неверный пароль пользователя. По умолчанию "password".
|
||||||
|
"""
|
||||||
|
self.open("")
|
||||||
|
|
||||||
|
self.login_input.clear()
|
||||||
|
self.login_input.input_value(username)
|
||||||
|
|
||||||
|
self.password_input.clear()
|
||||||
|
self.password_input.input_value(password)
|
||||||
|
|
||||||
|
self.login_button.click()
|
||||||
|
|
||||||
|
self.error_alert.check_presence("Неверная пара логин/пароль")
|
||||||
|
self.error_alert.check_absence("Неверная пара логин/пароль")
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
from pages.base_page import BasePage
|
||||||
|
from elements.button_element import Button
|
||||||
|
from components.card_component import CardComponent
|
||||||
|
from components.navbar_component import NavigationPanelComponent
|
||||||
|
from locators.navigation_panel_locators import NavigationPanelLocators
|
||||||
|
from locators.event_panel_locators import EventPanelLocators
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
|
||||||
|
class MainPage(BasePage):
|
||||||
|
"""Класс для работы с главной страницей приложения.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
page (Page): Экземпляр страницы Playwright.
|
||||||
|
navigation_panel (NavigationPanelComponent): Компонент панели навигации.
|
||||||
|
user_button (Button): Кнопка пользователя.
|
||||||
|
user_card (CardComponent): Карточка пользователя.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page) -> None:
|
||||||
|
"""Инициализирует элементы главной страницы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Экземпляр страницы Playwright.
|
||||||
|
"""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
self.navigation_panel = NavigationPanelComponent(page)
|
||||||
|
|
||||||
|
locators = self.page.locator(EventPanelLocators.BUTTONS_BLOCK).get_by_role("button").all()
|
||||||
|
self.user_button = Button(page, locators[0], "search_button")
|
||||||
|
self.user_button = Button(page, locators[1], "user_button")
|
||||||
|
|
||||||
|
self.user_card = CardComponent(page)
|
||||||
|
|
||||||
|
# Действия:
|
||||||
|
def click_main_navigation_panel_item(self, item_name: str) -> None:
|
||||||
|
"""Кликает по элементу основной панели навигации.
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
return self.navigation_panel.is_scrollable_vertically(
|
||||||
|
NavigationPanelLocators.PANEL_SCROLL_CONTAINER
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_navigation_panel_item_visibility(self, item_name: str) -> None:
|
||||||
|
"""Проверяет видимость элемента в панели навигации.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_name: Название элемента для проверки.
|
||||||
|
"""
|
||||||
|
self.navigation_panel.check_item_visibility(
|
||||||
|
NavigationPanelLocators.PANEL_MAIN,
|
||||||
|
item_name
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
from pages.base_page import BasePage
|
||||||
|
from components.toolbar_component import ToolbarComponent
|
||||||
|
from components.table_component import TableComponent
|
||||||
|
from locators.table_locators import TableLocators
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceStatusTab(BasePage):
|
||||||
|
"""Класс для работы с вкладкой 'Статус обслуживания'.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с таблицей сервисов и проверки её состояния.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (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: Количество строк с данными.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица пуста.
|
||||||
|
"""
|
||||||
|
table_content = self.services_table.read(TableLocators.TABLE_WORK_AREA)
|
||||||
|
rows_count = len(table_content)
|
||||||
|
|
||||||
|
if rows_count == 0:
|
||||||
|
assert False, "The contents of the table are missing"
|
||||||
|
|
||||||
|
return rows_count - 1
|
||||||
|
|
||||||
|
def 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: Если таблица пуста или заголовки не соответствуют ожидаемым.
|
||||||
|
"""
|
||||||
|
expected_headers = [
|
||||||
|
'Контейнер',
|
||||||
|
'Время создания',
|
||||||
|
'Статус',
|
||||||
|
'Время работы',
|
||||||
|
'Image ID',
|
||||||
|
'Image ТЭГ'
|
||||||
|
]
|
||||||
|
|
||||||
|
table_content = self.services_table.read(TableLocators.TABLE_WORK_AREA)
|
||||||
|
|
||||||
|
if len(table_content) == 0:
|
||||||
|
assert False, "The contents of the table are missing"
|
||||||
|
|
||||||
|
actual_headers = table_content[0]
|
||||||
|
|
||||||
|
self.check_equals(
|
||||||
|
actual_headers,
|
||||||
|
expected_headers,
|
||||||
|
f"Expected table headers {expected_headers} are not equal {actual_headers}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(table_content) == 1:
|
||||||
|
assert False, "Table body is missing"
|
||||||
|
|
||||||
|
def check_services_table_verticall_scrolling(self) -> bool:
|
||||||
|
"""Проверяет возможность вертикальной прокрутки таблицы.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если прокрутка возможна, иначе False.
|
||||||
|
"""
|
||||||
|
return self.services_table.is_scrollable_vertically(
|
||||||
|
TableLocators.TABLE_SCROLL_CONTAINER
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_services_table_first_row_visibility(self) -> None:
|
||||||
|
"""Проверяет видимость первой строки таблицы.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если первая строка не видна.
|
||||||
|
"""
|
||||||
|
self.services_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)
|
||||||
|
|
||||||
|
def check_services_table_last_row_visibility(self) -> None:
|
||||||
|
"""Проверяет видимость последней строки таблицы.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
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): Индекс проверяемой строки.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если строка не выделена.
|
||||||
|
"""
|
||||||
|
self.services_table.check_row_highlighting(
|
||||||
|
TableLocators.TABLE_WORK_AREA,
|
||||||
|
row_index
|
||||||
|
)
|
||||||
|
|
||||||
|
def should_be_toolbar(self) -> None:
|
||||||
|
"""Проверяет наличие тулбара на вкладке.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если тулбар отсутствует.
|
||||||
|
"""
|
||||||
|
self.toolbar.check_presence("Toolbar is missing")
|
||||||
|
|
||||||
|
def should_be_services_table(self) -> None:
|
||||||
|
"""Проверяет наличие таблицы сервисов.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица отсутствует.
|
||||||
|
"""
|
||||||
|
self.services_table.check_presence(
|
||||||
|
TableLocators.TABLE_WORK_AREA,
|
||||||
|
"Service statuses table is missing"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
from pages.base_page import BasePage
|
||||||
|
from elements.tooltip_button_element import TooltipButton
|
||||||
|
from components.toolbar_component import ToolbarComponent
|
||||||
|
from components.table_component import TableComponent
|
||||||
|
from locators.button_locators import ButtonLocators
|
||||||
|
from locators.table_locators import TableLocators
|
||||||
|
from playwright.sync_api import Page, Locator
|
||||||
|
from data.roles_dict import roles_dict
|
||||||
|
|
||||||
|
|
||||||
|
class SessionsTab(BasePage):
|
||||||
|
"""Класс для работы с вкладкой 'Сессия'.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с таблицей сессий и проверки её состояния.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (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: Количество строк с данными.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица пуста.
|
||||||
|
"""
|
||||||
|
table_content = self.sessions_table.read(TableLocators.TABLE_WORK_AREA)
|
||||||
|
rows_count = len(table_content)
|
||||||
|
|
||||||
|
if rows_count == 0:
|
||||||
|
assert False, "The contents of the table are missing"
|
||||||
|
|
||||||
|
return rows_count - 1
|
||||||
|
|
||||||
|
def get_delete_session_button_from_row(self, row_index: int) -> TooltipButton:
|
||||||
|
"""Возвращает кнопку удаления сессии для указанной строки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
row_index (int): Индекс строки в таблице
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TooltipButton: Экземпляр кнопки с подсказкой
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если строка не найдена.
|
||||||
|
"""
|
||||||
|
row_locator = self.sessions_table.get_row_locator(
|
||||||
|
TableLocators.TABLE_WORK_AREA,
|
||||||
|
row_index
|
||||||
|
)
|
||||||
|
assert isinstance(row_locator, Locator), f"Row with index {row_index} is missing"
|
||||||
|
|
||||||
|
button_locator = row_locator.locator(ButtonLocators.BUTTON_DELETE_SESSION)
|
||||||
|
return TooltipButton(self.page, button_locator, "delete_session_button")
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица пуста или заголовки не соответствуют.
|
||||||
|
"""
|
||||||
|
expected_headers = [
|
||||||
|
'ID сессии',
|
||||||
|
'ID пользователя',
|
||||||
|
'Время жизни',
|
||||||
|
'Роль',
|
||||||
|
'Адрес'
|
||||||
|
]
|
||||||
|
|
||||||
|
table_content = self.sessions_table.read(TableLocators.TABLE_WORK_AREA)
|
||||||
|
len_table_content = len(table_content)
|
||||||
|
|
||||||
|
if len_table_content == 0:
|
||||||
|
assert False, "The contents of the table are missing"
|
||||||
|
|
||||||
|
actual_headers = table_content[0]
|
||||||
|
|
||||||
|
self.check_equals(
|
||||||
|
actual_headers,
|
||||||
|
expected_headers,
|
||||||
|
f"Expected table headers {expected_headers} are not equal {actual_headers}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len_table_content == 1:
|
||||||
|
assert False, "Table body is missing"
|
||||||
|
|
||||||
|
if verify:
|
||||||
|
self.verify_sessions_table_content(table_content)
|
||||||
|
|
||||||
|
for index in range(len_table_content - 1):
|
||||||
|
self.should_be_delete_button_on_sessions_table_row(index, "Удалить")
|
||||||
|
|
||||||
|
def check_sessions_table_verticall_scrolling(self) -> bool:
|
||||||
|
"""Проверяет возможность вертикальной прокрутки таблицы.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если прокрутка возможна, иначе False.
|
||||||
|
"""
|
||||||
|
return self.sessions_table.is_scrollable_vertically(
|
||||||
|
TableLocators.TABLE_SCROLL_CONTAINER
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_sessions_table_first_row_visibility(self) -> None:
|
||||||
|
"""Проверяет видимость первой строки таблицы.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если первая строка не видна.
|
||||||
|
"""
|
||||||
|
self.sessions_table.check_first_row_visibility(TableLocators.TABLE_WORK_AREA)
|
||||||
|
|
||||||
|
def check_sessions_table_last_row_visibility(self) -> None:
|
||||||
|
"""Проверяет видимость последней строки таблицы.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
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): Индекс проверяемой строки.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если строка не выделена.
|
||||||
|
"""
|
||||||
|
self.sessions_table.check_row_highlighting(
|
||||||
|
TableLocators.TABLE_WORK_AREA,
|
||||||
|
row_index
|
||||||
|
)
|
||||||
|
|
||||||
|
def should_be_toolbar(self) -> None:
|
||||||
|
"""Проверяет наличие тулбара на вкладке.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если тулбар отсутствует.
|
||||||
|
"""
|
||||||
|
self.toolbar.check_presence("Toolbar is missing")
|
||||||
|
|
||||||
|
def should_be_sessions_table(self) -> None:
|
||||||
|
"""Проверяет наличие таблицы сессий.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица отсутствует.
|
||||||
|
"""
|
||||||
|
self.sessions_table.check_presence(
|
||||||
|
TableLocators.TABLE_WORK_AREA,
|
||||||
|
"Sessions table is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
def should_be_delete_button_on_sessions_table_row(
|
||||||
|
self,
|
||||||
|
row_index: int,
|
||||||
|
tooltip: str
|
||||||
|
) -> None:
|
||||||
|
"""Проверяет наличие кнопки удаления в строке таблицы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
row_index (int): Индекс проверяемой строки
|
||||||
|
tooltip (str): Ожидаемый текст подсказки
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
delete_button.check_tooltip_with_text(ButtonLocators.TOOLTIP, tooltip)
|
||||||
|
|
||||||
|
def verify_sessions_table_content(self, sessions_table: list) -> None:
|
||||||
|
"""Сверяет данные таблицы с данными из БД.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sessions_table (list): Данные из таблицы на странице
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если данные не соответствуют.
|
||||||
|
"""
|
||||||
|
expected_sessions_list = []
|
||||||
|
|
||||||
|
# Отправка запроса к бэкенду для получения информации о сессиях
|
||||||
|
response = self.send_get_api_request("e-nms/auth/sessions")
|
||||||
|
response_body = self.get_response_body(response)
|
||||||
|
|
||||||
|
for item in response_body:
|
||||||
|
session_info = []
|
||||||
|
session_info.append(item["id"])
|
||||||
|
session_info.append(item["userId"])
|
||||||
|
|
||||||
|
# Временно неподдерживаемое поле: время жизни сессии
|
||||||
|
session_info.append("")
|
||||||
|
|
||||||
|
roles = []
|
||||||
|
for role in item["roles"]:
|
||||||
|
if role in roles_dict.keys():
|
||||||
|
roles.append(roles_dict[role])
|
||||||
|
|
||||||
|
session_info.append(",".join(roles))
|
||||||
|
session_info.append(item["ip"])
|
||||||
|
|
||||||
|
expected_sessions_list.append(session_info)
|
||||||
|
|
||||||
|
del sessions_table[0] # Удаляем заголовок
|
||||||
|
|
||||||
|
self.check_lists_equals(
|
||||||
|
sessions_table,
|
||||||
|
expected_sessions_list,
|
||||||
|
"Actual sessions list is not equal expected users list on base db"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,451 @@
|
||||||
|
from pages.base_page import BasePage
|
||||||
|
from components.alert_component import AlertComponent
|
||||||
|
from components.toolbar_component import ToolbarComponent
|
||||||
|
from components.table_component import TableComponent
|
||||||
|
from modal_windows.modal_add_user import AddUserModalWindow
|
||||||
|
from modal_windows.modal_edit_user import EditUserModalWindow
|
||||||
|
from locators.table_locators import TableLocators
|
||||||
|
from data.roles_dict import roles_dict
|
||||||
|
from playwright.sync_api import Page
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class UsersTab(BasePage):
|
||||||
|
"""Класс для работы с вкладкой 'Пользователи'.
|
||||||
|
|
||||||
|
Предоставляет методы для взаимодействия с таблицей пользователей,
|
||||||
|
модальными окнами добавления/редактирования и проверки состояния элементов.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page (Page): Экземпляр страницы Playwright.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, page: Page) -> None:
|
||||||
|
"""Инициализация компонентов вкладки 'Пользователи'."""
|
||||||
|
super().__init__(page)
|
||||||
|
|
||||||
|
locator_button_1 = self.page.get_by_role("navigation").filter(
|
||||||
|
has_text=re.compile("Пользователи")
|
||||||
|
).get_by_role("button").nth(0)
|
||||||
|
locator_button_2 = self.page.get_by_role("navigation").filter(
|
||||||
|
has_text=re.compile("Пользователи")
|
||||||
|
).get_by_role("button").nth(1)
|
||||||
|
|
||||||
|
self.toolbar = ToolbarComponent(page, "Пользователи")
|
||||||
|
self.toolbar.add_button(locator_button_1, "edit")
|
||||||
|
self.toolbar.add_button(locator_button_1, "add_user")
|
||||||
|
self.toolbar.add_button(locator_button_2, "close")
|
||||||
|
|
||||||
|
self.users_table = TableComponent(page)
|
||||||
|
self.modal_windows = {}
|
||||||
|
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): Заголовок окна (имя пользователя для редактирования)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если указан неподдерживаемый тип окна.
|
||||||
|
"""
|
||||||
|
if window_type == "add_user":
|
||||||
|
self.modal_windows["add_user"] = AddUserModalWindow(self.page)
|
||||||
|
elif window_type == "edit_user":
|
||||||
|
self.modal_windows[title] = EditUserModalWindow(self.page, title)
|
||||||
|
else:
|
||||||
|
assert False, "Unsupported modal window type"
|
||||||
|
|
||||||
|
def get_modal_window(self, title: str) -> None:
|
||||||
|
"""Возвращает модальное окно по заголовку.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): Заголовок окна
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ModalWindowComponent: Экземпляр модального окна
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если окно не найдено.
|
||||||
|
"""
|
||||||
|
modal_window = self.modal_windows.get(title)
|
||||||
|
if modal_window is None:
|
||||||
|
assert False, f"Modal window with title '{title}' not found"
|
||||||
|
return modal_window
|
||||||
|
|
||||||
|
def delete_modal_window(self, title: str) -> None:
|
||||||
|
"""Удаляет модальное окно из коллекции.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): Заголовок окна
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если окно не найдено.
|
||||||
|
"""
|
||||||
|
if self.modal_windows.get(title) is None:
|
||||||
|
assert False, f"Modal window with title '{title}' not found"
|
||||||
|
self.modal_windows[title] = None
|
||||||
|
|
||||||
|
def close_modal_window_by_toolbar_button(self, title: str) -> None:
|
||||||
|
"""Закрывает модальное окно через кнопку в тулбаре.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): Заголовок окна
|
||||||
|
"""
|
||||||
|
modal_window = self.get_modal_window(title)
|
||||||
|
modal_window.close_window_by_toolbar_button()
|
||||||
|
self.delete_modal_window(title)
|
||||||
|
|
||||||
|
def close_modal_window(self, title: str) -> None:
|
||||||
|
"""Закрывает модальное окно через кнопку закрытия.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): Заголовок окна
|
||||||
|
"""
|
||||||
|
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): Имя пользователя (заголовок окна)
|
||||||
|
"""
|
||||||
|
self.close_modal_window_by_toolbar_button(title)
|
||||||
|
|
||||||
|
def close_edit_user_window(self, title: str) -> None:
|
||||||
|
"""Закрывает окно редактирования пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): Имя пользователя (заголовок окна)
|
||||||
|
"""
|
||||||
|
self.close_modal_window(title)
|
||||||
|
|
||||||
|
def add_new_user(self, user_data: dict) -> None:
|
||||||
|
"""Добавляет нового пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_data (dict): Данные пользователя
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если не отображается сообщение об успешном добавлении.
|
||||||
|
"""
|
||||||
|
self.get_modal_window("add_user").new_user(user_data)
|
||||||
|
self.success_alert.check_presence(' Новый пользователь \n успешно добавлен! ')
|
||||||
|
self.success_alert.check_absence(' Новый пользователь \n успешно добавлен! ')
|
||||||
|
|
||||||
|
def delete_user(self, user_name: str) -> None:
|
||||||
|
"""Удаляет пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name (str): Имя пользователя
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если не отображается сообщение об успешном удалении.
|
||||||
|
"""
|
||||||
|
self.get_modal_window(user_name).delete_user()
|
||||||
|
self.success_alert.check_presence('\nПользователь удалён\n')
|
||||||
|
self.success_alert.check_absence('\nПользователь удалён\n')
|
||||||
|
|
||||||
|
def edit_user(self, user_name: str, user_data: dict) -> None:
|
||||||
|
"""Редактирует данные пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name (str): Имя пользователя
|
||||||
|
user_data (dict): Новые данные пользователя
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если не отображается сообщение об успешном обновлении.
|
||||||
|
"""
|
||||||
|
self.get_modal_window(user_name).edit_user(user_data)
|
||||||
|
self.success_alert.check_presence('\nОбновление успешно\n')
|
||||||
|
self.success_alert.check_absence('\nОбновление успешно\n')
|
||||||
|
|
||||||
|
def reset_password(self, user_name: str) -> str:
|
||||||
|
"""Сбрасывает пароль пользователя.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name (str): Имя пользователя
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Новый пароль (если получен)
|
||||||
|
"""
|
||||||
|
new_password = ""
|
||||||
|
self.get_modal_window(user_name).reset_password()
|
||||||
|
|
||||||
|
self.success_alert.check_presence("")
|
||||||
|
alert_message = self.success_alert.get_text()
|
||||||
|
if len(alert_message) > 0:
|
||||||
|
new_password = re.findall(r'[\d]+', alert_message)[0]
|
||||||
|
|
||||||
|
return new_password
|
||||||
|
|
||||||
|
def find_user_in_table(self, name: str, role: str) -> int:
|
||||||
|
"""Ищет пользователя в таблице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя пользователя
|
||||||
|
role (str): Роль пользователя
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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"
|
||||||
|
|
||||||
|
del table_content[0] # Удаляем заголовок
|
||||||
|
|
||||||
|
for row_index, user_info in enumerate(table_content):
|
||||||
|
if name in user_info and role in user_info:
|
||||||
|
return row_index
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def open_add_user_window(self) -> None:
|
||||||
|
"""Открывает окно добавления пользователя.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если кнопки недоступны или окно не открылось.
|
||||||
|
"""
|
||||||
|
if self.toolbar.is_button_not_present("close"):
|
||||||
|
self.toolbar.check_button_presence("edit")
|
||||||
|
self.toolbar.click_button("edit")
|
||||||
|
|
||||||
|
self.toolbar.check_button_presence("add_user")
|
||||||
|
self.toolbar.click_button("add_user")
|
||||||
|
self.add_modal_window("add_user", "")
|
||||||
|
self.get_modal_window("add_user").check_by_window_title()
|
||||||
|
|
||||||
|
def open_edit_user_page_by_index(self, row_index: int) -> tuple:
|
||||||
|
"""Открывает окно редактирования по индексу строки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
row_index (int): Индекс строки в таблице
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (имя пользователя, роль)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица пуста или индекс вне диапазона.
|
||||||
|
"""
|
||||||
|
tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"}
|
||||||
|
table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA)
|
||||||
|
|
||||||
|
if len(table_content) == 0:
|
||||||
|
assert False, "The contents of the table are missing"
|
||||||
|
|
||||||
|
del table_content[0] # Удаляем заголовок
|
||||||
|
|
||||||
|
if row_index >= len(table_content):
|
||||||
|
assert False, "Row_index is out of range"
|
||||||
|
|
||||||
|
user_name = table_content[row_index][0]
|
||||||
|
for key, val in tmp_dict.items():
|
||||||
|
if user_name == val:
|
||||||
|
user_name = key
|
||||||
|
|
||||||
|
role = table_content[row_index][1]
|
||||||
|
|
||||||
|
self.page.locator(TableLocators.TABLE_WORK_AREA).locator("//tbody/tr").nth(row_index).click()
|
||||||
|
self.add_modal_window("edit_user", user_name)
|
||||||
|
self.get_modal_window(user_name).check_by_window_title()
|
||||||
|
|
||||||
|
return user_name, role
|
||||||
|
|
||||||
|
def open_edit_user_page_by_user(self, user_name: str, role: str) -> None:
|
||||||
|
"""Открывает окно редактирования по имени пользователя и роли.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name (str): Имя пользователя
|
||||||
|
role (str): Роль пользователя
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
self.page.locator(TableLocators.TABLE_WORK_AREA).locator("//tbody/tr").nth(row_index).click()
|
||||||
|
self.add_modal_window("edit_user", user_name)
|
||||||
|
self.get_modal_window(user_name).check_by_window_title()
|
||||||
|
|
||||||
|
def check_users_table_content(self, verify: bool = False) -> None:
|
||||||
|
"""Проверяет содержимое таблицы пользователей.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
verify (bool, optional): Проверять соответствие данных из БД. По умолчанию False.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица пуста или заголовки не соответствуют.
|
||||||
|
"""
|
||||||
|
expected_headers = ['Имя пользователя', 'Роль', 'E-mail', 'Номер для СМС']
|
||||||
|
table_content = self.users_table.read(TableLocators.TABLE_WORK_AREA)
|
||||||
|
|
||||||
|
if len(table_content) == 0:
|
||||||
|
assert False, "The contents of the table are missing"
|
||||||
|
|
||||||
|
actual_headers = table_content[0]
|
||||||
|
self.check_equals(
|
||||||
|
actual_headers,
|
||||||
|
expected_headers,
|
||||||
|
f"Expected table headers {expected_headers} are not equal {actual_headers}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(table_content) == 1:
|
||||||
|
assert False, "Table body is missing"
|
||||||
|
|
||||||
|
if verify:
|
||||||
|
self.verify_users_table_content(table_content)
|
||||||
|
|
||||||
|
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): Роль пользователя
|
||||||
|
"""
|
||||||
|
edit_user_window = self.get_modal_window(user_name)
|
||||||
|
edit_user_window.check_content(user_name, role)
|
||||||
|
|
||||||
|
def should_be_toolbar(self) -> None:
|
||||||
|
"""Проверяет наличие тулбара.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если тулбар или кнопка редактирования отсутствуют.
|
||||||
|
"""
|
||||||
|
self.toolbar.check_presence("Toolbar is missing")
|
||||||
|
self.toolbar.check_button_presence("edit")
|
||||||
|
|
||||||
|
def should_be_toolbar_buttons(self) -> None:
|
||||||
|
"""Проверяет наличие и функциональность кнопок тулбара.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если кнопки недоступны или имеют некорректные подсказки.
|
||||||
|
"""
|
||||||
|
self.toolbar.check_button_presence("edit")
|
||||||
|
self.toolbar.check_button_tooltip("edit", "Редактировать")
|
||||||
|
|
||||||
|
self.toolbar.get_button_by_name("edit").click()
|
||||||
|
self.toolbar.check_button_presence("add_user")
|
||||||
|
self.toolbar.check_button_presence("close")
|
||||||
|
self.toolbar.check_button_tooltip("add_user", "Добавить")
|
||||||
|
self.toolbar.check_button_tooltip("close", "Закрыть")
|
||||||
|
|
||||||
|
self.toolbar.get_button_by_name("close").click()
|
||||||
|
self.toolbar.check_button_presence("edit")
|
||||||
|
|
||||||
|
def should_be_users_table(self) -> None:
|
||||||
|
"""Проверяет наличие таблицы пользователей.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если таблица отсутствует.
|
||||||
|
"""
|
||||||
|
self.users_table.check_presence(
|
||||||
|
TableLocators.TABLE_WORK_AREA,
|
||||||
|
"Users table is missing"
|
||||||
|
)
|
||||||
|
|
||||||
|
def should_be_user_in_table(self, name: str, role: str) -> None:
|
||||||
|
"""Проверяет наличие пользователя в таблице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя пользователя
|
||||||
|
role (str): Роль пользователя
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
def should_not_be_user_in_table(self, name: str, role: str) -> None:
|
||||||
|
"""Проверяет отсутствие пользователя в таблице.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Имя пользователя
|
||||||
|
role (str): Роль пользователя
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
def verify_users_table_content(self, users_table: list) -> None:
|
||||||
|
"""Сверяет данные таблицы с данными из БД.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
users_table (list): Данные из таблицы на странице
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: Если данные не соответствуют.
|
||||||
|
"""
|
||||||
|
expected_users_list = []
|
||||||
|
tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"}
|
||||||
|
|
||||||
|
query = {
|
||||||
|
"id": ["/catalogs/user"],
|
||||||
|
"data": {
|
||||||
|
"namePath": True,
|
||||||
|
"children": {"flatten": True}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.send_post_api_request("e-cmdb/api/query", query)
|
||||||
|
response_body = self.get_response_body(response)
|
||||||
|
|
||||||
|
for item in response_body[0]["children"]:
|
||||||
|
user_info = []
|
||||||
|
user_name = item["name"]
|
||||||
|
|
||||||
|
if user_name in tmp_dict.keys():
|
||||||
|
item["name"] = tmp_dict[user_name]
|
||||||
|
user_info.append(item["name"])
|
||||||
|
|
||||||
|
if item["role"] is not None:
|
||||||
|
role = item["role"]
|
||||||
|
if role in roles_dict.keys():
|
||||||
|
item["role"] = roles_dict[role]
|
||||||
|
user_info.append(item["role"])
|
||||||
|
else:
|
||||||
|
user_info.append("")
|
||||||
|
|
||||||
|
if item["email"] is not None:
|
||||||
|
user_info.append(item["email"])
|
||||||
|
else:
|
||||||
|
user_info.append("")
|
||||||
|
|
||||||
|
if item["sms_phone"] is not None:
|
||||||
|
user_info.append(item["sms_phone"])
|
||||||
|
else:
|
||||||
|
user_info.append("")
|
||||||
|
|
||||||
|
expected_users_list.append(user_info)
|
||||||
|
|
||||||
|
del users_table[0] # Удаляем заголовок
|
||||||
|
|
||||||
|
self.check_lists_equals(
|
||||||
|
users_table,
|
||||||
|
expected_users_list,
|
||||||
|
"Actual users list is not equal expected users list on base db"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
pages
|
||||||
|
|
||||||
|
base_page.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлен модульный docstring с описанием назначения модуля
|
||||||
|
- Добавлен подробный docstring для класса BasePage с описанием атрибутов
|
||||||
|
- Указание на возможные исключения (где уместно)
|
||||||
|
- Сохранены все технические комментарии (# Действия:, # Проверки:)
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8
|
||||||
|
- Добавлены аннотации типов для всех аргументов и возвращаемых значений
|
||||||
|
- Логика работы методов осталась без изменений
|
||||||
|
- Добавлено логирование ошибок вместо print
|
||||||
|
- Улучшено форматирование длинных строк для лучшей читаемости
|
||||||
|
|
||||||
|
license_tab.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в формате Google Style на русском языке
|
||||||
|
- Переведены разделительные комментарии (#actions: → # Действия:, # assertions: → # Проверки:)
|
||||||
|
- Сохранены все технические комментарии в оригинальном виде
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8 (пробелы, переносы длинных строк)
|
||||||
|
- Добавлены аннотации типов для методов
|
||||||
|
- Сохранена вся исходная логика без изменений
|
||||||
|
|
||||||
|
login_page.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в формате Google Style на русском языке
|
||||||
|
- Сохранены все технические комментарии в оригинальном виде
|
||||||
|
- Добавлены аннотации типов для методов
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8 (импорты, пробелы, отступы)
|
||||||
|
- В docstring методов добавлена информация о:
|
||||||
|
Назначении метода
|
||||||
|
Аргументах
|
||||||
|
Возвращаемых значениях
|
||||||
|
Возможных исключениях
|
||||||
|
- Сохранена вся исходная логика без изменений
|
||||||
|
- Улучшена читаемость кода за счет правильного форматирования и структурирования
|
||||||
|
|
||||||
|
main_page.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлены docstring для класса и всех методов в формате Google Style на русском языке
|
||||||
|
- Переведены разделительные комментарии (#actions: → # Действия:, # assertions: → # Проверки:)
|
||||||
|
- Добавлены аннотации типов для всех методов
|
||||||
|
- Улучшено форматирование кода в соответствии с PEP 8
|
||||||
|
- Исправлена опечатка в названии метода click_configuration_navigation_panel_item (было click_configuration_navigation_panel_item)
|
||||||
|
- Сохранены все технические особенности исходного кода
|
||||||
|
- Улучшена читаемость за счет правильного структурирования кода и комментариев
|
||||||
|
|
||||||
|
service_status_tab.py
|
||||||
|
Изменения включают:
|
||||||
|
- Добавлена полная документация:
|
||||||
|
Docstring класса с описанием назначения
|
||||||
|
Подробные docstring для каждого метода
|
||||||
|
Указание типов аргументов и возвращаемых значений
|
||||||
|
Описание возможных исключений
|
||||||
|
- Оптимизировано форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, длина строк, пробелы)
|
||||||
|
Логическое разделение блоков кода
|
||||||
|
Улучшенные переносы длинных строк
|
||||||
|
- Улучшена читаемость:
|
||||||
|
Последовательное именование методов
|
||||||
|
Четкая структура документации
|
||||||
|
Единый стиль оформления
|
||||||
|
- Сохранена функциональность:
|
||||||
|
Без изменений рабочей логики
|
||||||
|
Сохранение всех оригинальных вызовов
|
||||||
|
Оставление сообщений об ошибках на английском (как в требованиях)
|
||||||
|
|
||||||
|
users_tab.py
|
||||||
|
Изменения включают:
|
||||||
|
- Полная документация:
|
||||||
|
Добавлены docstring для класса и всех методов
|
||||||
|
Указаны типы аргументов и возвращаемых значений
|
||||||
|
Описаны возможные исключения
|
||||||
|
Добавлены пояснения к сложным методам
|
||||||
|
- Оптимизированное форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, длина строк, пробелы)
|
||||||
|
Логическая группировка кода
|
||||||
|
Улучшенные переносы длинных строк
|
||||||
|
- Улучшенная читаемость:
|
||||||
|
Последовательные именования
|
||||||
|
Четкое разделение блоков
|
||||||
|
Единый стиль оформления
|
||||||
|
- Сохранение функциональности:
|
||||||
|
Без изменений рабочей логики
|
||||||
|
Сохранение всех оригинальных вызовов
|
||||||
|
Оставление сообщений об ошибках на английском (как в требованиях)
|
||||||
|
|
||||||
|
session_tab.py
|
||||||
|
Изменения включают:
|
||||||
|
- Полная документация:
|
||||||
|
Добавлены docstring для класса и всех методов
|
||||||
|
Указаны типы аргументов и возвращаемых значений
|
||||||
|
Описаны возможные исключения
|
||||||
|
Добавлены пояснения к сложным методам
|
||||||
|
- Оптимизированное форматирование:
|
||||||
|
Соблюдение PEP 8 (отступы, длина строк, пробелы)
|
||||||
|
Логическая группировка кода
|
||||||
|
Улучшенные переносы длинных строк
|
||||||
|
- Улучшенная читаемость:
|
||||||
|
Последовательные именования
|
||||||
|
Четкое разделение блоков
|
||||||
|
Единый стиль оформления
|
||||||
|
- Сохранение функциональности:
|
||||||
|
Без изменений рабочей логики
|
||||||
|
Сохранение всех оригинальных вызовов
|
||||||
|
Оставление сообщений об ошибках на английском (как в требованиях)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[pytest]
|
||||||
|
markers =
|
||||||
|
smoke: Маркер для smoke-тестов (критически важные тесты)
|
||||||
|
session: Тесты, связанные с управлением сессиями
|
||||||
|
users: Тесты для работы с пользователями
|
||||||
|
auth: Тесты авторизации
|
||||||
|
ui: UI-тесты (проверки интерфейса)
|
||||||
|
creation: Тесты на создание данных
|
||||||
|
cleanup: Тесты на удаление/очистку данных
|
||||||
|
develop: Тесты в активной разработке (нестабильные)
|
||||||
|
|
||||||
|
addopts = -v -s
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
pytest
|
||||||
|
playwright
|
||||||
|
requests
|
||||||
|
qase-pytest==4.2.0
|
||||||
|
python-dotenv
|
||||||
|
jsondiff
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,181 @@
|
||||||
|
|
||||||
|
/* Avoid breaking parameter names, etc. in table cells. */
|
||||||
|
.doc-contents td code {
|
||||||
|
word-break: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No line break before first paragraph of descriptions. */
|
||||||
|
.doc-md-description,
|
||||||
|
.doc-md-description>p:first-child {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No text transformation from Material for MkDocs for H5 headings. */
|
||||||
|
.md-typeset h5 .doc-object-name {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Max width for docstring sections tables. */
|
||||||
|
.doc .md-typeset__table,
|
||||||
|
.doc .md-typeset__table table {
|
||||||
|
display: table !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc .md-typeset__table tr {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Defaults in Spacy table style. */
|
||||||
|
.doc-param-default {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parameter headings must be inline, not blocks. */
|
||||||
|
.doc-heading-parameter {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default font size for parameter headings. */
|
||||||
|
.md-typeset .doc-heading-parameter {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prefer space on the right, not the left of parameter permalinks. */
|
||||||
|
.doc-heading-parameter .headerlink {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backward-compatibility: docstring section titles in bold. */
|
||||||
|
.doc-section-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backlinks crumb separator. */
|
||||||
|
.doc-backlink-crumb {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: .2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.doc-backlink-crumb:not(:first-child)::before {
|
||||||
|
background-color: var(--md-default-fg-color--lighter);
|
||||||
|
content: "";
|
||||||
|
display: inline;
|
||||||
|
height: 1rem;
|
||||||
|
--md-path-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6z"/></svg>');
|
||||||
|
-webkit-mask-image: var(--md-path-icon);
|
||||||
|
mask-image: var(--md-path-icon);
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
.doc-backlink-crumb.last {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Symbols in Navigation and ToC. */
|
||||||
|
:root, :host,
|
||||||
|
[data-md-color-scheme="default"] {
|
||||||
|
--doc-symbol-parameter-fg-color: #df50af;
|
||||||
|
--doc-symbol-attribute-fg-color: #953800;
|
||||||
|
--doc-symbol-function-fg-color: #8250df;
|
||||||
|
--doc-symbol-method-fg-color: #8250df;
|
||||||
|
--doc-symbol-class-fg-color: #0550ae;
|
||||||
|
--doc-symbol-module-fg-color: #5cad0f;
|
||||||
|
|
||||||
|
--doc-symbol-parameter-bg-color: #df50af1a;
|
||||||
|
--doc-symbol-attribute-bg-color: #9538001a;
|
||||||
|
--doc-symbol-function-bg-color: #8250df1a;
|
||||||
|
--doc-symbol-method-bg-color: #8250df1a;
|
||||||
|
--doc-symbol-class-bg-color: #0550ae1a;
|
||||||
|
--doc-symbol-module-bg-color: #5cad0f1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-md-color-scheme="slate"] {
|
||||||
|
--doc-symbol-parameter-fg-color: #ffa8cc;
|
||||||
|
--doc-symbol-attribute-fg-color: #ffa657;
|
||||||
|
--doc-symbol-function-fg-color: #d2a8ff;
|
||||||
|
--doc-symbol-method-fg-color: #d2a8ff;
|
||||||
|
--doc-symbol-class-fg-color: #79c0ff;
|
||||||
|
--doc-symbol-module-fg-color: #baff79;
|
||||||
|
|
||||||
|
--doc-symbol-parameter-bg-color: #ffa8cc1a;
|
||||||
|
--doc-symbol-attribute-bg-color: #ffa6571a;
|
||||||
|
--doc-symbol-function-bg-color: #d2a8ff1a;
|
||||||
|
--doc-symbol-method-bg-color: #d2a8ff1a;
|
||||||
|
--doc-symbol-class-bg-color: #79c0ff1a;
|
||||||
|
--doc-symbol-module-bg-color: #baff791a;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol {
|
||||||
|
border-radius: .1rem;
|
||||||
|
font-size: .85em;
|
||||||
|
padding: 0 .3em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-parameter,
|
||||||
|
a code.doc-symbol-parameter {
|
||||||
|
color: var(--doc-symbol-parameter-fg-color);
|
||||||
|
background-color: var(--doc-symbol-parameter-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-parameter::after {
|
||||||
|
content: "param";
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-attribute,
|
||||||
|
a code.doc-symbol-attribute {
|
||||||
|
color: var(--doc-symbol-attribute-fg-color);
|
||||||
|
background-color: var(--doc-symbol-attribute-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-attribute::after {
|
||||||
|
content: "attr";
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-function,
|
||||||
|
a code.doc-symbol-function {
|
||||||
|
color: var(--doc-symbol-function-fg-color);
|
||||||
|
background-color: var(--doc-symbol-function-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-function::after {
|
||||||
|
content: "func";
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-method,
|
||||||
|
a code.doc-symbol-method {
|
||||||
|
color: var(--doc-symbol-method-fg-color);
|
||||||
|
background-color: var(--doc-symbol-method-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-method::after {
|
||||||
|
content: "meth";
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-class,
|
||||||
|
a code.doc-symbol-class {
|
||||||
|
color: var(--doc-symbol-class-fg-color);
|
||||||
|
background-color: var(--doc-symbol-class-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-class::after {
|
||||||
|
content: "class";
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-module,
|
||||||
|
a code.doc-symbol-module {
|
||||||
|
color: var(--doc-symbol-module-fg-color);
|
||||||
|
background-color: var(--doc-symbol-module-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
code.doc-symbol-module::after {
|
||||||
|
content: "mod";
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-signature .autorefs {
|
||||||
|
color: inherit;
|
||||||
|
border-bottom: 1px dotted currentcolor;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue