Сохранение текущих изменений перед откатом

pull/1/head
Radislav 2025-09-11 14:34:33 +03:00
parent 257fe09aa5
commit e9f3f79f0b
77 changed files with 192 additions and 146 deletions

BIN
.txt

Binary file not shown.

View File

@ -59,7 +59,7 @@ class BaseComponent:
# return elements
# Проверки:
def check_visibility(self, locator: str | Locator, msg: str) -> None:
def check_presence(self, locator: str | Locator, msg: str) -> None:
"""Проверка видимости элемента на странице.
Args:
@ -168,7 +168,7 @@ class BaseComponent:
loc = self.get_locator(locator)
loc.evaluate("el => el.scrollBy(el.scrollWidth, 0)")
loc.evaluate("el => el.scrollBy(el.scrollWidth, 0)")
loc.wait_for(timeout=2000)

View File

@ -97,11 +97,11 @@ class ConfirmComponent(BaseComponent):
def should_be_cancel_button(self) -> None:
"""Проверяет наличие и видимость кнопки Отмены."""
self.cancel_button.check_visibility("Cancel button is missing")
self.cancel_button.check_presence("Cancel button is missing")
def should_be_allow_button(self) -> None:
"""Проверяет наличие и видимость кнопки Подтверждения."""
self.allow_button.check_visibility("Allow button is missing")
self.allow_button.check_presence("Allow button is missing")
def check_cancel_button_text(self, expected_text: str) -> None:
"""Проверяет текст кнопки Отмены."""

View File

@ -104,18 +104,18 @@ class ModalWindowComponent(BaseComponent):
self.toolbar.check_toolbar_presence(f"Modal window with '{self.toolbar.title}' is missing")
def check_button_visibility(self, name: str) -> None:
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_visibility(f"Button with name '{name}' is missing")
button.check_presence(f"Button with name '{name}' is missing")
def check_toolbar_button_presence(self, name: str) -> None:
"""Проверяет наличие кнопки в панели инструментов."""
self.toolbar.check_button_visibility(name)
self.toolbar.check_button_presence(name)
def check_toolbar_button_tooltip(self, name: str, tooltip: str) -> None:
"""Проверяет подсказку у кнопки в панели инструментов."""

View File

@ -207,4 +207,4 @@ class NavigationPanelComponent(BaseComponent):
loc = loc.get_by_text("Шаблоны").nth(1)
else:
loc = loc.get_by_text(item_name)
self.check_visibility(loc, msg)
self.check_presence(loc, msg)

View File

@ -125,7 +125,7 @@ class ToolbarComponent(BaseComponent):
locator = self.get_locator(ToolbarLocators.TITLE).filter(has_text=self.title)
expect(locator).to_be_visible(), message
def check_button_visibility(self, name: str) -> None:
def check_button_presence(self, name: str) -> None:
"""Проверяет наличие и видимость кнопки с предварительной прокруткой к элементу.
Args:
@ -141,7 +141,7 @@ class ToolbarComponent(BaseComponent):
raise AssertionError(f"Unsupported button name {name}")
button.locator.scroll_into_view_if_needed()
button.check_visibility(f"Button with name {name} is missing")
button.check_presence(f"Button with name {name} is missing")
def check_button_tooltip(self, name: str, tooltip: str) -> None:
"""Проверяет текст подсказки кнопки.

View File

@ -0,0 +1,113 @@
components
alert_component.py
Изменения включают:
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
- Сохранены все оригинальные технические сообщения в assert и raise
- Улучшено форматирование кода в соответствии с PEP 8
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
- Сохранена исходная логика работы компонента
- Добавлены пояснения к работе методов в docstring
base_component.py
Изменения включают:
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
- Комментарии разделены на русскоязычные разделы "Действия", "Проверки" и "Методы прокрутки"
- Сохранены все оригинальные технические сообщения в assert и raise
- Закомментированный код оставлен без изменений
- Улучшено форматирование кода в соответствии с PEP 8
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
- Сохранена исходная логика работы компонента
- Исправлена опечатка в имени логгера ("BASE_COMPONENT")
card_component.py
Изменения включают:
- Добавлены docstring для класса и методов в формате Google Style Guide
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
- Улучшено форматирование кода (переносы строк, отступы) в соответствии с PEP 8
- Сохранены все оригинальные технические названия и сообщения
- Добавлен placeholder для будущих методов проверок
- Улучшена читаемость инициализации logout_button за счет переноса аргументов
- Сохранена исходная функциональность компонента
- Добавлено пояснение о возможном расширении функционала проверок
confirm_component.py
Изменения включают:
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
- Улучшено форматирование кода (переносы строк, отступы) в соответствии с PEP 8
- Сохранены все оригинальные технические названия и сообщения
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
- Улучшена читаемость инициализации кнопок за счет переноса аргументов
- Сохранена исходная функциональность компонента
- Добавлены пояснения к работе каждого метода в docstring
json_container_component.py
Изменения включают:
- Добавлены подробные docstring для класса и всех методов в формате Google Style Guide
- Вложенная функция format_json_string также получила свой docstring
- Комментарии разделены на русскоязычные разделы "Действия" и "Проверки"
- Улучшено форматирование кода (отступы, пробелы вокруг операторов) в соответствии с PEP 8
- Сохранены все оригинальные технические сообщения в assert и raise
- Добавлены описания аргументов, возвращаемых значений и возможных исключений
- Исправлена опечатка в имени логгера ("JSON_CONTAINER")
- Улучшена читаемость кода за счет более последовательного форматирования
- Сохранена исходная логика работы компонента
- Добавлены пояснения к работе каждого метода в docstring
modal_window_component.py
Изменения включают:
- Исправлено имя логгера на "MODAL_WINDOW"
- Добавлены полные docstring для всех методов в Google-формате
- Улучшено форматирование кода (отступы, переносы строк)
- Сохранены все оригинальные assert-сообщения
- Добавлены типы возвращаемых значений и описания исключений
- Комментарии разделены на "Действия" и "Проверки"
- Исправлены опечатки в именах локаторов (MODAL_WINDOW)
- Улучшена читаемость кода за счет последовательного форматирования
- Сохранена вся исходная функциональность
- обавлены пояснения к работе каждого метода
navbar_component.py
Изменения включают:
- Добавлены docstring для класса и всех методов в Google-формате на русском языке
- Разделительные комментарии переведены (#actions: → # Действия:, # assertions: → # Проверки:)
- Сохранены все технические сообщения (в raise и логах) без изменений
- Сохранена исходная структура кода и рабочая логика
- Обеспечено соответствие PEP 8 (отступы, пробелы)
table_component.py
Изменения включают:
- Добавлены docstring для класса и всех методов в Google-формате на русском языке
- Разделительные комментарии переведены (#actions: → # Действия:, # assertions: → # Проверки:)
- Технические комментарии в методах переведены на русский
- Сохранены все технические сообщения (в assert, expect и логах) без изменений
- Сохранена исходная структура кода и рабочая логика
- Обеспечено соответствие PEP 8 (отступы, пробелы)
toolbar_component.py
Изменения включают:
- Полная документация:
Добавлены docstring для класса и всех методов
Указаны типы аргументов и возвращаемых значений
Описаны возможные исключения
Добавлены пояснения к важным параметрам
- Оптимизированное форматирование:
Соблюдение PEP 8 (отступы, длина строк, пробелы)
Логическая группировка методов
Улучшенные переносы длинных строк
- Улучшенная читаемость:
Последовательные именования переменных
Четкое разделение блоков
Единый стиль оформления
- Соответствие требованиям:
PEP 8
Google Python Style Guide
Требованиям из README_форматированиеода.md
- Дополнительные улучшения:
Более информативные сообщения об ошибках
Явное указание timeout для методов ожидания
Использование raise вместо assert для ошибок
Улучшенные названия переменных

View File

@ -46,13 +46,13 @@ 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_env_name(self) -> str:
"""Возвращает имя текущего окружения.
Возвращает:
str: имя текущего окружения.
"""
"""
return self.env
def get_request_url(self) -> str:

View File

@ -74,7 +74,7 @@ class BaseElement:
logger.info(f"Check that {self.type_of} '{self.name}' has text '{text}'")
expect(self.locator).to_have_text(text), msg
def check_visibility(self, msg: str) -> None:
def check_presence(self, msg: str) -> None:
"""Проверяет видимость элемента на странице."""
logger.info(f"Check that {self.type_of} '{self.name}' is present")

View File

@ -27,7 +27,7 @@ class Button(BaseElement):
return "button"
# Действия:
# Действия:
# (Методы действий будут добавлены по мере необходимости)
# Проверки:

View File

@ -30,7 +30,7 @@ def pytest_addoption(parser: Parser):
help="Choose browser: chrome, remote_chrome or firefox")
parser.addoption('--h', action='store', default=False,
help='Choose headless: True or False')
parser.addoption('--s', action='store', default="{'width': 1920, 'height': 400}",
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}",
@ -161,7 +161,7 @@ def get_context(browser: Browser, request: FixtureRequest, start: str) -> Browse
if start == 'local':
# current_viewport = json.loads(request.config.getoption('--s'))
context = browser.new_context(
# no_viewport=True,
viewport= ast.literal_eval(request.config.getoption('--s')),

View File

@ -298,7 +298,7 @@ class AddADUserModalWindow(ModalWindowComponent):
elif name == "group_input":
item.click()
group_list = self.get_content_item("group_list")
group_list.check_visibility(menu_locator,
group_list.check_presence(menu_locator,
"Groups list is missing")
is_scrollable_vertically = group_list.check_vertical_scrolling(menu_locator)
@ -307,7 +307,7 @@ class AddADUserModalWindow(ModalWindowComponent):
elif name == "role_input":
item.click()
roles_list = self.get_content_item("roles_list")
roles_list.check_visibility(menu_locator,
roles_list.check_presence(menu_locator,
"Roles list is missing")
is_scrollable_vertically = roles_list.check_vertical_scrolling(menu_locator)
@ -323,13 +323,13 @@ class AddADUserModalWindow(ModalWindowComponent):
elif name in no_op_names:
continue
else:
item.check_visibility(
item.check_presence(
f"Modal window content item with name '{name}' is missing"
)
self.check_button_visibility("search")
self.check_button_visibility("add")
self.check_button_visibility("close")
self.check_button_presence("search")
self.check_button_presence("add")
self.check_button_presence("close")
search_button = self.get_button_by_name("search")
search_button.click()
@ -344,7 +344,7 @@ class AddADUserModalWindow(ModalWindowComponent):
user_AD_input.click()
user_AD_list = self.get_content_item("user_AD_list")
user_AD_list.check_visibility(menu_locator,
user_AD_list.check_presence(menu_locator,
"Users AD list is missing")
is_scrollable_vertically = user_AD_list.check_vertical_scrolling(menu_locator)
assert is_scrollable_vertically, "Users AD list should be scrollable_vertically"
@ -352,13 +352,13 @@ class AddADUserModalWindow(ModalWindowComponent):
self.update_input_form_fields(expand=True)
self.get_content_item("name_input").check_visibility(
self.get_content_item("name_input").check_presence(
"Modal window content item with name 'name_input' is missing")
self.get_content_item("role_input").check_visibility(
self.get_content_item("role_input").check_presence(
"Modal window content item with name 'role_input' is missing")
self.get_content_item("commentary_input").check_visibility(
self.get_content_item("commentary_input").check_presence(
"Modal window content item with name 'commentary_input' is missing")
self.get_content_item("email_input").check_visibility(
self.get_content_item("email_input").check_presence(
"Modal window content item with name 'email_input' is missing")
self.get_content_item("phone_input").check_visibility(
self.get_content_item("phone_input").check_presence(
"Modal window content item with name 'phone_input' is missing")

View File

@ -226,7 +226,7 @@ class AddLocalUserModalWindow(ModalWindowComponent):
elif name == "role_input":
item.click()
roles_list = self.get_content_item("roles_list")
roles_list.check_visibility(menu_locator,
roles_list.check_presence(menu_locator,
"Roles list is missing")
is_scrollable_vertically = roles_list.check_vertical_scrolling(menu_locator)
@ -241,10 +241,9 @@ class AddLocalUserModalWindow(ModalWindowComponent):
elif name == "roles_list":
continue
else:
item.check_visibility(
item.check_presence(
f"Modal window content item with name '{name}' is missing"
)
self.check_button_visibility("add")
self.check_button_visibility("close")
self.check_button_presence("add")
self.check_button_presence("close")

View File

@ -215,17 +215,17 @@ class EditUserModalWindow(ModalWindowComponent):
elif name == "role_input":
item.click()
roles_list = self.get_content_item("roles_list")
roles_list.check_visibility(menu_locator,
roles_list.check_presence(menu_locator,
"Roles list is missing")
roles_list.check_item_with_text(role)
elif name == "roles_list":
continue
else:
item.check_visibility(
item.check_presence(
f"Modal window content item with name '{name}' is missing"
)
self.check_button_visibility("save")
self.check_button_visibility("delete")
self.check_button_visibility("reset_password")
self.check_button_visibility("close")
self.check_button_presence("save")
self.check_button_presence("delete")
self.check_button_presence("reset_password")
self.check_button_presence("close")

View File

@ -2,7 +2,6 @@
Содержит общие методы для взаимодействия со страницей и API.
"""
import time
from typing import Dict, Any
from playwright.sync_api import Page, Response, APIRequestContext, expect
@ -79,113 +78,50 @@ class BasePage:
"""
api_request_context = self.get_api_request_context()
token = host.get_access_token()
# Проверяем что токен получен
if not token:
logger.error("Failed to get access token: token is None or empty")
# Возвращаем заглушечный response или бросаем исключение
# В Playwright можно создать mock response если нужно
return None
headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"}
full_url = f"{host.get_request_url()}{uri}"
logger.debug("Sending GET request to: %s", full_url)
response = api_request_context.get(full_url, headers=headers)
# Логируем статус ответа
logger.debug("GET response status: %s", response.status)
response = api_request_context.get(
f"{host.get_request_url()}{uri}",
headers=headers
)
return response
def send_post_api_request(self, uri: str, payload: Dict) -> Response:
"""Отправляет POST-запрос к API."""
"""Отправляет POST-запрос к API.
Args:
uri (str): URI API-эндпоинта (без базового URL).
payload (Dict): Данные для отправки в теле запроса.
Returns:
Response: Ответ сервера.
"""
api_request_context = self.get_api_request_context()
token = host.get_access_token()
if not token:
logger.error("Failed to get access token: token is None or empty")
return None
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
full_url = f"{host.get_request_url()}{uri}"
logger.debug("Sending POST request to: %s", full_url)
# Сериализуем payload в JSON
json_data = json.dumps(payload)
# Проверяем что сериализация прошла успешно
if json_data is None:
logger.error("Failed to serialize payload to JSON: result is None")
return None
if not isinstance(json_data, str):
logger.error("Failed to serialize payload to JSON: expected string got %s", type(json_data))
return None
headers = {"Accept": "application/json", "Authorization": f"Bearer {token}"}
response = api_request_context.post(
full_url,
f"{host.get_request_url()}{uri}",
headers=headers,
data=json_data # Передаем сериализованный JSON как data
data=payload
)
logger.debug("POST response status: %s", response.status)
return response
def get_response_body(self, response: Response) -> dict | list | None:
def get_response_body(self, response: Response) -> dict | None:
"""Извлекает тело ответа в format JSON.
Args:
response (Response): Ответ сервера.
Returns:
dict | list | None: Распарсенное тело ответа или None в случае ошибки.
dict | None: Распарсенное тело ответа или None в случае ошибки.
"""
start_time = time.time()
# Проверяем что response не None
if response is None:
logger.error("Response object is None")
processing_time = time.time() - start_time
logger.debug("Response processing time1: %.3f seconds", processing_time)
try:
response_body = response.json()
except json.JSONDecodeError:
logger.error("Failed to decode JSON response")
return None
return response_body
# Проверяем статус ответа
if response.status >= 400:
logger.error("API request failed with status %s", response.status)
processing_time = time.time() - start_time
logger.debug("Response processing time2: %.3f seconds", processing_time)
return None
# Пытаемся получить JSON
json_result = response.json()
# Проверяем что результат не None
if json_result is None:
logger.error("JSON parsing returned None")
processing_time = time.time() - start_time
logger.debug("Response processing time3: %.3f seconds", processing_time)
return None
# Принимаем как словари, так и списки
if not isinstance(json_result, (dict, list)):
logger.error("Expected dict or list but got %s", type(json_result))
processing_time = time.time() - start_time
logger.debug("Response processing time4: %.3f seconds", processing_time)
return None
processing_time = time.time() - start_time
logger.debug("Response processing time5: %.3f seconds", processing_time)
return json_result
# Проверки:
# Проверки:
def check_URL(self, uri: str, msg: str) -> None:
"""Проверяет, что текущий URL соответствует ожидаемому.
@ -196,7 +132,7 @@ class BasePage:
Raises:
AssertionError: Если URL не соответствует ожидаемому.
"""
expect(self.page).to_have_url( # pylint: disable=expression-not-assigned
expect(self.page).to_have_url(
f"{host.get_base_url()}{uri}",
timeout=60000
), msg

View File

@ -113,7 +113,7 @@ class LicenseTab(BasePage):
def should_be_json_container(self) -> None:
"""Проверяет наличие JSON-контейнера."""
self.json_container.check_visibility(
self.json_container.check_presence(
JsonContainerLocators.CONTAINER,
"Json container with license info is missing"
)

View File

@ -93,7 +93,7 @@ class MainPage(BasePage):
def should_be_navigation_panel(self) -> None:
"""Проверяет наличие панели навигации."""
self.navigation_panel.check_visibility(
self.navigation_panel.check_presence(
NavigationPanelLocators.PANEL_MAIN,
"Navigation panel is missing"
)
@ -101,7 +101,7 @@ class MainPage(BasePage):
def should_be_user_button(self) -> None:
"""Проверяет наличие кнопки пользователя."""
self.user_button.check_visibility("User button is missing on event panel")
self.user_button.check_presence("User button is missing on event panel")
def check_navigation_panel_verticall_scrolling(self) -> bool:
"""Проверяет возможность вертикальной прокрутки панели.

View File

@ -154,8 +154,7 @@ class ServiceStatusTab(BasePage):
AssertionError: Если таблица отсутствует.
"""
self.services_table.check_visibility(
self.services_table.check_presence(
TableLocators.TABLE_WORK_AREA,
"Service statuses table is missing"
)

View File

@ -139,7 +139,6 @@ class SessionsTab(BasePage):
# Находим кнопку удаления сеанса и нажимаем на нее
delete_session_button = self.get_delete_session_button_from_row(row_index)
delete_session_button.click()
self.page.wait_for_timeout(1000)
# Подтверждаем удаление
self.delete_session_confirm.click_allow_button()
@ -312,7 +311,7 @@ class SessionsTab(BasePage):
AssertionError: Если таблица отсутствует.
"""
self.sessions_table.check_visibility(
self.sessions_table.check_presence(
TableLocators.TABLE_WORK_AREA,
"Sessions table is missing"
)
@ -336,7 +335,7 @@ class SessionsTab(BasePage):
self.wait_for_tooltip_to_disappear()
delete_button = self.get_delete_session_button_from_row(row_index)
delete_button.check_visibility(
delete_button.check_presence(
f"Delete session button is missing on {row_index} row"
)
delete_button.check_tooltip_with_text(ButtonLocators.TOOLTIP, tooltip)

View File

@ -293,10 +293,10 @@ class UsersTab(BasePage):
"""
if self.toolbar.is_button_not_present("close"):
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_presence("edit")
self.toolbar.click_button("edit")
self.toolbar.check_button_visibility("add_user")
self.toolbar.check_button_presence("add_user")
self.toolbar.click_button("add_user")
self.add_modal_window("add_local_user", "")
self.get_modal_window("add_local_user").check_by_window_title()
@ -441,7 +441,7 @@ class UsersTab(BasePage):
"""
self.toolbar.check_toolbar_presence("Toolbar is missing")
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_presence("edit")
def should_be_toolbar_buttons(self) -> None:
"""Проверяет наличие и функциональность кнопок тулбара.
@ -450,17 +450,17 @@ class UsersTab(BasePage):
AssertionError: Если кнопки недоступны или подсказки неверны.
"""
self.toolbar.check_button_visibility("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_visibility("add_user")
self.toolbar.check_button_visibility("close")
self.toolbar.check_button_presence("add_user")
self.toolbar.check_button_presence("close")
self.toolbar.check_button_tooltip("add_user", "Добавить")
self.toolbar.check_button_tooltip("close", "Закрыть")
self.toolbar.get_button_by_name("close").click()
self.toolbar.check_button_visibility("edit")
self.toolbar.check_button_presence("edit")
def should_be_users_table(self) -> None:
"""Проверяет наличие таблицы пользователей.
@ -469,7 +469,7 @@ class UsersTab(BasePage):
AssertionError: Если таблица отсутствует.
"""
self.users_table.check_visibility(
self.users_table.check_presence(
TableLocators.TABLE_WORK_AREA,
"Users table is missing"
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -50,7 +50,7 @@ class TestUsersModalWindow:
assert is_scrollable_vertically, "Should be vertical scrolling"
modal_window.scroll_window_down()
modal_window.check_button_visibility("close")
modal_window.check_button_presence("close")
ut.wait_for_timeout(3000)
modal_window.scroll_window_up()
@ -84,7 +84,7 @@ class TestUsersModalWindow:
assert is_scrollable_vertically, "Should be vertical scrolling"
modal_window.scroll_window_down()
modal_window.check_button_visibility("close")
modal_window.check_button_presence("close")
ut.wait_for_timeout(3000)
modal_window.scroll_window_up()