""" 异常处理器模块 提供测试异常分类处理功能,区分致命错误和可重试错误。 """ from typing import Optional, List from playwright.sync_api import Page class FatalTestError(Exception): """致命测试错误""" pass class RetryableError(Exception): """可重试错误""" pass class TestExceptionHandler: """测试异常处理器""" # 致命错误:立即停止测试 FATAL_ERRORS: List[str] = [ "Browser crashed", "Connection refused", "Target page closed", "Database connection failed", "Session deleted", "invalid session id", ] # 可重试错误:自动重试 RETRYABLE_ERRORS: List[str] = [ "TimeoutError", "Timeout exceeded", "Element not found", "Network error", "net::ERR", "Stale element reference", "element is detached", "Execution context was destroyed", "Unable to locate element", "waiting for locator", ] # 非致命错误:记录但继续 NON_FATAL_ERRORS: List[str] = [ "Screenshot failed", "Log write failed", "Screenshot is not supported", ] @classmethod def handle_exception( cls, error: Exception, page: Optional[Page] = None, test_name: str = "", screenshot_helper=None, ) -> Optional[Exception]: """ 处理测试异常 Args: error: 异常对象 page: Playwright页面对象 test_name: 测试名称 screenshot_helper: 截图辅助工具 Returns: None: 非致命错误,继续执行 RetryableError: 可重试错误 FatalTestError: 致命错误,停止测试 """ error_msg = str(error) error_type = type(error).__name__ # 致命错误 if any(fatal in error_msg for fatal in cls.FATAL_ERRORS): if screenshot_helper and page: screenshot_helper.take_screenshot( page, f"{test_name}_fatal_error" if test_name else "fatal_error" ) raise FatalTestError(f"测试遇到致命错误 [{error_type}]: {error_msg}") # 可重试错误 if any(retryable in error_msg for retryable in cls.RETRYABLE_ERRORS): if screenshot_helper and page: screenshot_helper.take_screenshot( page, f"{test_name}_retryable_error" if test_name else "retryable_error", ) return RetryableError(f"可重试错误 [{error_type}]: {error_msg}") # 非致命错误 if any(non_fatal in error_msg for non_fatal in cls.NON_FATAL_ERRORS): # 仅记录错误,不抛出 return None # 未知错误 - 根据错误类型判断 if "assert" in error_msg.lower(): # 断言错误通常是测试逻辑问题,不重试 raise error # 其他错误视为可重试 if screenshot_helper and page: screenshot_helper.take_screenshot( page, f"{test_name}_unknown_error" if test_name else "unknown_error" ) return RetryableError(f"未知错误 [{error_type}]: {error_msg}") @classmethod def is_fatal_error(cls, error: Exception) -> bool: """判断是否为致命错误""" error_msg = str(error) return any(fatal in error_msg for fatal in cls.FATAL_ERRORS) @classmethod def is_retryable_error(cls, error: Exception) -> bool: """判断是否为可重试错误""" error_msg = str(error) return any(retryable in error_msg for retryable in cls.RETRYABLE_ERRORS)