Правка code style, актуализация тестов под текущие изменения GUI

pull/1/head
nsubbot 2025-07-23 08:20:36 +03:00
parent ce6fe0390c
commit 10315ba38f
52 changed files with 899 additions and 782 deletions

View File

@ -7,4 +7,9 @@ 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 - тест вкладки "Сессии"
- tests\e2e\test_sessions_tab.py - тест вкладки "Сессии"
===================23.07.2025==========================
- Все файлы прошли проверку pylint, внесены исправления для фикса замечаний линтера
- Возвращено заведение пользователя с введением пароля
- Актуализированы тесты под текущее состояние интерфейса пользователя версии 1.7

View File

@ -1,18 +1,16 @@
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
from elements.text_element import Text
from components.base_component import BaseComponent
logger = get_logger("ALERT")
class AlertComponent(BaseComponent):
"""Компонент для работы с alert-окнами.
Поддерживает различные типы alert-окон: error, success, info, warning.
Атрибуты:
page: экземпляр страницы Playwright
alert_type: тип alert-окна (error/success/info/warning)
@ -21,69 +19,74 @@ class AlertComponent(BaseComponent):
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")
self.text = Text(page, f"//div[@class='v-alert {self.alert_type}']/div", "Alert message")
# Действия:
def get_text(self) -> str:
"""Получение текста сообщения из alert-окна.
Returns:
str: текст сообщения alert-окна
"""
return self.text.get_text(0)
# Проверки:
def check_presence(self, text: str):
def check_alert_presence(self, text: str):
"""Проверка наличия alert-окна с заданным текстом.
Args:
text: текст для проверки (если пустая строка - проверяется только наличие окна)
Raises:
AssertionError: если alert-окно не найдено
"""
msg = f"No {self.alert_type} alert window on page"
if text == "":
expect(self.page.get_by_role("alert")).to_be_visible(), msg
else:
expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_visible(), msg
def check_absence(self, text: str, timeout: int = 30000):
def check_alert_absence(self, text: str, timeout: int = 30000):
"""Проверка отсутствия alert-окна с заданным текстом.
Args:
text: текст для проверки
timeout: время ожидания исчезновения (в миллисекундах)
Raises:
AssertionError: если alert-окно не исчезает в течение заданного времени
"""
seconds = int(timeout/1000)
msg = f"Alert {self.alert_type} window should disappear after {seconds} seconds"
msg = f"Alert {self.alert_type} window should disappear after {seconds} seconds"
expect(self.page.get_by_role("alert").filter(has_text=text)).to_be_hidden(timeout=timeout), msg
def check_text(self, alert_text: str):
"""Проверка точного соответствия текста в alert-окне.
Args:
alert_text: ожидаемый текст сообщения
Raises:
AssertionError: если текст не соответствует ожидаемому
"""
self.text.check_have_text(alert_text, f"Unexpected message in alert {self.alert_type} window")
self.text.check_have_text(alert_text,
f"Unexpected message in alert {self.alert_type} window")

View File

@ -1,48 +1,171 @@
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 check_presence(self, locator: str | Locator, msg: str):
# Закомментированный код сохранен без изменений
# 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: str | Locator) -> bool:
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: str | Locator) -> bool:
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: str | Locator):
# Методы прокрутки:
def scroll_up(self, locator):
"""Прокрутка элемента вверх.
Args:
locator: локатор элемента
Raises:
AssertionError: если прокрутка не выполнена до конца
"""
loc = self.get_locator(locator)
loc.evaluate("el => el.scrollTo(0, 0)")
def scroll_down(self, locator: str | Locator):
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)")
def scroll_left(self, locator: str | Locator):
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")
def scroll_right(self, locator: str | Locator):
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")
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"

View File

@ -1,18 +1,16 @@
from playwright.sync_api import Page
from components.base_component import BaseComponent
from elements.button_element import Button
from tools.logger import get_logger
from elements.button_element import Button
from components.base_component import BaseComponent
logger = get_logger("USER_CARD")
class CardComponent(BaseComponent):
"""Компонент карточки пользователя.
Предоставляет методы для взаимодействия с элементами карточки пользователя.
Атрибуты:
page: экземпляр страницы Playwright
logout_button: кнопка выхода из системы
@ -20,25 +18,25 @@ class CardComponent(BaseComponent):
def __init__(self, page: Page):
"""Инициализация компонента карточки пользователя.
Args:
page: экземпляр страницы Playwright
"""
super().__init__(page)
self.logout_button = Button(
page,
page.get_by_role("button", name="Выйти"),
page,
page.get_by_role("button", name="Выйти"),
"logout button"
)
# Действия:
def click_logout_button(self):
"""Нажатие кнопки выхода из системы.
Выполняет клик по кнопке 'Выйти' в карточке пользователя.
"""
self.logout_button.click()
# Проверки:
# (Методы проверок могут быть добавлены здесь в будущем)
# (Методы проверок могут быть добавлены здесь в будущем)

View File

@ -1,54 +1,52 @@
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
from locators.confirm_locators import ConfirmLocators
from elements.text_element import Text
from elements.button_element import Button
from components.base_component import BaseComponent
logger = get_logger("CONFIRM_WINDOW")
class ConfirmComponent(BaseComponent):
"""Компонент окна подтверждения действий."""
def __init__(self, page: Page, cancel_button_text: str, allow_button_text: str):
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,
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,
page,
page.get_by_role("button", name=allow_button_text).first,
"confirm allow button"
)
# Действия:
def click_allow_button(self) -> None:
"""Нажатие кнопки подтверждения действия."""
self.allow_button.click()
def click_cancel_button(self) -> None:
"""Нажатие кнопки отмены действия."""
self.cancel_button.click()
def click_close_button(self) -> None:
"""Нажатие кнопки закрытия окна подтверждения."""
self.close_button.click()
# Проверки:
def check_title(self, title: str, msg: str) -> None:
"""Проверка текста заголовка окна подтверждения."""
self.title.check_have_text(title, msg)
def check_text(self, text: str, msg: str) -> None:
"""Проверка текста сообщения в окне подтверждения."""
self.text.check_have_text(text, msg)
self.text.check_have_text(text, msg)

View File

@ -1,58 +1,57 @@
from playwright.sync_api import Page
import json
import jsondiff
from components.base_component import BaseComponent
from playwright.sync_api import Page
from tools.logger import get_logger
from components.base_component import BaseComponent
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:
for substring in substrings:
if substring.find(':') == -1:
if substring == '{':
formatted_string_list.append(substring)
@ -63,38 +62,38 @@ class JsonContainerComponent(BaseComponent):
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)
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}"
assert len(diff) == 0, f"{msg}. DIFF is {diff}"

View File

@ -1,11 +1,9 @@
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
from locators.modal_window_locators import ModalWindowLocators
from elements.button_element import Button
from components.toolbar_component import ToolbarComponent
from components.base_component import BaseComponent
logger = get_logger("MODAL_WINDOW")
@ -23,81 +21,81 @@ class ModalWindowComponent(BaseComponent):
def add_content_item(self, name: str, item: object) -> None:
"""Добавление элемента содержимого в окно."""
self.content_items[name] = item
def get_content_item(self, name: str) -> object | None:
"""Получение элемента содержимого по имени."""
return self.content_items.get(name)
def add_toolbar_title(self, title: str) -> None:
"""Добавление заголовка в панель инструментов."""
self.toolbar.add_title(title)
def add_toolbar_button(self, locator: str, name: str) -> None:
"""Добавление кнопки в панель инструментов."""
self.toolbar.add_button(locator, name)
def add_button(self, locator: str, name: str) -> None:
"""Добавление кнопки в окно."""
self.buttons.append(Button(self.page, locator, name))
def get_button_by_name(self, name: str) -> Button | None:
"""Поиск кнопки по имени."""
for button in self.buttons:
if button.name == name:
return button
return None
def click_button(self, name: str) -> None:
"""Нажатие кнопки по имени."""
button = self.get_button_by_name(name)
if button is None:
assert False, f"Button with name '{name}' not found"
button.click()
def click_toolbar_close_button(self) -> None:
"""Нажатие кнопки закрытия в панели инструментов."""
self.toolbar.click_button("close")
def scroll_window_down(self) -> None:
"""Прокрутка содержимого окна вниз."""
self.scroll_down(ModalWindowLocators.MODAL_WINDOW)
def scroll_window_up(self) -> None:
"""Прокрутка содержимого окна вверх."""
self.scroll_up(ModalWindowLocators.MODAL_WINDOW)
def scroll_window_left(self) -> None:
"""Прокрутка содержимого окна влево."""
self.scroll_left(ModalWindowLocators.MODAL_WINDOW)
def scroll_window_right(self) -> None:
"""Прокрутка содержимого окна вправо."""
self.scroll_right(ModalWindowLocators.MODAL_WINDOW)
# Проверки:
def check_window_vertical_scrolling(self) -> bool:
"""Проверка возможности вертикальной прокрутки."""
return self.is_scrollable_vertically(ModalWindowLocators.MODAL_WINDOW)
def check_window_horizontal_scrolling(self) -> bool:
"""Проверка возможности горизонтальной прокрутки."""
return self.is_scrollable_horizontally(ModalWindowLocators.MODAL_WINDOW)
def check_by_window_title(self) -> None:
"""Проверка наличия окна по заголовку."""
self.toolbar.check_presence(f"Modal window with '{self.toolbar.title}' is missing")
self.toolbar.check_toolbar_presence(f"Modal window with '{self.toolbar.title}' is missing")
def check_button_presence(self, name: str) -> None:
"""Проверка наличия кнопки по имени."""
button = self.get_button_by_name(name)
if button is None:
assert False, f"Button with name '{name}' not found"
button.check_presence(f"Button with name '{name}' is missing")
def check_toolbar_button_presence(self, name: str) -> None:
"""Проверка наличия кнопки в панели инструментов."""
self.toolbar.check_button_presence(name)
def check_toolbar_button_tooltip(self, name: str, tooltip: str) -> None:
"""Проверка подсказки кнопки в панели инструментов."""
self.toolbar.check_button_tooltip(name, tooltip)
self.toolbar.check_button_tooltip(name, tooltip)

View File

@ -1,9 +1,7 @@
from playwright.sync_api import Page, Locator
from components.base_component import BaseComponent
from locators.navigation_panel_locators import NavigationPanelLocators
from tools.logger import get_logger
from locators.navigation_panel_locators import NavigationPanelLocators
from components.base_component import BaseComponent
logger = get_logger("NAVIGATION_PANEL")
@ -13,35 +11,46 @@ class NavigationPanelComponent(BaseComponent):
def __init__(self, page: Page):
super().__init__(page)
# Действия:
def get_item_names(self, locator: str | Locator) -> list[str]:
"""Получает тексты всех элементов по указанному локатору."""
loc = self.get_locator(locator)
return loc.all_inner_texts()
def click_item(self, locator: str | Locator, item_name: str) -> None:
"""Кликает по элементу с указанным текстом."""
loc = self.get_locator(locator)
loc.get_by_text(item_name).click()
def click_sub_item(self, locator: str | Locator, sublevel_number: int, item_name: str) -> None:
"""Кликает по вложенному элементу с указанным текстом."""
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: str | Locator, item_name: str) -> None:
"""Проверяет видимость элемента с указанным текстом."""
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)
## временно: в навигационной панели есть две панели с именем Шаблоны
## для их различия добавлены индексы Шаблоны_1 для Настройки/Шаблоны
## Шаблоны_2 для Настройки/ZTP/Шаблоны
loc = self.get_locator(locator)
if item_name == "Шаблоны_1":
loc = loc.get_by_text("Шаблоны").first
elif item_name == "Шаблоны_2":
loc = loc.get_by_text("Шаблоны").nth(1)
else:
loc = loc.get_by_text(item_name)
self.check_presence(loc, msg)

View File

@ -1,8 +1,6 @@
from playwright.sync_api import Page, expect, Locator
from components.base_component import BaseComponent
from tools.logger import get_logger
from components.base_component import BaseComponent
logger = get_logger("TABLE")
@ -12,20 +10,31 @@ class TableComponent(BaseComponent):
def __init__(self, page: Page):
super().__init__(page)
# Действия:
def get_row_locator(self, table_locator, row_index) -> Locator | None:
"""Конструирует локатор для строки с заданным индексом."""
table = self.get_locator(table_locator)
rows = table.locator("//tbody/tr")
if row_index in range(rows.count()):
return rows.nth(row_index)
else:
return None
def read(self, locator: str | Locator) -> list[list[str]]:
"""Читает данные из таблицы."""
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()):
@ -36,33 +45,33 @@ class TableComponent(BaseComponent):
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: str | Locator) -> None:
"""Проверяет видимость первой строки таблицы."""
table = self.get_locator(locator)
first_row = table.locator("//tbody/tr").first
expect(first_row).to_be_visible(), "The first table row is not visible"
def check_last_row_visibility(self, locator: str | Locator) -> None:
"""Проверяет видимость последней строки таблицы."""
table = self.get_locator(locator)
last_row = table.locator("//tbody/tr").last
expect(last_row).to_be_visible(), "The last table row is not visible"
def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
"""Проверяет подсветку строки при наведении."""
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)
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"
assert initial_color != new_color, "Color of row did not change when hovering the cursor"

View File

@ -1,8 +1,8 @@
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
from locators.toolbar_locators import ToolbarLocators
from elements.tooltip_button_element import TooltipButton
from components.base_component import BaseComponent
logger = get_logger("TOOLBAR")
@ -105,7 +105,7 @@ class ToolbarComponent(BaseComponent):
raise AssertionError(f"Unsupported button name {name}")
return button.is_not_present(timeout=1000) # Ожидание 1 секунда
def check_presence(self, message: str) -> None:
def check_toolbar_presence(self, message: str) -> None:
"""Проверяет видимость тулбара.
Args:
@ -141,4 +141,4 @@ class ToolbarComponent(BaseComponent):
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)
button.check_tooltip_with_text(ToolbarLocators.TOOLTIP, tooltip)

View File

@ -1,8 +1,7 @@
from dotenv import load_dotenv
import inspect
from pathlib import Path
import pytest
import os
import inspect
from dotenv import load_dotenv
load_dotenv()
@ -14,17 +13,17 @@ 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]:
@ -33,26 +32,26 @@ def pytest_sessionfinish(session, exitstatus):
'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')
@ -62,4 +61,4 @@ def pytest_addoption(parser):
action="store_true",
default=False,
help="Генерировать Markdown документацию с группировкой по классам"
)
)

View File

@ -1,6 +1,5 @@
import os
class Constants:
"""Класс для хранения констант и переменных окружения.
@ -16,4 +15,4 @@ class Constants:
login = os.getenv('AUTH_LOGIN')
password = os.getenv('AUTH_PASSWORD')
except KeyError:
print("LOGIN OR PASSWORD WASN'T FOUND")
print("LOGIN OR PASSWORD WASN'T FOUND")

View File

@ -1,6 +1,5 @@
import os
from typing import Dict, Optional
from typing import Dict
class Environment:
"""Класс для работы с окружением и URL-адресами."""
@ -29,20 +28,20 @@ class Environment:
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) -> str:
"""Возвращает URL для API-запросов."""
if self.env in self.URLS:
return self.URLS[self.env]
raise Exception(f"Unknown value of ENV variable {self.env}")
def set_access_token(self, token: str) -> None:
"""Устанавливает токен доступа."""
self.token = token
def get_access_token(self) -> str:
"""Возвращает текущий токен доступа."""
return self.token
host: Environment = Environment()
host: Environment = Environment()

View File

@ -5,4 +5,4 @@ roles_dict = {
"operator": "Оператор",
"inform_secur_user": "Специалист информационной безопасности",
"user": "Пользователь"
}
}

View File

@ -1,11 +1,8 @@
from playwright.sync_api import Page, Locator, expect, TimeoutError
from typing import Optional
from tools.logger import get_logger
logger = get_logger("BASE_ELEMENT")
class BaseElement:
"""Базовый класс для работы с элементами страницы."""
@ -27,29 +24,29 @@ class BaseElement:
# Действия:
def click(self) -> None:
logger.info(f'Clicking {self.type_of} "{self.name}"')
logger.info(f"Clicking {self.type_of} '{self.name}'")
self.locator.click()
def get_text(self, index: int) -> str:
logger.info(f'Get text for {self.type_of} "{self.name}"')
logger.info(f"Get text for {self.type_of} '{self.name}'")
return self.locator.nth(index).text_content()
def wait_for_element(self, timeout: int = 12000) -> None:
logger.info(f'Wait for {self.type_of} "{self.name}"')
logger.info(f"Wait for {self.type_of} '{self.name}'")
self.locator.wait_for(timeout=timeout)
# Проверки:
def check_have_text(self, text: str, msg: str) -> None:
logger.info(f'Check that {self.type_of} "{self.name}" has text "{text}"')
logger.info(f"Check that {self.type_of} '{self.name}' has text '{text}'")
expect(self.locator).to_have_text(text), msg
def check_presence(self, msg: str) -> None:
logger.info(f'Check that {self.type_of} "{self.name}" is present')
logger.info(f"Check that {self.type_of} '{self.name}' is present")
print(self.locator)
expect(self.locator).to_be_visible(visible=True, timeout=12000), msg
def is_present(self, timeout: int = 5000) -> bool:
logger.info(f'Check that {self.type_of} "{self.name}" is present')
logger.info(f"Check that {self.type_of} '{self.name}' is present")
try:
self.locator.wait_for(timeout=timeout)
except TimeoutError:
@ -57,9 +54,9 @@ class BaseElement:
return True
def is_not_present(self, timeout: int = 5000) -> bool:
logger.info(f'Check that {self.type_of} "{self.name}" is missing')
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
return False

View File

@ -1,7 +1,5 @@
from playwright.sync_api import expect
from elements.base_element import BaseElement
from tools.logger import get_logger
from elements.base_element import BaseElement
logger = get_logger("BUTTON")
@ -25,4 +23,4 @@ class Button(BaseElement):
# (Методы действий будут добавлены по мере необходимости)
# Проверки:
# (Методы проверок будут добавлены по мере необходимости)
# (Методы проверок будут добавлены по мере необходимости)

View File

@ -1,5 +1,5 @@
from elements.base_element import BaseElement
from tools.logger import get_logger
from elements.base_element import BaseElement
logger = get_logger("CHECKBOX")
@ -22,12 +22,12 @@ class Checkbox(BaseElement):
# Действия:
def check(self) -> None:
"""Устанавливает чекбокс в отмеченное состояние."""
logger.info(f'Checking checkbox "{self.name}"')
logger.info(f"Checking checkbox '{self.name}'")
self.locator.check()
def uncheck(self) -> None:
"""Снимает отметку с чекбокса."""
logger.info(f'Unchecking checkbox "{self.name}"')
logger.info(f"Unchecking checkbox '{self.name}'")
self.locator.uncheck()
# Проверки:
@ -37,5 +37,5 @@ class Checkbox(BaseElement):
Returns:
True, если чекбокс отмечен, иначе False.
"""
logger.info(f'Checking if checkbox "{self.name}" is checked')
return self.locator.is_checked()
logger.info(f"Checking if checkbox '{self.name}' is checked")
return self.locator.is_checked()

View File

@ -1,10 +1,8 @@
from playwright.sync_api import expect
from elements.base_element import BaseElement
from tools.logger import get_logger
from elements.base_element import BaseElement
logger = get_logger("DROPDOWN_LIST")
class DropdownList(BaseElement):
"""Класс для работы с выпадающими списками на странице.
@ -43,4 +41,4 @@ class DropdownList(BaseElement):
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"
assert False, f"Dropdown list item '{text}' is missing or disabled"

View File

@ -1,9 +1,8 @@
from elements.base_element import BaseElement
from tools.logger import get_logger
from elements.base_element import BaseElement
logger = get_logger("TEXT")
class Text(BaseElement):
"""Класс для работы с текстовыми элементами на странице.
@ -23,4 +22,4 @@ class Text(BaseElement):
# (Методы действий будут добавлены по мере необходимости)
# Проверки:
# (Методы проверок будут добавлены по мере необходимости)
# (Методы проверок будут добавлены по мере необходимости)

View File

@ -1,6 +1,6 @@
from playwright.sync_api import expect
from elements.base_element import BaseElement
from tools.logger import get_logger
from elements.base_element import BaseElement
logger = get_logger("TEXT_INPUT")
@ -56,4 +56,4 @@ class TextInput(BaseElement):
AssertionError: Если поле не пустое.
"""
logger.info(f'Checking that text input "{self.name}" is empty')
expect(self.locator).to_be_empty(), msg
expect(self.locator).to_be_empty(), msg

View File

@ -1,9 +1,8 @@
from elements.base_element import BaseElement
from tools.logger import get_logger
from elements.base_element import BaseElement
logger = get_logger("TOOLTIP_BUTTON")
class TooltipButton(BaseElement):
"""Класс элемента кнопки с всплывающей подсказкой.
@ -31,13 +30,13 @@ class TooltipButton(BaseElement):
"""
# Наведение на элемент для отображения подсказки
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}'"
)
)

View File

@ -5,7 +5,6 @@
import pytest
from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright, Playwright
import os
from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureRequest
@ -24,20 +23,24 @@ def pytest_addoption(parser: Parser):
--t: Таймаут по умолчанию (мс)
--l: Локаль браузера
"""
parser.addoption('--bn', action='store', default="chrome",
parser.addoption('--bn', action='store', default="chrome",
help="Choose browser: chrome, remote_chrome or firefox")
parser.addoption('--h', action='store', default=False,
parser.addoption('--h', action='store', default=False,
help='Choose headless: True or False')
parser.addoption('--s', action='store', default={'width': 1600, 'height': 900},
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,
# 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('--s', action='store', default={'width': 300, 'height': 420},
# 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,
parser.addoption('--t', action='store', default=60000,
help='Choose timeout')
parser.addoption('--l', action='store', default='ru-RU',
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')
@ -60,7 +63,7 @@ def browser(request: FixtureRequest) -> Page:
Автоматически закрывает браузер и контексты после завершения тестов.
"""
playwright = sync_playwright().start()
# Выбор браузера на основе параметра командной строки
if request.config.getoption("bn") == 'remote_chrome':
browser = get_remote_chrome(playwright, request)
@ -78,9 +81,9 @@ def browser(request: FixtureRequest) -> Page:
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()
@ -177,8 +180,8 @@ def get_context(browser: Browser, request: FixtureRequest, start: str) -> Browse
@pytest.fixture(scope="function")
def return_back(browser: Page):
"""Фикстура для возврата на предыдущую страницу в браузере.
Args:
browser: Экземпляр страницы браузера.
"""
browser.go_back()
browser.go_back()

View File

@ -1,6 +1,4 @@
class ButtonLocators:
BUTTON_LICENSE_UPDATE = "//div[@class='scrollarea__footer']//button"
TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]"
BUTTON_DELETE_SESSION = "button.v-btn--icon svg[fill='#4caf50']"

View File

@ -10,4 +10,4 @@ class ConfirmLocators:
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']"
TEXT = f"{CONFIRM}/div[2]/div[@class='v-card__text']"

View File

@ -5,4 +5,4 @@ class EventPanelLocators:
BUTTONS_BLOCK (str): XPath локатор блока кнопок в панели инструментов.
Находится во втором блоке элементов toolbar'а внутри контентной области.
"""
BUTTONS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]"
BUTTONS_BLOCK = "//nav/div[@class='v-toolbar__content']/div[@class='v-toolbar__items'][2]"

View File

@ -9,4 +9,4 @@ class InputLocators:
- Контейнер поля ввода (v-input__control)
- Непосредственно текстовое поле (textarea)
"""
LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea"
LICENSE_ID_UPDATE = "//div[@class='scrollarea__footer']//div[@class='v-input__control']//textarea"

View File

@ -8,4 +8,4 @@ class JsonContainerLocators:
Ищет div с классом, содержащим 'scrollarea__body'.
"""
CONTAINER = "//div[contains(@class,'jv-container')]"
SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]"
SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body')]"

View File

@ -4,17 +4,17 @@ class ModalWindowLocators:
Атрибуты:
MODAL_WINDOW (str): XPath локатор активного модального окна.
INPUT_FORM_USER_DATA (str): XPath локатор формы для ввода пользовательских данных.
TEXT_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath текстового поля ввода
TEXT_FIELD_INPUT_FORM_USER_DATA (str): Относительный XPath текстового поля ввода
внутри формы пользовательских данных.
ROLES_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"
LABEL_INPUT_FORM_USER_DATA = "//label[contains(@class,'v-label')]/span"

View File

@ -14,6 +14,6 @@ class NavigationPanelLocators:
"""
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')]"
NODE_CHILDREN = "//div[contains(@class,'v-treeview-node__children')]"

View File

@ -10,4 +10,4 @@ class TableLocators:
содержащего таблицу с классом 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"
TABLE_SCROLL_CONTAINER = "//div[contains(@class, 'scrollarea__body') and .//table[@class='scrolltable__container']]//tbody"

View File

@ -8,4 +8,4 @@ class TextLocators:
Ищет span с классами 'title' и 'text_select' (выделяемый текст).
"""
TITLE_LICENSE_INPUT_FORM = "//span[@class='title']"
LICENSE_ID = "//span[@class='title text_select']"
LICENSE_ID = "//span[@class='title text_select']"

View File

@ -12,4 +12,4 @@ class ToolbarLocators:
- 'menuable__content__active' (показанное состояние)
"""
TITLE = "//nav//div[contains(@class, 'v-toolbar__title')]"
TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]"
TOOLTIP = "//div[contains(@class,'v-tooltip__content menuable__content__active')]"

View File

@ -1,14 +1,15 @@
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 playwright.sync_api import Page
from tools.logger import get_logger
from locators.modal_window_locators import ModalWindowLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.dropdown_list_element import DropdownList
from elements.checkbox_element import Checkbox
from data.roles_dict import roles_dict
from components.modal_window_component import ModalWindowComponent
from components.confirm_component import ConfirmComponent
logger = get_logger("ADD_USER_MODAL_WINDOW")
@ -29,87 +30,87 @@ class AddUserModalWindow(ModalWindowComponent):
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),
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),
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)
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[4]").locator(roles_field_locator)
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",
"roles_list",
DropdownList(page, roles_menu_locator, "roles_list")
)
loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(text_field_locator)
password_input = TextInput(page, loc, "password_input")
self.add_content_item("password_input", password_input)
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),
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),
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):
@ -118,9 +119,9 @@ class AddUserModalWindow(ModalWindowComponent):
Args:
user_data (dict): Словарь с данными пользователя. Может содержать ключи:
- active_directory_checked (bool): Состояние чекбокса Active Directory
- type_auth (str): Тип авторизации
- name (str): Имя пользователя
- role (str): Роль пользователя
- password (str): Пароль пользователя
- commentary (str): Комментарий
- email (str): Email
- phone_number (str): Номер телефона
@ -130,57 +131,57 @@ class AddUserModalWindow(ModalWindowComponent):
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:
if "password" in fields:
input_field = self.get_content_item("password_input")
input_field.input_value(user_data["password"])
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.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.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,
title,
f"Confirmation dialog window with title '{title}' is missing"
)
self.new_user_confirm.click_allow_button()
@ -189,41 +190,44 @@ class AddUserModalWindow(ModalWindowComponent):
"""Закрывает модальное окно с помощью кнопки 'Закрыть'."""
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",
"Active Directory",
"Label 'Active Directory' is missing"
)
elif name == "push_notification_checkbox_label":
item.check_have_text(
"Подписка на Push-уведомления",
"Подписка на 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():
# временно, пока есть несоответствие со списком ролей в вкладке Сессии
if role == "Пользователь":
continue
roles_list.check_item_with_text(role)
elif name == "roles_list":
continue
@ -231,6 +235,6 @@ class AddUserModalWindow(ModalWindowComponent):
item.check_presence(
f"Modal window content item with name '{name}' is missing"
)
self.check_button_presence("add")
self.check_button_presence("close")
self.check_button_presence("close")

View File

@ -1,13 +1,13 @@
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 playwright.sync_api import Page
from tools.logger import get_logger
from locators.modal_window_locators import ModalWindowLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.dropdown_list_element import DropdownList
from elements.checkbox_element import Checkbox
from components.modal_window_component import ModalWindowComponent
from components.confirm_component import ConfirmComponent
logger = get_logger("EDIT_USER_MODAL_WINDOW")
@ -29,80 +29,76 @@ class EditUserModalWindow(ModalWindowComponent):
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)
loc = self.page.locator(input_form_locator).locator("xpath=div[1]").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_loc = self.page.locator(input_form_locator).locator("xpath=div[2]").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",
"roles_list",
DropdownList(page, roles_menu_locator, "roles_list")
)
loc = self.page.locator(input_form_locator).locator("xpath=div[4]").locator(text_field_locator)
loc = self.page.locator(input_form_locator).locator("xpath=div[3]").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)
loc = self.page.locator(input_form_locator).locator("xpath=div[4]").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)
loc = self.page.locator(input_form_locator).locator("xpath=div[5]").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),
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),
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, " Отмена ", " Удалить ")
@ -111,33 +107,32 @@ class EditUserModalWindow(ModalWindowComponent):
"""Закрывает модальное окно с помощью кнопки 'Закрыть'."""
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,
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): Комментарий
@ -146,48 +141,44 @@ class EditUserModalWindow(ModalWindowComponent):
- 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:
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.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.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,
title,
f"Confirmation dialog window with title '{title}' is missing"
)
self.save_user_confirm.click_allow_button()
@ -196,27 +187,27 @@ class EditUserModalWindow(ModalWindowComponent):
"""Инициирует сброс пароля пользователя."""
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-уведомления",
"Подписка на Push-уведомления",
"Label 'Подписка на Push-уведомления' is missing"
)
elif name == "name_input":
@ -236,8 +227,8 @@ class EditUserModalWindow(ModalWindowComponent):
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")
self.check_button_presence("close")

View File

@ -1,10 +1,10 @@
"""Базовый класс страницы для работы с Playwright."""
from playwright.sync_api import Page, Response, APIRequestContext, expect
from typing import Any, Dict, List, Optional
from data.environment import host
from tools.logger import get_logger
import json
from typing import Any, Dict, List, Optional
from playwright.sync_api import Page, Response, APIRequestContext, expect
from tools.logger import get_logger
from data.environment import host
logger = get_logger("BASE_PAGE")
@ -82,4 +82,4 @@ class BasePage:
return False
return True
assert compare_lists(actual, expected), msg
assert compare_lists(actual, expected), msg

View File

@ -1,20 +1,19 @@
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
from locators.text_locators import TextLocators
from locators.input_locators import InputLocators
from locators.json_container_locators import JsonContainerLocators
from locators.button_locators import ButtonLocators
from elements.text_input_element import TextInput
from elements.text_element import Text
from elements.button_element import Button
from components.toolbar_component import ToolbarComponent
from components.json_container_component import JsonContainerComponent
from components.alert_component import AlertComponent
from pages.base_page import BasePage
class LicenseTab(BasePage):
"""Класс для работы с вкладкой 'Лицензии'.
Атрибуты:
page (Page): Экземпляр страницы Playwright.
toolbar (ToolbarComponent): Компонент панели инструментов.
@ -28,15 +27,15 @@ class LicenseTab(BasePage):
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")
@ -47,34 +46,34 @@ class LicenseTab(BasePage):
# Действия:
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)
return self.json_container.is_scrollable_vertically(loc)
def check_content(self) -> None:
"""Проверяет наличие всех основных элементов на вкладке."""
self.should_be_toolbar()
@ -82,50 +81,50 @@ class LicenseTab(BasePage):
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)
self.error_alert.check_alert_presence(text)
self.error_alert.check_alert_absence(text)
def should_be_toolbar(self) -> None:
"""Проверяет наличие панели инструментов."""
self.toolbar.check_presence("Toolbar is missing")
self.toolbar.check_toolbar_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 = "Обновить лицензию"
@ -133,7 +132,7 @@ class LicenseTab(BasePage):
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)
@ -141,13 +140,13 @@ class LicenseTab(BasePage):
# 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:"
)
)

View File

@ -1,17 +1,14 @@
from playwright.sync_api import Page
from elements.button_element import Button
from elements.text_input_element import TextInput
from elements.button_element import Button
from data.environment import host
from data.constants import Constants
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): Поле ввода логина.
@ -22,28 +19,28 @@ class LoginPage(BasePage):
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: Если после входа открылась неожиданная страница.
"""
@ -53,44 +50,44 @@ class LoginPage(BasePage):
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("Неверная пара логин/пароль")
self.error_alert.check_alert_presence("Неверная пара логин/пароль")
self.error_alert.check_alert_absence("Неверная пара логин/пароль")

View File

@ -1,15 +1,14 @@
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 playwright.sync_api import Page
from locators.navigation_panel_locators import NavigationPanelLocators
from locators.event_panel_locators import EventPanelLocators
from playwright.sync_api import Page
from elements.button_element import Button
from components.navbar_component import NavigationPanelComponent
from components.card_component import CardComponent
from pages.base_page import BasePage
class MainPage(BasePage):
"""Класс для работы с главной страницей приложения.
Атрибуты:
page (Page): Экземпляр страницы Playwright.
navigation_panel (NavigationPanelComponent): Компонент панели навигации.
@ -19,63 +18,63 @@ class MainPage(BasePage):
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:
"""Проверяет наличие панели навигации."""
@ -83,28 +82,28 @@ class MainPage(BasePage):
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
)
)

View File

@ -1,8 +1,8 @@
from pages.base_page import BasePage
from playwright.sync_api import Page
from locators.table_locators import TableLocators
from components.toolbar_component import ToolbarComponent
from components.table_component import TableComponent
from locators.table_locators import TableLocators
from playwright.sync_api import Page
from pages.base_page import BasePage
class ServiceStatusTab(BasePage):
@ -17,126 +17,126 @@ class ServiceStatusTab(BasePage):
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 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,
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,
TableLocators.TABLE_WORK_AREA,
row_index
)
def should_be_toolbar(self) -> None:
"""Проверяет наличие тулбара на вкладке.
Raises:
AssertionError: Если тулбар отсутствует.
"""
self.toolbar.check_presence("Toolbar is missing")
self.toolbar.check_toolbar_presence("Toolbar is missing")
def should_be_services_table(self) -> None:
"""Проверяет наличие таблицы сервисов.
Raises:
AssertionError: Если таблица отсутствует.
"""
self.services_table.check_presence(
TableLocators.TABLE_WORK_AREA,
TableLocators.TABLE_WORK_AREA,
"Service statuses table is missing"
)
)

View File

@ -1,12 +1,11 @@
from pages.base_page import BasePage
from playwright.sync_api import Page, Locator
from locators.table_locators import TableLocators
from locators.button_locators import ButtonLocators
from elements.tooltip_button_element import TooltipButton
from data.roles_dict import roles_dict
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
from pages.base_page import BasePage
class SessionsTab(BasePage):
"""Класс для работы с вкладкой 'Сессия'.
@ -20,7 +19,7 @@ class SessionsTab(BasePage):
def __init__(self, page: Page) -> None:
"""Инициализация компонентов вкладки 'Сессия'."""
super().__init__(page)
self.toolbar = ToolbarComponent(page, "Сессия")
self.sessions_table = TableComponent(page)
@ -29,16 +28,16 @@ class SessionsTab(BasePage):
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:
@ -54,11 +53,11 @@ class SessionsTab(BasePage):
AssertionError: Если строка не найдена.
"""
row_locator = self.sessions_table.get_row_locator(
TableLocators.TABLE_WORK_AREA,
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")
@ -80,33 +79,33 @@ class SessionsTab(BasePage):
AssertionError: Если таблица пуста или заголовки не соответствуют.
"""
expected_headers = [
'ID сессии',
'ID пользователя',
'Время жизни',
'Роль',
'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, "Удалить")
@ -146,7 +145,7 @@ class SessionsTab(BasePage):
AssertionError: Если строка не выделена.
"""
self.sessions_table.check_row_highlighting(
TableLocators.TABLE_WORK_AREA,
TableLocators.TABLE_WORK_AREA,
row_index
)
@ -156,7 +155,7 @@ class SessionsTab(BasePage):
Raises:
AssertionError: Если тулбар отсутствует.
"""
self.toolbar.check_presence("Toolbar is missing")
self.toolbar.check_toolbar_presence("Toolbar is missing")
def should_be_sessions_table(self) -> None:
"""Проверяет наличие таблицы сессий.
@ -165,13 +164,13 @@ class SessionsTab(BasePage):
AssertionError: Если таблица отсутствует.
"""
self.sessions_table.check_presence(
TableLocators.TABLE_WORK_AREA,
TableLocators.TABLE_WORK_AREA,
"Sessions table is missing"
)
def should_be_delete_button_on_sessions_table_row(
self,
row_index: int,
self,
row_index: int,
tooltip: str
) -> None:
"""Проверяет наличие кнопки удаления в строке таблицы.
@ -199,19 +198,19 @@ class SessionsTab(BasePage):
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():
@ -219,13 +218,13 @@ class SessionsTab(BasePage):
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"
)
)

View File

@ -1,14 +1,13 @@
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
import re
from playwright.sync_api import Page
from modal_windows.modal_edit_user import EditUserModalWindow
from modal_windows.modal_add_user import AddUserModalWindow
from locators.table_locators import TableLocators
from data.roles_dict import roles_dict
from playwright.sync_api import Page
import re
from components.toolbar_component import ToolbarComponent
from components.table_component import TableComponent
from components.alert_component import AlertComponent
from pages.base_page import BasePage
class UsersTab(BasePage):
"""Класс для работы с вкладкой 'Пользователи'.
@ -30,12 +29,12 @@ class UsersTab(BasePage):
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")
@ -51,9 +50,9 @@ class UsersTab(BasePage):
AssertionError: Если указан неподдерживаемый тип окна.
"""
if window_type == "add_user":
self.modal_windows["add_user"] = AddUserModalWindow(self.page)
self.modal_windows["add_user"] = AddUserModalWindow(self.page)
elif window_type == "edit_user":
self.modal_windows[title] = EditUserModalWindow(self.page, title)
self.modal_windows[title] = EditUserModalWindow(self.page, title)
else:
assert False, "Unsupported modal window type"
@ -141,8 +140,8 @@ class UsersTab(BasePage):
AssertionError: Если не отображается сообщение об успешном добавлении.
"""
self.get_modal_window("add_user").new_user(user_data)
self.success_alert.check_presence(' Новый пользователь \n успешно добавлен! ')
self.success_alert.check_absence(' Новый пользователь \n успешно добавлен! ')
self.success_alert.check_alert_presence(' Новый пользователь \n успешно добавлен! ')
self.success_alert.check_alert_absence(' Новый пользователь \n успешно добавлен! ')
def delete_user(self, user_name: str) -> None:
"""Удаляет пользователя.
@ -154,8 +153,8 @@ class UsersTab(BasePage):
AssertionError: Если не отображается сообщение об успешном удалении.
"""
self.get_modal_window(user_name).delete_user()
self.success_alert.check_presence('\nПользователь удалён\n')
self.success_alert.check_absence('\nПользователь удалён\n')
self.success_alert.check_alert_presence('\nПользователь удалён\n')
self.success_alert.check_alert_absence('\nПользователь удалён\n')
def edit_user(self, user_name: str, user_data: dict) -> None:
"""Редактирует данные пользователя.
@ -168,8 +167,8 @@ class UsersTab(BasePage):
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')
self.success_alert.check_alert_presence('\nОбновление успешно\n')
self.success_alert.check_alert_absence('\nОбновление успешно\n')
def reset_password(self, user_name: str) -> str:
"""Сбрасывает пароль пользователя.
@ -182,12 +181,12 @@ class UsersTab(BasePage):
"""
new_password = ""
self.get_modal_window(user_name).reset_password()
self.success_alert.check_presence("")
self.success_alert.check_alert_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:
@ -206,9 +205,9 @@ class UsersTab(BasePage):
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
@ -216,14 +215,14 @@ class UsersTab(BasePage):
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", "")
@ -243,26 +242,26 @@ class UsersTab(BasePage):
"""
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"
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]
user_name = key
role = table_content[row_index][2]
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:
@ -275,10 +274,10 @@ class UsersTab(BasePage):
Raises:
AssertionError: Если пользователь не найден.
"""
row_index = self.find_user_in_table(user_name, role)
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()
@ -292,22 +291,22 @@ class UsersTab(BasePage):
Raises:
AssertionError: Если таблица пуста или заголовки не соответствуют.
"""
expected_headers = ['Имя пользователя', 'Роль', 'E-mail', 'Номер для СМС']
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,
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)
@ -322,44 +321,44 @@ class UsersTab(BasePage):
user_name (str): Имя пользователя
role (str): Роль пользователя
"""
edit_user_window = self.get_modal_window(user_name)
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_toolbar_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_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_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,
TableLocators.TABLE_WORK_AREA,
"Users table is missing"
)
@ -373,7 +372,7 @@ class UsersTab(BasePage):
Raises:
AssertionError: Если пользователь не найден.
"""
found = self.find_user_in_table(name, role)
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"
@ -387,7 +386,7 @@ class UsersTab(BasePage):
Raises:
AssertionError: Если пользователь найден.
"""
found = self.find_user_in_table(name, role)
found = self.find_user_in_table(name, role)
if found != -1:
assert False, f"User with name {name} and role {role} has been found"
@ -402,26 +401,31 @@ class UsersTab(BasePage):
"""
expected_users_list = []
tmp_dict = {"admin": "Администратор", "manager": "Контактное лицо", "operator": "Оператор"}
query = {
"id": ["/catalogs/user"],
"id": ["/catalogs/user"],
"data": {
"namePath": True,
"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["type_auth"] is not None:
user_info.append(item["type_auth"])
else:
user_info.append("")
if item["role"] is not None:
role = item["role"]
if role in roles_dict.keys():
@ -429,23 +433,23 @@ class UsersTab(BasePage):
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,
users_table,
expected_users_list,
"Actual users list is not equal expected users list on base db"
)
)

View File

@ -1,8 +1,8 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.license_tab import LicenseTab
from playwright.sync_api import Page
import pytest
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
from pages.license_tab import LicenseTab
class TestJsonContainer:
@ -13,7 +13,7 @@ class TestJsonContainer:
"""Фикстура для настройки тестового окружения."""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки")
@ -26,9 +26,9 @@ class TestJsonContainer:
is_scrollable = lt.check_json_container_verticall_scrolling()
assert is_scrollable, "Should be verticall scrolling"
lt.scroll_json_container_down()
lt.wait_for_timeout(3000)
lt.scroll_json_container_up()
lt.wait_for_timeout(2000)
lt.wait_for_timeout(2000)

View File

@ -1,58 +1,57 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.login_page import LoginPage
# Запуск с viewport: {'width': 300, 'height': 420}
# @pytest.mark.smoke
class TestNavigationPanel:
"""Класс тестов для проверки панели навигации.
Атрибуты:
browser: фикстура для работы с браузером
"""
def test_verticall_scrolling(self, browser):
"""Тест вертикальной прокрутки панели навигации.
Аргументы:
browser: фикстура для работы с браузером
Возвращает:
None
Исключения:
AssertionError: если панель навигации не поддерживает вертикальную прокрутку
"""
# Действия:
lp = LoginPage(browser)
lp.do_login()
# Мы на главной странице
mp = MainPage(browser)
# Проверки:
# Проверяем наличие панели навигации
mp.should_be_navigation_panel()
# Открываем все пункты панели
mp.click_main_navigation_panel_item("Настройки")
mp.click_configuration_navigation_panel_item("Аутентификация")
mp.click_configuration_navigation_panel_item("Уведомления")
mp.click_configuration_navigation_panel_item("Аутентификация")
mp.click_configuration_navigation_panel_item("Уведомления")
mp.click_configuration_navigation_panel_item("Обслуживание и диагностика")
mp.click_configuration_navigation_panel_item("Zero Touch Provisioning")
# Проверяем возможность вертикальной прокрутки
is_scrollable = mp.check_navigation_panel_verticall_scrolling()
assert is_scrollable, "Should be vertical scrolling"
# Действия:
# Прокручиваем вверх и проверяем видимость элемента
mp.scroll_navigation_panel_up()
mp.check_navigation_panel_item_visibility("Панель приборов")
mp.wait_for_timeout(3000)
# Прокручиваем вниз и проверяем видимость элемента
# Прокручиваем вниз и проверяем видимость элемента Настройки/ZTP/Шаблоны
mp.scroll_navigation_panel_down()
mp.check_navigation_panel_item_visibility("Шаблоны")
mp.wait_for_timeout(2000)
mp.check_navigation_panel_item_visibility("Шаблоны_2")
mp.wait_for_timeout(2000)

View File

@ -1,9 +1,8 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.service_status_tab import ServiceStatusTab
from playwright.sync_api import Page
import pytest
from playwright.sync_api import Page
from pages.service_status_tab import ServiceStatusTab
from pages.main_page import MainPage
from pages.login_page import LoginPage
class TestServiceStatusTable:
"""Тесты для проверки таблицы статусов сервисов."""
@ -13,7 +12,7 @@ class TestServiceStatusTable:
"""Фикстура для настройки тестового окружения."""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки")
@ -23,17 +22,17 @@ class TestServiceStatusTable:
def test_scrolling(self, browser: Page) -> None:
"""Тест проверки прокрутки таблицы статусов сервисов."""
sst = ServiceStatusTab(browser)
sst.should_be_services_table()
sst.check_services_table_content()
is_scrollable_vertically = sst.check_services_table_verticall_scrolling()
assert is_scrollable_vertically, "Should be vertical scrolling"
sst.scroll_services_table_down()
sst.check_services_table_last_row_visibility()
sst.wait_for_timeout(3000)
sst.scroll_services_table_up()
sst.check_services_table_first_row_visibility()
sst.wait_for_timeout(2000)
sst.wait_for_timeout(2000)

View File

@ -1,9 +1,8 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.users_tab import UsersTab
from playwright.sync_api import Page
import pytest
from playwright.sync_api import Page
from pages.users_tab import UsersTab
from pages.main_page import MainPage
from pages.login_page import LoginPage
class TestUsersModalWindow:
"""Тесты для проверки модальных окон работы с пользователями."""
@ -13,7 +12,7 @@ class TestUsersModalWindow:
"""Фикстура для настройки тестового окружения."""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки")
@ -25,21 +24,21 @@ class TestUsersModalWindow:
ut = UsersTab(browser)
user_name, role = ut.open_edit_user_page_by_index(0)
modal_window = ut.get_modal_window(user_name)
is_scrollable_vertically = modal_window.check_window_vertical_scrolling()
assert is_scrollable_vertically, "Should be vertical scrolling"
modal_window.scroll_window_down()
modal_window.check_button_presence("close")
ut.wait_for_timeout(3000)
modal_window.scroll_window_up()
modal_window.check_toolbar_button_presence("close")
ut.wait_for_timeout(3000)
is_scrollable_horizontally = modal_window.check_window_horizontal_scrolling()
assert is_scrollable_horizontally, "Should be horizontal scrolling"
modal_window.scroll_window_right()
ut.wait_for_timeout(3000)
modal_window.scroll_window_left()
@ -50,22 +49,23 @@ class TestUsersModalWindow:
ut = UsersTab(browser)
ut.open_add_user_window()
modal_window = ut.get_modal_window("add_user")
is_scrollable_vertically = modal_window.check_window_vertical_scrolling()
assert is_scrollable_vertically, "Should be vertical scrolling"
modal_window.scroll_window_down()
modal_window.check_button_presence("close")
ut.wait_for_timeout(3000)
modal_window.scroll_window_up()
modal_window.check_toolbar_button_presence("close")
ut.wait_for_timeout(3000)
is_scrollable_horizontally = modal_window.check_window_horizontal_scrolling()
assert is_scrollable_horizontally, "Should be horizontal scrolling"
modal_window.scroll_window_right()
ut.wait_for_timeout(3000)
modal_window.scroll_window_left()
ut.wait_for_timeout(2000)
## Временно закомментарено - для окна добавления пользователя убрали горизонтальный скроллинг - BUG???
# is_scrollable_horizontally = modal_window.check_window_horizontal_scrolling()
# assert is_scrollable_horizontally, "Should be horizontal scrolling"
# modal_window.scroll_window_right()
# ut.wait_for_timeout(3000)
# modal_window.scroll_window_left()
# ut.wait_for_timeout(2000)

View File

@ -1,11 +1,10 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.license_tab import LicenseTab
from playwright.sync_api import Page
import pytest
import uuid
from typing import List
import pytest
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
from pages.license_tab import LicenseTab
class TestLicenseTab:
"""Тесты для вкладки 'Лицензии'."""
@ -15,7 +14,7 @@ class TestLicenseTab:
"""Подготовка тестового окружения."""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки")
@ -26,7 +25,7 @@ class TestLicenseTab:
"""Тест содержимого вкладки 'Лицензии'."""
lt = LicenseTab(browser)
lt.check_content()
def test_license_tab_input_form_and_check_alert(self, browser: Page) -> None:
"""Тест формы ввода лицензии и проверки алертов."""
def gen_test_data() -> List[str]:
@ -34,24 +33,24 @@ class TestLicenseTab:
data = []
for i in range(3):
data.append(uuid.uuid4().hex)
lowercase_str = uuid.uuid4().hex
data.append(lowercase_str.upper())
data.append(lowercase_str + "fffffffff")
data.append(lowercase_str + "fffffffff")
data.append("0")
data.append("000000000000000000000000000000000000000000000000")
data.append("-1")
return data
lt = LicenseTab(browser)
lt.should_be_empty_input_form()
lt.fill_license_input_form("")
lt.should_be_error_alert_window_with_text("Неверный лицензионный ключ")
data = gen_test_data()
for data_string in data:
lt.fill_license_input_form(data_string)
lt.should_be_error_alert_window_with_text("Ошибка обновления лицензии")
lt.fill_license_input_form(data_string)
lt.should_be_error_alert_window_with_text("Ошибка обновления лицензии")

View File

@ -1,26 +1,24 @@
import pytest
from pages.login_page import LoginPage
from pages.main_page import MainPage
from playwright.sync_api import Page
from pages.main_page import MainPage
from pages.login_page import LoginPage
class TestLogin:
"""Тесты для функционала входа и выхода из системы."""
def test_successful_login(self, browser: Page) -> None:
"""Тест успешного входа в систему."""
lp = LoginPage(browser)
lp.do_login()
def test_unsuccessful_login(self, browser: Page) -> None:
"""Тест неудачного входа в систему."""
lp = LoginPage(browser)
lp.do_unsuccessful_login()
def test_successful_login_and_logout(self, browser: Page) -> None:
"""Тест успешного входа и выхода из системы."""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.do_logout()
mp.do_logout()

View File

@ -1,8 +1,7 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.service_status_tab import ServiceStatusTab
import pytest
from pages.service_status_tab import ServiceStatusTab
from pages.main_page import MainPage
from pages.login_page import LoginPage
class TestServiceStatusTab:
"""Набор тестов для вкладки 'Статус обслуживания'.
@ -20,16 +19,16 @@ class TestServiceStatusTab:
"""
lp = LoginPage(browser)
lp.do_login()
# Переход на главную страницу
mp = MainPage(browser)
# Проверка наличия панели навигации
mp.should_be_navigation_panel()
# Клик по пункту 'Настройки' в главной панели навигации
mp.click_main_navigation_panel_item("Настройки")
# Клик по пункту 'Обслуживание и диагностика' в панели навигации настроек
mp.click_configuration_navigation_panel_item("Обслуживание и диагностика")
@ -48,13 +47,13 @@ class TestServiceStatusTab:
# Проверка тулбара вкладки
sst.should_be_toolbar()
# Проверка наличия таблицы статусов сервисов
sst.should_be_services_table()
# Проверка содержимого таблицы сервисов
sst.check_services_table_content()
def test_service_status_table_row_highlighting(self, browser):
"""Тест выделения строк в таблице сервисов.
@ -67,14 +66,14 @@ class TestServiceStatusTab:
# Проверка тулбара вкладки
sst.should_be_toolbar()
# Проверка наличия таблицы статусов сервисов
sst.should_be_services_table()
# Получение количества строк в таблице
rows_count = sst.get_rows_count()
# Проверка выделения строк
sst.check_services_table_row_highlighting(0)
sst.check_services_table_row_highlighting(rows_count - 1)
sst.check_services_table_row_highlighting(int(rows_count / 2))
sst.check_services_table_row_highlighting(int(rows_count / 2))

View File

@ -1,8 +1,7 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.session_tab import SessionsTab
import pytest
from pages.session_tab import SessionsTab
from pages.main_page import MainPage
from pages.login_page import LoginPage
class TestSessionsTab:
"""Набор тестов для вкладки 'Сеансы'.
@ -21,10 +20,10 @@ class TestSessionsTab:
# Авторизация в системе
login_page = LoginPage(browser)
login_page.do_login()
# Инициализация главной страницы
main_page = MainPage(browser)
# Проверка и взаимодействие с элементами навигации
main_page.should_be_navigation_panel()
main_page.click_main_navigation_panel_item("Настройки")
@ -45,6 +44,6 @@ class TestSessionsTab:
# Проверка элементов интерфейса
sessions_tab.should_be_toolbar()
sessions_tab.should_be_sessions_table()
# Проверка содержимого таблицы с верификацией данных из БД
sessions_tab.check_sessions_table_content(verify=True)
sessions_tab.check_sessions_table_content(verify=True)

View File

@ -1,10 +1,9 @@
from pages.login_page import LoginPage
from pages.main_page import MainPage
from pages.users_tab import UsersTab
from playwright.sync_api import Page
import pytest
from typing import Dict
import pytest
from playwright.sync_api import Page
from pages.users_tab import UsersTab
from pages.main_page import MainPage
from pages.login_page import LoginPage
class TestUsersTab:
"""Тесты для вкладки 'Пользователи'."""
@ -14,7 +13,7 @@ class TestUsersTab:
"""Подготовка тестового окружения."""
lp = LoginPage(browser)
lp.do_login()
mp = MainPage(browser)
mp.should_be_navigation_panel()
mp.click_main_navigation_panel_item("Настройки")
@ -26,7 +25,7 @@ class TestUsersTab:
ut.should_be_toolbar()
ut.should_be_users_table()
ut.check_users_table_content(True)
def test_users_tab_toolbar_buttons(self, browser: Page) -> None:
"""Тест кнопок на панели инструментов."""
ut = UsersTab(browser)
@ -37,12 +36,13 @@ class TestUsersTab:
ut = UsersTab(browser)
ut.open_add_user_window()
ut.check_add_user_window_content()
def test_add_user_window_close_buttons(self, browser: Page) -> None:
"""Тест кнопок закрытия окна добавления пользователя."""
ut = UsersTab(browser)
ut.open_add_user_window()
ut.open_add_user_window()
ut.close_add_user_window_by_toolbar_button()
ut.open_add_user_window()
ut.close_add_user_window()
@ -55,18 +55,18 @@ class TestUsersTab:
def test_edit_user_window_close_buttons(self, browser: Page) -> None:
"""Тест кнопок закрытия окна редактирования пользователя."""
ut = UsersTab(browser)
user_name, role = ut.open_edit_user_page_by_index(0)
user_name, role = ut.open_edit_user_page_by_index(0)
ut.close_edit_user_window_by_toolbar_button(user_name)
user_name, role = ut.open_edit_user_page_by_index(0)
user_name, role = ut.open_edit_user_page_by_index(0)
ut.close_edit_user_window(user_name)
def test_add_and_delete_user(self, browser: Page) -> None:
"""Тест добавления и удаления пользователя."""
user_data: Dict[str, str] = {"name": "User", "role": "Администратор"}
user_data: Dict[str, str] = {"name": "User", "role": "Администратор", "password": "987654"}
mp = MainPage(browser)
ut = UsersTab(browser)
ut.open_add_user_window()
ut.add_new_user(user_data)
mp.click_configuration_navigation_panel_item("Пользователи")
@ -80,21 +80,21 @@ class TestUsersTab:
def test_reset_password(self, browser: Page) -> None:
"""Тест сброса пароля пользователя."""
user_data: Dict[str, str] = {"name": "autoadmin", "role": "Администратор"}
user_data: Dict[str, str] = {"name": "autoadmin", "role": "Администратор", "password": "123456"}
mp = MainPage(browser)
ut = UsersTab(browser)
ut.open_add_user_window()
ut.add_new_user(user_data)
mp.click_configuration_navigation_panel_item("Пользователи")
mp.click_configuration_navigation_panel_item("Пользователи")
ut.open_edit_user_page_by_user(user_data["name"], user_data["role"])
new_password = ut.reset_password(user_data["name"])
if len(new_password) == 0:
if len(new_password) == 0:
assert False, "Unsuccessful password reset"
new_lp = LoginPage(browser)
new_lp.do_login(username=user_data["name"], password=new_password)
new_mp = MainPage(browser)
@ -111,14 +111,14 @@ class TestUsersTab:
mp_1.click_configuration_navigation_panel_item("Пользователи")
mp_1.click_configuration_navigation_panel_item("Пользователи")
ut_1.should_not_be_user_in_table(user_data["name"], user_data["role"])
def test_edit_user_role(self, browser: Page) -> None:
"""Тест изменения роли пользователя."""
user_data: Dict[str, str] = {"name": "autooperator", "role": "Оператор"}
user_data: Dict[str, str] = {"name": "autooperator", "role": "Оператор", "password": "123245"}
mp = MainPage(browser)
ut = UsersTab(browser)
ut.open_add_user_window()
ut.add_new_user(user_data)
mp.click_configuration_navigation_panel_item("Пользователи")
@ -133,4 +133,4 @@ class TestUsersTab:
ut.delete_user(user_data["name"])
mp.click_configuration_navigation_panel_item("Пользователи")
mp.click_configuration_navigation_panel_item("Пользователи")
ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"])
ut.should_not_be_user_in_table(user_data["name"], new_user_data["role"])

View File

@ -25,12 +25,12 @@ INIT_TEMPLATE: str = """# Auto-generated by fix_python_project.py
class ProjectFixer:
"""Основной класс для исправления структуры Python-проекта.
Атрибуты:
root_dir (str): Корневая директория проекта.
log (List[str]): Список записей лога выполненных операций.
"""
def __init__(self, root_dir: str = '.'):
"""Инициализирует экземпляр ProjectFixer.
@ -42,7 +42,7 @@ class ProjectFixer:
def remove_bom(self, filepath: str) -> bool:
"""Удаляет BOM-маркер из файла, если он присутствует.
Обрабатывает все файлы, включая находящиеся в tests/.
Args:
@ -57,7 +57,7 @@ class ProjectFixer:
try:
with open(filepath, 'rb') as f:
content = f.read()
if content.startswith(b'\xEF\xBB\xBF'):
with open(filepath, 'wb') as f:
f.write(content[3:])
@ -69,7 +69,7 @@ class ProjectFixer:
def should_skip_init(self, dir_path: str) -> bool:
"""Проверяет, нужно ли пропустить создание __init__.py в директории.
Игнорирует служебные папки (tests/, .git/ и др.).
Args:
@ -79,7 +79,7 @@ class ProjectFixer:
bool: True, если директорию следует пропустить.
"""
dir_name = os.path.basename(dir_path)
return (dir_name in INIT_IGNORED_DIRS or
return (dir_name in INIT_IGNORED_DIRS or
dir_name.startswith('.'))
def needs_init_py(self, dir_path: str) -> bool:
@ -93,7 +93,7 @@ class ProjectFixer:
"""
if self.should_skip_init(dir_path):
return False
try:
items = os.listdir(dir_path)
has_py_files = any(f.endswith('.py') and f != '__init__.py' for f in items)
@ -149,14 +149,14 @@ class ProjectFixer:
if __name__ == '__main__':
# Обработка аргументов командной строки
target_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
# Инициализация и запуск обработки
fixer = ProjectFixer(target_dir)
print(f"Исправление структуры проекта в: {fixer.root_dir}")
fixer.process_directory()
fixer.save_log()
# Вывод результатов
print(f"Готово! Внесено {len(fixer.log)} изменений.")
print(f"Подробности сохранены в project_fix.log")
print("Подробности сохранены в project_fix.log")

View File

@ -34,4 +34,4 @@ def get_logger(name: str) -> logging.Logger:
logger.addHandler(handler)
return logger
return logger