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