# -*- coding: utf-8 -*-
"""消息格式化工具模块
提供统一的消息格式化功能,改善用户交互体验。
"""
from typing import Optional, List, Dict, Any
from enum import Enum
class MessageType(Enum):
"""消息类型枚举"""
SUCCESS = "success"
ERROR = "error"
WARNING = "warning"
INFO = "info"
PROGRESS = "progress"
class MessageFormatter:
"""消息格式化器
提供统一的消息格式化,包含图标、结构化信息等。
"""
# 消息类型对应的图标
ICONS = {
MessageType.SUCCESS: "✅",
MessageType.ERROR: "❌",
MessageType.WARNING: "⚠️",
MessageType.INFO: "ℹ️",
MessageType.PROGRESS: "⏳"
}
@classmethod
def format(
cls,
message: str,
msg_type: MessageType = MessageType.INFO,
details: Optional[str] = None,
suggestions: Optional[List[str]] = None,
duration_ms: Optional[float] = None
) -> str:
"""格式化消息
Args:
message: 主消息内容
msg_type: 消息类型
details: 详细信息
suggestions: 建议列表
duration_ms: 操作耗时(毫秒)
"""
icon = cls.ICONS.get(msg_type, "")
result = f"{icon} {message}"
if duration_ms is not None:
result += f" ({duration_ms:.0f}ms)"
if details:
result += f"\n {details}"
if suggestions:
result += "\n💡 建议:"
for suggestion in suggestions:
result += f"\n • {suggestion}"
return result
@classmethod
def success(cls, message: str, duration_ms: float = None) -> str:
"""成功消息"""
return cls.format(message, MessageType.SUCCESS, duration_ms=duration_ms)
@classmethod
def error(cls, message: str, suggestions: List[str] = None) -> str:
"""错误消息"""
return cls.format(message, MessageType.ERROR, suggestions=suggestions)
@classmethod
def warning(cls, message: str, details: str = None) -> str:
"""警告消息"""
return cls.format(message, MessageType.WARNING, details=details)
@classmethod
def info(cls, message: str) -> str:
"""信息消息"""
return cls.format(message, MessageType.INFO)
@classmethod
def progress(cls, message: str, current: int = None, total: int = None) -> str:
"""进度消息"""
if current is not None and total is not None:
percentage = (current / total) * 100
message = f"{message} [{current}/{total}] {percentage:.0f}%"
return cls.format(message, MessageType.PROGRESS)
class ErrorSuggestions:
"""错误建议生成器
根据错误类型生成相应的修复建议。
"""
# 常见错误的建议映射
SUGGESTIONS = {
"browser_not_connected": [
"调用 connect_browser() 连接浏览器",
"检查浏览器是否已启动",
"确认调试端口(默认9222)是否正确"
],
"element_not_found": [
"使用 take_screenshot() 确认页面状态",
"使用 find_elements() 查找正确的选择器",
"检查元素是否在iframe中",
"等待页面加载完成后重试"
],
"timeout": [
"检查网络连接是否正常",
"页面可能加载过慢,稍后重试",
"检查浏览器是否响应正常"
],
"selector_invalid": [
"检查选择器语法是否正确",
"使用 get_dom_tree() 获取正确的选择器",
"尝试使用其他选择器类型(css/xpath/text)"
],
"screenshot_failed": [
"检查浏览器窗口是否最小化",
"确认页面已完全加载",
"尝试使用 full_page=False 截取可视区域"
]
}
@classmethod
def get_suggestions(cls, error_type: str) -> List[str]:
"""获取错误建议"""
return cls.SUGGESTIONS.get(error_type, [
"检查操作参数是否正确",
"查看日志获取详细错误信息"
])
@classmethod
def detect_error_type(cls, error_message: str) -> str:
"""根据错误消息检测错误类型"""
error_lower = error_message.lower()
if "连接" in error_message or "browser" in error_lower:
return "browser_not_connected"
elif "未找到" in error_message or "not found" in error_lower or "不存在" in error_message:
return "element_not_found"
elif "超时" in error_message or "timeout" in error_lower:
return "timeout"
elif "选择器" in error_message or "selector" in error_lower:
return "selector_invalid"
elif "截图" in error_message or "screenshot" in error_lower:
return "screenshot_failed"
else:
return "unknown"
@classmethod
def format_error_with_suggestions(cls, error_message: str) -> str:
"""格式化错误消息并附带建议"""
error_type = cls.detect_error_type(error_message)
suggestions = cls.get_suggestions(error_type)
return MessageFormatter.error(error_message, suggestions)
class ResultFormatter:
"""结果格式化器
用于格式化各种操作结果。
"""
@classmethod
def format_element_list(cls, elements: List[Dict[str, Any]], max_display: int = 5) -> str:
"""格式化元素列表"""
if not elements:
return "未找到匹配的元素"
total = len(elements)
display_count = min(total, max_display)
lines = [f"找到 {total} 个元素:"]
for i, elem in enumerate(elements[:display_count]):
tag = elem.get("tag", "unknown")
text = elem.get("text", "")[:30]
attrs = elem.get("attributes", {})
# 构建简洁的元素描述
desc_parts = [f"[{i}] <{tag}>"]
if attrs.get("id"):
desc_parts.append(f"#{attrs['id']}")
if attrs.get("class"):
classes = attrs['class'].split()[:2] # 最多显示2个class
desc_parts.append(f".{'.'.join(classes)}")
if text:
desc_parts.append(f'"{text}..."' if len(elem.get("text", "")) > 30 else f'"{text}"')
lines.append(" " + " ".join(desc_parts))
if total > max_display:
lines.append(f" ... 还有 {total - max_display} 个元素")
return "\n".join(lines)
@classmethod
def format_browser_status(cls, status: Dict[str, Any]) -> str:
"""格式化浏览器状态"""
if not status.get("connected"):
return MessageFormatter.error("浏览器未连接")
lines = [MessageFormatter.success("浏览器已连接")]
if status.get("current_url"):
lines.append(f" 📍 URL: {status['current_url']}")
if status.get("title"):
lines.append(f" 📄 标题: {status['title']}")
if status.get("tab_count"):
lines.append(f" 📑 标签页数: {status['tab_count']}")
return "\n".join(lines)
@classmethod
def format_screenshot_result(cls, path: str, duration_ms: float = None) -> str:
"""格式化截图结果"""
msg = f"截图已保存: {path}"
return MessageFormatter.success(msg, duration_ms)
@classmethod
def format_click_result(cls, selector: str, success: bool, details: str = None) -> str:
"""格式化点击结果"""
if success:
return MessageFormatter.success(f"已点击元素: {selector}")
else:
suggestions = ErrorSuggestions.get_suggestions("element_not_found")
return MessageFormatter.error(f"点击失败: {selector}", suggestions)