Initial commit

This commit is contained in:
KamilM1205 2026-01-19 23:32:11 +04:00
commit 9795660e1f
43 changed files with 2757 additions and 0 deletions

0
config/__init__.py Normal file
View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

76
config/api_config.py Normal file
View file

@ -0,0 +1,76 @@
import os
from typing import Dict, Any, List
from config.environment import EnvironmentLoader
class APIConfig:
"""Настройки API"""
# Базовые настройки API
API_BASE_URL = EnvironmentLoader.get_env_variable('API_BASE_URL', 'http://localhost:8080/api/v1')
API_VERSION = EnvironmentLoader.get_env_variable('API_VERSION', 'v1')
API_TIMEOUT = EnvironmentLoader.get_env_variable('API_TIMEOUT', 30) # секунды
# Настройки запросов
MAX_RETRIES = EnvironmentLoader.get_env_variable('MAX_RETRIES', 3)
RETRY_DELAY = EnvironmentLoader.get_env_variable('RETRY_DELAY', 1) # секунды
RETRY_BACKOFF_FACTOR = EnvironmentLoader.get_env_variable('RETRY_BACKOFF_FACTOR', 2)
# Настройки валидации
VALIDATE_SCHEMAS = EnvironmentLoader.get_env_variable('VALIDATE_SCHEMAS', True)
STRICT_VALIDATION = EnvironmentLoader.get_env_variable('STRICT_VALIDATION', False)
# Настройки тестовых данных
TEST_DATA_PREFIX = EnvironmentLoader.get_env_variable('TEST_DATA_PREFIX', 'test_')
CLEANUP_AFTER_TESTS = EnvironmentLoader.get_env_variable('CLEANUP_AFTER_TESTS', True)
CLEANUP_ONLY_FAILED = EnvironmentLoader.get_env_variable('CLEANUP_ONLY_FAILED', False)
# Endpoints (можно переопределить через окружение)
ENDPOINTS = {
'auth_login': EnvironmentLoader.get_env_variable('ENDPOINT_AUTH_LOGIN', '/login'),
'auth_logout': EnvironmentLoader.get_env_variable('ENDPOINT_AUTH_LOGOUT', '/logout'),
'users_create': EnvironmentLoader.get_env_variable('ENDPOINT_USERS_CREATE', '/team/'),
'users_get_all': EnvironmentLoader.get_env_variable('ENDPOINT_USERS_GET_ALL', '/members/'),
'users_get_by_id': EnvironmentLoader.get_env_variable('ENDPOINT_USERS_GET_BY_ID', '/members/{id}'),
'users_get_by_name': EnvironmentLoader.get_env_variable('ENDPOINT_USERS_GET_BY_NAME', '/members/name/{name}'),
'users_update': EnvironmentLoader.get_env_variable('ENDPOINT_USERS_UPDATE', '/team/{id}'),
'users_delete': EnvironmentLoader.get_env_variable('ENDPOINT_USERS_DELETE', '/team/{id}'),
'posts_create': EnvironmentLoader.get_env_variable('ENDPOINT_POSTS_CREATE', '/post'),
'posts_get_all': EnvironmentLoader.get_env_variable('ENDPOINT_POSTS_GET_ALL', '/post'),
'posts_get_by_id': EnvironmentLoader.get_env_variable('ENDPOINT_POSTS_GET_BY_ID', '/post/{id}'),
'posts_get_by_offset': EnvironmentLoader.get_env_variable('ENDPOINT_POSTS_GET_BY_OFFSET', '/post/offset/{offset}'),
'posts_update': EnvironmentLoader.get_env_variable('ENDPOINT_POSTS_UPDATE', '/post/{id}'),
'posts_delete': EnvironmentLoader.get_env_variable('ENDPOINT_POSTS_DELETE', '/post/{id}'),
'images_upload': EnvironmentLoader.get_env_variable('ENDPOINT_IMAGES_UPLOAD', '/images/'),
'images_get': EnvironmentLoader.get_env_variable('ENDPOINT_IMAGES_GET', '/images/{path}'),
'images_delete': EnvironmentLoader.get_env_variable('ENDPOINT_IMAGES_DELETE', '/images/{path}'),
'images_list': EnvironmentLoader.get_env_variable('ENDPOINT_IMAGES_LIST', '/images'),
}
# Настройки по умолчанию для запросов
DEFAULT_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0',
'Accept': 'application/json',
'Accept-Language': EnvironmentLoader.get_env_variable('API_ACCEPT_LANGUAGE', 'en-US,en;q=0.9'),
}
# Настройки прокси (если нужно)
PROXY_ENABLED = EnvironmentLoader.get_env_variable('PROXY_ENABLED', False)
PROXY_HTTP = EnvironmentLoader.get_env_variable('PROXY_HTTP', None)
PROXY_HTTPS = EnvironmentLoader.get_env_variable('PROXY_HTTPS', None)
@classmethod
def get_endpoint(cls, endpoint_name: str, **kwargs) -> str:
"""Получение endpoint с подстановкой параметров"""
endpoint_template = cls.ENDPOINTS.get(endpoint_name)
if not endpoint_template:
raise ValueError(f"Endpoint '{endpoint_name}' not found in configuration")
return cls.API_BASE_URL + endpoint_template.format(**kwargs)
@classmethod
def get_all_endpoints(cls) -> Dict[str, str]:
"""Получение всех endpoints"""
return cls.ENDPOINTS.copy()
# Экспорт конфигурации
api_config = APIConfig

152
config/environment.py Normal file
View file

@ -0,0 +1,152 @@
import os
import sys
from pathlib import Path
from typing import Optional, Dict, Any
from dotenv import load_dotenv, find_dotenv
import logging
logger = logging.getLogger(__name__)
class EnvironmentLoader:
"""Класс для загрузки переменных окружения из .env файлов"""
# Порядок загрузки файлов .env (от более специфичного к общему)
ENV_FILES_ORDER = [
'.env.local', # Локальные переопределения (не в git)
f'.env.{os.getenv("ENV", "development")}', # Окружение: .env.test, .env.production
'.env', # Основной файл
]
@classmethod
def load_environment(cls, env_file: Optional[str] = None):
"""
Загрузка переменных окружения.
Args:
env_file: Конкретный файл для загрузки (если None, используется порядок из ENV_FILES_ORDER)
"""
if env_file:
# Загрузка конкретного файла
env_path = Path(env_file)
if env_path.exists():
logger.info(f"Loading environment from specified file: {env_path}")
load_dotenv(dotenv_path=env_path, override=True)
else:
logger.warning(f"Specified env file not found: {env_path}")
return
# Автоматическая загрузка в определенном порядке
env_loaded = False
for env_filename in cls.ENV_FILES_ORDER:
env_path = find_dotenv(env_filename, usecwd=True)
if env_path:
logger.info(f"Loading environment from: {env_path}")
load_dotenv(dotenv_path=env_path, override=True)
env_loaded = True
if not env_loaded:
logger.warning("No .env files found, using system environment variables")
@classmethod
def get_env_variable(cls, key: str, default: Any = None, required: bool = False) -> Any:
"""
Получение переменной окружения с проверкой.
Args:
key: Ключ переменной окружения
default: Значение по умолчанию, если переменная не найдена
required: Обязательна ли переменная (вызывает исключение, если не найдена)
Returns:
Значение переменной окружения
Raises:
ValueError: Если переменная required=True и не найдена
"""
value = os.getenv(key)
if value is None:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
# Автоматическое преобразование типов
if value.lower() in ('true', 'false'):
return value.lower() == 'true'
elif value.isdigit():
return int(value)
elif cls._is_float(value):
return float(value)
elif value.startswith('[') and value.endswith(']'):
# Список значений, разделенных запятыми
return [item.strip() for item in value[1:-1].split(',') if item.strip()]
return value
@staticmethod
def _is_float(value: str) -> bool:
"""Проверка, можно ли преобразовать строку в float"""
try:
float(value)
return True
except ValueError:
return False
@classmethod
def validate_environment(cls):
"""Валидация обязательных переменных окружения"""
required_vars = [
# 'API_BASE_URL',
# 'UI_BASE_URL',
]
missing_vars = []
for var in required_vars:
if not os.getenv(var):
missing_vars.append(var)
if missing_vars:
raise EnvironmentError(
f"Missing required environment variables: {', '.join(missing_vars)}\n"
f"Please set them in .env file or system environment."
)
@classmethod
def print_environment_info(cls):
"""Вывод информации о текущем окружении (для отладки)"""
env_info = {
'ENV': os.getenv('ENV', 'development'),
'API_BASE_URL': os.getenv('API_BASE_URL'),
'UI_BASE_URL': os.getenv('UI_BASE_URL'),
'DEBUG': os.getenv('DEBUG', 'False'),
'LOG_LEVEL': os.getenv('LOG_LEVEL', 'INFO'),
}
logger.info("Current environment configuration:")
for key, value in env_info.items():
logger.info(f" {key}: {value}")
@classmethod
def get_all_env_variables(cls, prefix: str = '') -> Dict[str, Any]:
"""
Получение всех переменных окружения с опциональным префиксом.
Args:
prefix: Фильтр по префиксу (например, 'API_' для всех API настроек)
Returns:
Словарь переменных окружения
"""
env_vars = {}
for key, _ in os.environ.items():
if len(prefix) == 0 and not key.startswith(prefix):
continue
env_vars[key] = cls.get_env_variable(key)
return env_vars
# Инициализация при импорте модуля
EnvironmentLoader.load_environment()

42
config/session_config.py Normal file
View file

@ -0,0 +1,42 @@
from typing import Dict
from config.environment import EnvironmentLoader
class SessionConfig:
"""Настройки сессий и авторизации"""
# Настройки сессий
SESSION_TIMEOUT = EnvironmentLoader.get_env_variable('SESSION_TIMEOUT', 1800) # 30 минут
SESSION_COOKIE_NAME = EnvironmentLoader.get_env_variable('SESSION_COOKIE_NAME', 'session')
SESSION_COOKIE_SECURE = EnvironmentLoader.get_env_variable('SESSION_COOKIE_SECURE', True)
SESSION_COOKIE_HTTPONLY = EnvironmentLoader.get_env_variable('SESSION_COOKIE_HTTPONLY', False)
# Настройки администратора для тестов
ADMIN_USERNAME = EnvironmentLoader.get_env_variable('ADMIN_USERNAME', 'muts')
ADMIN_PASSWORD = EnvironmentLoader.get_env_variable('ADMIN_PASSWORD', 'Abc1205')
@classmethod
def get_admin_credentials(cls) -> Dict[str, str]:
"""Получение учетных данных администратора"""
return {
"username": cls.ADMIN_USERNAME,
"password": cls.ADMIN_PASSWORD,
}
@classmethod
def get_session_headers(cls) -> Dict[str, str]:
"""Получение заголовков для сессий"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0",
"Accept": "application/json",
"Content-Type": "application/json",
"Host": "localhost:8080",
"Origin": "http://localhost:8080",
}
if cls.SESSION_COOKIE_SECURE:
headers["X-Requested-With"] = "XMLHttpRequest"
return headers
# Экспорт конфигурации
session_config = SessionConfig

168
config/settings.py Normal file
View file

@ -0,0 +1,168 @@
import logging
from typing import Dict, Any
from pathlib import Path
from config.environment import EnvironmentLoader
# Инициализация логгера
logger = logging.getLogger(__name__)
class Settings:
"""Основные настройки приложения"""
# Загрузка переменных окружения
ENV = EnvironmentLoader.get_env_variable('ENV', 'development')
DEBUG = EnvironmentLoader.get_env_variable('DEBUG', False)
# Базовые URL
API_BASE_URL = EnvironmentLoader.get_env_variable('API_BASE_URL', 'http://localhost:8080/api/v1')
UI_BASE_URL = EnvironmentLoader.get_env_variable('UI_BASE_URL', 'http://localhost:5173')
# Настройки логирования
LOG_LEVEL = EnvironmentLoader.get_env_variable('LOG_LEVEL', 'INFO').upper()
LOG_FORMAT = EnvironmentLoader.get_env_variable(
'LOG_FORMAT',
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
LOG_FILE = EnvironmentLoader.get_env_variable('LOG_FILE', 'logs/tests.log')
# Настройки тестов
TEST_TIMEOUT = EnvironmentLoader.get_env_variable('TEST_TIMEOUT', 30) # секунды
TEST_RETRIES = EnvironmentLoader.get_env_variable('TEST_RETRIES', 3)
TEST_PARALLEL_WORKERS = EnvironmentLoader.get_env_variable('TEST_PARALLEL_WORKERS', 'auto')
# Настройки Allure
ALLURE_RESULTS_DIR = EnvironmentLoader.get_env_variable('ALLURE_RESULTS_DIR', 'allure-results')
ALLURE_REPORT_DIR = EnvironmentLoader.get_env_variable('ALLURE_REPORT_DIR', 'allure-report')
# Настройки ожиданий
IMPLICIT_WAIT = EnvironmentLoader.get_env_variable('IMPLICIT_WAIT', 10) # секунды
EXPLICIT_WAIT = EnvironmentLoader.get_env_variable('EXPLICIT_WAIT', 30) # секунды
POLL_FREQUENCY = EnvironmentLoader.get_env_variable('POLL_FREQUENCY', 0.5) # секунды
# Базовые пути
PROJECT_ROOT = Path(__file__).parent.parent
TESTS_DIR = PROJECT_ROOT / 'tests'
DATA_DIR = TESTS_DIR / 'data'
REPORTS_DIR = PROJECT_ROOT / 'reports'
# Создание необходимых директорий
@classmethod
def create_directories(cls):
"""Создание необходимых директорий"""
directories = [
cls.REPORTS_DIR,
cls.DATA_DIR,
Path(cls.LOG_FILE).parent if cls.LOG_FILE else None,
Path(cls.ALLURE_RESULTS_DIR).parent if cls.ALLURE_RESULTS_DIR else None,
]
for directory in directories:
if directory and not directory.exists():
directory.mkdir(parents=True, exist_ok=True)
logger.debug(f"Created directory: {directory}")
@classmethod
def get_logging_config(cls) -> Dict[str, Any]:
"""Конфигурация логирования"""
return {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': cls.LOG_FORMAT,
'datefmt': '%Y-%m-%d %H:%M:%S',
},
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': cls.LOG_LEVEL,
'formatter': 'standard',
'stream': 'ext://sys.stdout',
},
'file': {
'class': 'logging.FileHandler',
'level': cls.LOG_LEVEL,
'formatter': 'detailed',
'filename': cls.LOG_FILE,
'mode': 'a',
},
},
'loggers': {
'': { # root logger
'handlers': ['console', 'file'],
'level': cls.LOG_LEVEL,
'propagate': True,
},
'tests': {
'handlers': ['console', 'file'],
'level': cls.LOG_LEVEL,
'propagate': False,
},
'api': {
'handlers': ['console', 'file'],
'level': cls.LOG_LEVEL,
'propagate': False,
},
'ui': {
'handlers': ['console', 'file'],
'level': cls.LOG_LEVEL,
'propagate': False,
},
},
}
@classmethod
def setup_logging(cls):
"""Настройка логирования"""
import logging.config
logging.config.dictConfig(cls.get_logging_config())
logger.info(f"Logging configured with level: {cls.LOG_LEVEL}")
@classmethod
def validate(cls):
"""Валидация настроек"""
# Проверка обязательных переменных
EnvironmentLoader.validate_environment()
# Проверка URL
import validators
if not validators.url(cls.API_BASE_URL):
logger.warning(f"API_BASE_URL might be invalid: {cls.API_BASE_URL}")
if not validators.url(cls.UI_BASE_URL):
logger.warning(f"UI_BASE_URL might be invalid: {cls.UI_BASE_URL}")
# Создание директорий
cls.create_directories()
logger.info(f"Settings validated for environment: {cls.ENV}")
@classmethod
def print_summary(cls):
"""Вывод сводки настроек"""
summary = f"""
===== Environment Settings =====
Environment: {cls.ENV}
Debug Mode: {cls.DEBUG}
API Base URL: {cls.API_BASE_URL}
UI Base URL: {cls.UI_BASE_URL}
Log Level: {cls.LOG_LEVEL}
Test Timeout: {cls.TEST_TIMEOUT}s
Test Retries: {cls.TEST_RETRIES}
Parallel Workers: {cls.TEST_PARALLEL_WORKERS}
=================================
"""
logger.info(summary)
# Инициализация настроек при импорте
Settings.validate()
Settings.setup_logging()
Settings.print_summary()
# Экспорт настроек
settings = Settings

90
config/ui_config.py Normal file
View file

@ -0,0 +1,90 @@
from typing import Dict, Any
from config.environment import EnvironmentLoader
class UIConfig:
"""Настройки UI тестов"""
# Базовые настройки
UI_BASE_URL = EnvironmentLoader.get_env_variable('UI_BASE_URL', 'http://localhost:5173')
UI_TIMEOUT = EnvironmentLoader.get_env_variable('UI_TIMEOUT', 30) # секунды
# Настройки браузера
BROWSER_NAME = EnvironmentLoader.get_env_variable('BROWSER_NAME', 'firefox').lower()
BROWSER_HEADLESS = EnvironmentLoader.get_env_variable('BROWSER_HEADLESS', True)
BROWSER_WIDTH = EnvironmentLoader.get_env_variable('BROWSER_WIDTH', 1920)
BROWSER_HEIGHT = EnvironmentLoader.get_env_variable('BROWSER_HEIGHT', 1080)
BROWSER_LANGUAGE = EnvironmentLoader.get_env_variable('BROWSER_LANGUAGE', 'en')
BROWSER_FULLSCREEN = EnvironmentLoader.get_env_variable('BROWSER_FULLSCREEN', False)
# Настройки Chrome
CHROME_HEADLESS_NEW = EnvironmentLoader.get_env_variable('CHROME_HEADLESS_NEW', True)
CHROME_DISABLE_GPU = EnvironmentLoader.get_env_variable('CHROME_DISABLE_GPU', True)
CHROME_NO_SANDBOX = EnvironmentLoader.get_env_variable('CHROME_NO_SANDBOX', True)
CHROME_DISABLE_DEV_SHM = EnvironmentLoader.get_env_variable('CHROME_DISABLE_DEV_SHM', True)
# Настройки Firefox
FIREFOX_HEADLESS = EnvironmentLoader.get_env_variable('FIREFOX_HEADLESS', True)
# Настройки ожиданий
IMPLICIT_WAIT = EnvironmentLoader.get_env_variable('IMPLICIT_WAIT', 10) # секунды
EXPLICIT_WAIT = EnvironmentLoader.get_env_variable('EXPLICIT_WAIT', 30) # секунды
PAGE_LOAD_TIMEOUT = EnvironmentLoader.get_env_variable('PAGE_LOAD_TIMEOUT', 60) # секунды
SCRIPT_TIMEOUT = EnvironmentLoader.get_env_variable('SCRIPT_TIMEOUT', 30) # секунды
# Настройки скриншотов
SCREENSHOTS_ON_FAILURE = EnvironmentLoader.get_env_variable('SCREENSHOTS_ON_FAILURE', True)
SCREENSHOTS_DIR = EnvironmentLoader.get_env_variable('SCREENSHOTS_DIR', 'screenshots')
SCREENSHOTS_FORMAT = EnvironmentLoader.get_env_variable('SCREENSHOTS_FORMAT', 'png')
# Пути
URLS = {
"team": EnvironmentLoader.get_env_variable('UI_URL_TEAM', '/team'),
"blog": EnvironmentLoader.get_env_variable('UI_URL_BLOG', '/blog')
}
@classmethod
def get_url(cls, url_name: str, **kwargs) -> str:
"""Получение endpoint с подстановкой параметров"""
url_template = cls.URLS.get(url_name)
if not url_template:
raise ValueError(f"URL '{url_name}' not found in configuration")
return cls.UI_BASE_URL + url_template.format(**kwargs)
@classmethod
def get_browser_options(cls) -> Dict[str, Any]:
"""Получение опций браузера"""
options = {
'headless': cls.BROWSER_HEADLESS,
'width': cls.BROWSER_WIDTH,
'height': cls.BROWSER_HEIGHT,
'language': cls.BROWSER_LANGUAGE,
}
if cls.BROWSER_NAME == 'chrome':
options.update({
'headless_new': cls.CHROME_HEADLESS_NEW,
'disable_gpu': cls.CHROME_DISABLE_GPU,
'no_sandbox': cls.CHROME_NO_SANDBOX,
'disable_dev_shm': cls.CHROME_DISABLE_DEV_SHM,
})
elif cls.BROWSER_NAME == 'firefox':
options.update({
'headless': cls.FIREFOX_HEADLESS,
})
return options
@classmethod
def get_wait_config(cls) -> Dict[str, Any]:
"""Получение конфигурации ожиданий"""
return {
'implicit': cls.IMPLICIT_WAIT,
'explicit': cls.EXPLICIT_WAIT,
'page_load': cls.PAGE_LOAD_TIMEOUT,
'script': cls.SCRIPT_TIMEOUT,
}
# Экспорт конфигурации
ui_config = UIConfig