Files
novalon-website/e2e-tests/config/settings.py
T
张翔 f14002559e feat(e2e-tests): 添加端到端测试框架及测试用例
refactor(components): 调整头部和页脚布局样式
style(hero-section): 更新徽章动画效果

docs: 添加测试框架README文档
test: 实现首页、导航和联系表单的测试用例
ci: 添加CI测试脚本和配置
2026-02-02 19:36:33 +08:00

259 lines
9.3 KiB
Python

"""
测试框架配置模块
提供全局配置管理和环境变量加载功能
"""
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
@dataclass
class BrowserConfig:
"""浏览器配置类"""
name: str
headless: bool = False
viewport_width: int = 1920
viewport_height: int = 1080
device_scale_factor: float = 1.0
is_mobile: bool = False
has_touch: bool = False
locale: str = "zh-CN"
timezone_id: str = "Asia/Shanghai"
@dataclass
class PerformanceThresholds:
"""性能指标阈值配置"""
page_load_time: int = 3000 # 毫秒
first_contentful_paint: int = 1500
largest_contentful_paint: int = 2500
time_to_interactive: int = 3000
first_byte: int = 500
dom_content_loaded: int = 1000
@dataclass
class ResponsiveBreakpoints:
"""响应式测试断点配置"""
mobile: Dict[str, int] = field(default_factory=lambda: {"width": 375, "height": 667})
tablet: Dict[str, int] = field(default_factory=lambda: {"width": 768, "height": 1024})
desktop: Dict[str, int] = field(default_factory=lambda: {"width": 1920, "height": 1080})
wide: Dict[str, int] = field(default_factory=lambda: {"width": 2560, "height": 1440})
class Settings:
"""全局配置管理类"""
_instance: Optional['Settings'] = None
_initialized: bool = False
def __new__(cls) -> 'Settings':
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not Settings._initialized:
self._load_config()
Settings._initialized = True
def _load_config(self) -> None:
"""加载所有配置"""
# 基础配置
self.base_url = self._get_env("TEST_BASE_URL", "http://localhost:3000")
self.fallback_url = self._get_env("TEST_BASE_URL_FALLBACK", "")
self.test_env = self._get_env("TEST_ENV", "development")
# 浏览器配置
self.default_browser = self._get_env("DEFAULT_BROWSER", "chromium")
self.headless_mode = self._get_env("HEADLESS_MODE", "false").lower() == "true"
self.viewport_width = int(self._get_env("DEFAULT_VIEWPORT_WIDTH", 1920))
self.viewport_height = int(self._get_env("DEFAULT_VIEWPORT_HEIGHT", 1080))
# 超时配置
self.max_retries = int(self._get_env("MAX_RETRIES", 2))
self.test_timeout = int(self._get_env("TEST_TIMEOUT", 60))
self.page_load_timeout = int(self._get_env("PAGE_LOAD_TIMEOUT", 30000))
self.element_timeout = int(self._get_env("ELEMENT_TIMEOUT", 10000))
# 并行配置
self.parallel_workers = int(self._get_env("PARALLEL_WORKERS", 4))
# 截图和视频
self.screenshot_on_failure = self._get_env("SCREENSHOT_ON_FAILURE", "true").lower() == "true"
self.video_recording = self._get_env("VIDEO_RECORDING", "false").lower() == "true"
self.screenshots_dir = self._get_env("SCREENSHOTS_DIR", "reports/screenshots")
self.videos_dir = self._get_env("VIDEOS_DIR", "reports/videos")
# 日志配置
self.log_level = self._get_env("LOG_LEVEL", "INFO")
self.log_file = self._get_env("LOG_FILE", "reports/e2e_tests.log")
self.console_log = self._get_env("CONSOLE_LOG", "true").lower() == "true"
# 报告配置
self.report_title = self._get_env("REPORT_TITLE", "Novalon Website E2E测试报告")
self.report_description = self._get_env(
"REPORT_DESCRIPTION",
"Novalon Website端到端自动化测试报告"
)
self.junit_xml_report = self._get_env("JUNIT_XML_REPORT", "false").lower() == "true"
self.junit_xml_path = self._get_env("JUNIT_XML_PATH", "reports/test-results.xml")
# 性能阈值
self._load_performance_thresholds()
# 响应式断点
self._load_responsive_breakpoints()
# 浏览器列表
self.browsers_to_test = ["chromium", "firefox", "webkit"]
# 测试数据
self._load_test_form_data()
# CI/CD配置
self.ci = self._get_env("CI", "false").lower() == "true"
self.git_branch = self._get_env("GIT_BRANCH", "")
self.git_commit = self._get_env("GIT_COMMIT", "")
self.git_repository = self._get_env("GIT_REPOSITORY", "")
# 创建必要的目录
self._create_directories()
def _get_env(self, key: str, default: str) -> str:
"""获取环境变量"""
return os.environ.get(key, default)
def _load_performance_thresholds(self) -> None:
"""加载性能阈值配置"""
import json
thresholds_str = self._get_env(
"PERFORMANCE_THRESHOLDS",
'{"page_load_time": 3000, "first_contentful_paint": 1500, '
'"largest_contentful_paint": 2500, "time_to_interactive": 3000, '
'"first_byte": 500, "dom_content_loaded": 1000}'
)
try:
thresholds = json.loads(thresholds_str)
self.performance_thresholds = PerformanceThresholds(**thresholds)
except (json.JSONDecodeError, TypeError):
self.performance_thresholds = PerformanceThresholds()
def _load_responsive_breakpoints(self) -> None:
"""加载响应式断点配置"""
import json
breakpoints_str = self._get_env(
"RESPONSIVE_BREAKPOINTS",
'{"mobile": {"width": 375, "height": 667}, '
'"tablet": {"width": 768, "height": 1024}, '
'"desktop": {"width": 1920, "height": 1080}, '
'"wide": {"width": 2560, "height": 1440}}'
)
try:
breakpoints = json.loads(breakpoints_str)
self.responsive_breakpoints = ResponsiveBreakpoints(**breakpoints)
except (json.JSONDecodeError, TypeError):
self.responsive_breakpoints = ResponsiveBreakpoints()
def _load_test_form_data(self) -> None:
"""加载测试表单数据"""
import json
form_data_str = self._get_env(
"TEST_FORM_DATA",
'{"valid": {"name": "测试用户", "phone": "13800138000", '
'"email": "test@example.com", "subject": "测试主题", '
'"message": "这是一条测试消息,用于验证表单功能是否正常。"}, '
'"invalid": {"email": "invalid-email", "phone": "123"}}'
)
try:
self.test_form_data = json.loads(form_data_str)
except json.JSONDecodeError:
self.test_form_data = {
"valid": {
"name": "测试用户",
"phone": "13800138000",
"email": "test@example.com",
"subject": "测试主题",
"message": "这是一条测试消息,用于验证表单功能是否正常。"
},
"invalid": {
"email": "invalid-email",
"phone": "123"
}
}
def _create_directories(self) -> None:
"""创建必要的目录"""
base_dirs = [
self.screenshots_dir,
self.videos_dir,
"reports",
"logs"
]
for dir_path in base_dirs:
Path(dir_path).mkdir(parents=True, exist_ok=True)
def get_browser_config(self, browser_name: Optional[str] = None) -> BrowserConfig:
"""获取浏览器配置"""
name = browser_name or self.default_browser
return BrowserConfig(
name=name,
headless=self.headless_mode,
viewport_width=self.viewport_width,
viewport_height=self.viewport_height
)
def get_test_data_path(self, filename: str) -> Path:
"""获取测试数据文件路径"""
return Path(__file__).parent / "test_data" / filename
def get_reports_path(self, filename: str = "") -> Path:
"""获取报告目录路径"""
reports_dir = Path("reports")
reports_dir.mkdir(parents=True, exist_ok=True)
if filename:
return reports_dir / filename
return reports_dir
def is_ci_environment(self) -> bool:
"""检查是否为CI环境"""
return self.ci or os.environ.get("CI", "").lower() in ["true", "1"]
def get_base_url(self) -> str:
"""获取测试基础URL,自动降级到备用URL"""
# 首先检查基础URL是否可用
if self._check_url_accessible(self.base_url):
return self.base_url
# 如果基础URL不可用,尝试备用URL
if self.fallback_url and self._check_url_accessible(self.fallback_url):
return self.fallback_url
# 如果都不可用,返回基础URL(测试时会报错)
return self.base_url
def _check_url_accessible(self, url: str) -> bool:
"""检查URL是否可访问"""
import requests
try:
response = requests.get(url, timeout=5)
return response.status_code < 500
except requests.RequestException:
return False
# 全局配置实例
settings = Settings()
def get_settings() -> Settings:
"""获取全局配置实例"""
return settings