""" 测试框架配置模块 提供全局配置管理和环境变量加载功能 """ 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