666 lines
26 KiB
Python
666 lines
26 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Appium 交互式UI测试工具 - 主入口文件
|
||
支持Android应用的UI自动化测试、XML布局分析、视觉比对等功能
|
||
"""
|
||
|
||
import os
|
||
import time
|
||
import threading
|
||
import datetime
|
||
import base64
|
||
from pathlib import Path
|
||
from appium import webdriver
|
||
from appium.options.android import UiAutomator2Options
|
||
from appium.webdriver.common.appiumby import AppiumBy
|
||
import keyboard
|
||
|
||
# 导入自定义模块
|
||
from config import Config
|
||
from xml_analyzer import XMLAnalyzer
|
||
from visual_comparator import VisualComparator
|
||
from test_reporter import TestReporter
|
||
|
||
|
||
class InteractiveUITester:
|
||
"""交互式UI测试工具"""
|
||
|
||
def __init__(self):
|
||
self.driver = None
|
||
self.running = False
|
||
|
||
# 使用配置文件中的设置
|
||
self.screenshots_dir = Config.SCREENSHOTS_DIR
|
||
self.xml_dir = Config.XML_LAYOUTS_DIR
|
||
self.designs_dir = Config.DESIGN_REFERENCES_DIR
|
||
self.comparisons_dir = Config.VISUAL_COMPARISONS_DIR
|
||
|
||
# 设置目录
|
||
Config.setup_directories()
|
||
|
||
# 初始化分析器
|
||
self.xml_analyzer = XMLAnalyzer()
|
||
self.visual_comparator = VisualComparator()
|
||
self.test_reporter = TestReporter()
|
||
|
||
# 性能监控数据
|
||
self.performance_data = {
|
||
'start_time': None,
|
||
'operations': [],
|
||
'memory_usage': []
|
||
}
|
||
|
||
# Appium配置
|
||
self.capabilities = Config.DEVICE_CAPABILITIES
|
||
self.appium_server_url = Config.APPIUM_SERVER_URL
|
||
|
||
def connect_device(self):
|
||
"""连接到Appium服务器"""
|
||
try:
|
||
print("🔌 正在连接Appium服务器...")
|
||
self.driver = webdriver.Remote(
|
||
self.appium_server_url,
|
||
options=UiAutomator2Options().load_capabilities(self.capabilities)
|
||
)
|
||
self.performance_data['start_time'] = time.time()
|
||
print("✅ 成功连接到设备!")
|
||
return True
|
||
except Exception as e:
|
||
print(f"❌ 连接失败: {e}")
|
||
return False
|
||
|
||
def disconnect_device(self):
|
||
"""断开设备连接"""
|
||
if self.driver:
|
||
try:
|
||
self.driver.quit()
|
||
print("🔌 已断开设备连接")
|
||
except:
|
||
pass
|
||
self.driver = None
|
||
|
||
def take_screenshot(self):
|
||
"""截图功能"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接,无法截图")
|
||
return None
|
||
|
||
try:
|
||
start_time = time.time()
|
||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
filename = f"screenshot_{timestamp}.png"
|
||
filepath = self.screenshots_dir / filename
|
||
|
||
# 获取截图
|
||
screenshot_base64 = self.driver.get_screenshot_as_base64()
|
||
screenshot_data = base64.b64decode(screenshot_base64)
|
||
|
||
# 保存截图
|
||
with open(filepath, 'wb') as f:
|
||
f.write(screenshot_data)
|
||
|
||
# 记录性能数据
|
||
operation_time = time.time() - start_time
|
||
self.performance_data['operations'].append({
|
||
'type': 'screenshot',
|
||
'time': operation_time,
|
||
'timestamp': timestamp
|
||
})
|
||
|
||
print(f"📸 截图已保存: {filepath}")
|
||
return filepath
|
||
|
||
except Exception as e:
|
||
print(f"❌ 截图失败: {e}")
|
||
return None
|
||
|
||
def save_page_xml(self):
|
||
"""保存当前页面XML布局"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接,无法获取XML")
|
||
return None
|
||
|
||
try:
|
||
start_time = time.time()
|
||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
filename = f"layout_{timestamp}.xml"
|
||
filepath = self.xml_dir / filename
|
||
|
||
# 获取页面源码
|
||
page_source = self.driver.page_source
|
||
|
||
# 保存XML
|
||
with open(filepath, 'w', encoding='utf-8') as f:
|
||
f.write(page_source)
|
||
|
||
# 分析XML
|
||
if self.xml_analyzer.parse_xml(page_source):
|
||
stats = self.xml_analyzer.get_statistics()
|
||
print(f"📄 XML布局已保存: {filepath}")
|
||
print(f"📊 分析结果: {stats['total_elements']}个元素, {stats['issues_count']}个问题")
|
||
|
||
# 显示主要问题
|
||
if self.xml_analyzer.issues:
|
||
print("⚠️ 发现的主要问题:")
|
||
for issue in self.xml_analyzer.issues[:3]: # 只显示前3个
|
||
print(f" • {issue['element']}: {issue['issue']}")
|
||
if len(self.xml_analyzer.issues) > 3:
|
||
print(f" ... 还有 {len(self.xml_analyzer.issues) - 3} 个问题")
|
||
|
||
# 记录性能数据
|
||
operation_time = time.time() - start_time
|
||
self.performance_data['operations'].append({
|
||
'type': 'xml_analysis',
|
||
'time': operation_time,
|
||
'timestamp': timestamp
|
||
})
|
||
|
||
return filepath
|
||
|
||
except Exception as e:
|
||
print(f"❌ 保存XML失败: {e}")
|
||
return None
|
||
|
||
def compare_with_design(self):
|
||
"""与设计图进行视觉比对(仅视觉比对)"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接,无法进行比对")
|
||
return
|
||
|
||
# 检查设计图目录
|
||
supported_formats = Config.get_supported_design_formats()
|
||
design_files = []
|
||
for fmt in supported_formats:
|
||
design_files.extend(list(self.designs_dir.glob(f"*{fmt}")))
|
||
|
||
if not design_files:
|
||
print(f"❌ 未找到设计图,请将设计图放入 {self.designs_dir} 目录")
|
||
print(f"💡 支持的格式: {', '.join(supported_formats)}")
|
||
return
|
||
|
||
# 截图
|
||
screenshot_path = self.take_screenshot()
|
||
if not screenshot_path:
|
||
return
|
||
|
||
try:
|
||
# 使用最新的设计图进行比对
|
||
design_path = max(design_files, key=lambda x: x.stat().st_mtime)
|
||
print(f"🎨 使用设计图: {design_path.name}")
|
||
|
||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
comparison_path = self.comparisons_dir / f"comparison_{timestamp}.png"
|
||
|
||
# 执行视觉比对
|
||
print("👁️ 开始视觉比对...")
|
||
visual_result = self.visual_comparator.compare_images(
|
||
screenshot_path, design_path, comparison_path
|
||
)
|
||
|
||
if visual_result:
|
||
similarity = visual_result['similarity_score'] * 100
|
||
diff_count = len(visual_result['diff_regions'])
|
||
|
||
print(f"📊 视觉比对结果:")
|
||
print(f" 相似度: {similarity:.1f}%")
|
||
print(f" 差异区域: {diff_count}个")
|
||
print(f" 比对结果: {comparison_path}")
|
||
|
||
if similarity >= Config.VISUAL_COMPARISON['excellent_threshold'] * 100:
|
||
print("✅ 视觉效果优秀,与设计高度一致")
|
||
elif similarity >= Config.VISUAL_COMPARISON['good_threshold'] * 100:
|
||
print("⚠️ 视觉效果良好,有轻微差异")
|
||
else:
|
||
print("❌ 视觉差异较大,建议检查实现")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 视觉比对失败: {e}")
|
||
|
||
def compare_layout_only(self):
|
||
"""仅进行XML-SVG布局比对"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接,无法进行比对")
|
||
return
|
||
|
||
# 检查SVG设计图
|
||
svg_files = list(self.designs_dir.glob("*.svg"))
|
||
if not svg_files:
|
||
print(f"❌ 未找到SVG设计图,请将SVG文件放入 {self.designs_dir} 目录")
|
||
return
|
||
|
||
# 保存当前页面XML
|
||
xml_path = self.save_page_xml()
|
||
if not xml_path:
|
||
return
|
||
|
||
try:
|
||
# 使用最新的SVG设计图
|
||
svg_path = max(svg_files, key=lambda x: x.stat().st_mtime)
|
||
print(f"📐 使用SVG设计图: {svg_path.name}")
|
||
|
||
# 执行布局比对
|
||
layout_result = self.xml_analyzer.compare_layouts(str(svg_path))
|
||
|
||
if layout_result:
|
||
print(f"\n📊 布局比对详细结果:")
|
||
print(f" XML元素数量: {layout_result['xml_elements_count']}")
|
||
print(f" SVG元素数量: {layout_result['svg_elements_count']}")
|
||
print(f" 匹配元素数量: {len(layout_result['matched_elements'])}")
|
||
print(f" 布局相似度: {layout_result['layout_similarity']:.1%}")
|
||
|
||
# 显示匹配详情
|
||
if layout_result['matched_elements']:
|
||
print(f"\n✅ 匹配的元素:")
|
||
for i, match in enumerate(layout_result['matched_elements'][:5]): # 只显示前5个
|
||
xml_elem = match['xml_element']
|
||
svg_elem = match['svg_element']
|
||
print(f" {i+1}. {xml_elem['type']} ↔ {svg_elem['type']} (相似度: {match['similarity']:.1%})")
|
||
if xml_elem.get('text') or svg_elem.get('text'):
|
||
print(f" 文本: '{xml_elem.get('text', '')}' ↔ '{svg_elem.get('text', '')}'")
|
||
|
||
# 显示未匹配元素
|
||
if layout_result['unmatched_xml']:
|
||
print(f"\n⚠️ 未匹配的XML元素 ({len(layout_result['unmatched_xml'])}个):")
|
||
for i, elem in enumerate(layout_result['unmatched_xml'][:3]): # 只显示前3个
|
||
print(f" {i+1}. {elem['type']} - '{elem.get('text', '无文本')}'")
|
||
|
||
if layout_result['unmatched_svg']:
|
||
print(f"\n⚠️ 未匹配的SVG元素 ({len(layout_result['unmatched_svg'])}个):")
|
||
for i, elem in enumerate(layout_result['unmatched_svg'][:3]): # 只显示前3个
|
||
print(f" {i+1}. {elem['type']} - '{elem.get('text', '无文本')}'")
|
||
|
||
# 总结
|
||
print(f"\n{self.xml_analyzer.get_layout_comparison_summary()}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 布局比对失败: {e}")
|
||
|
||
def analyze_current_page(self):
|
||
"""分析当前页面(XML分析和布局比对)"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接")
|
||
return
|
||
|
||
print("🔍 开始综合页面分析...")
|
||
|
||
# 1. 保存XML并分析
|
||
xml_path = self.save_page_xml()
|
||
|
||
# 2. 截图
|
||
screenshot_path = self.take_screenshot()
|
||
|
||
# 3. 检查是否有SVG设计图进行布局比对
|
||
supported_formats = Config.get_supported_design_formats()
|
||
design_files = []
|
||
for fmt in supported_formats:
|
||
design_files.extend(list(self.designs_dir.glob(f"*{fmt}")))
|
||
|
||
# 查找SVG设计图
|
||
svg_files = [f for f in design_files if f.suffix.lower() == '.svg']
|
||
|
||
if svg_files:
|
||
print("\n📐 发现SVG设计图,开始布局比对分析...")
|
||
# 使用最新的SVG设计图
|
||
svg_design = max(svg_files, key=lambda x: x.stat().st_mtime)
|
||
print(f"🎨 使用SVG设计图: {svg_design.name}")
|
||
|
||
# 执行XML-SVG布局比对
|
||
print("🔍 开始XML-SVG布局比对...")
|
||
layout_result = self.xml_analyzer.compare_layouts(str(svg_design))
|
||
|
||
if layout_result:
|
||
print(f"📐 布局比对结果:")
|
||
print(f" XML元素: {layout_result['xml_elements_count']}个")
|
||
print(f" SVG元素: {layout_result['svg_elements_count']}个")
|
||
print(f" 匹配元素: {len(layout_result['matched_elements'])}个")
|
||
print(f" 布局相似度: {layout_result['layout_similarity']:.1%}")
|
||
|
||
if layout_result['layout_similarity'] >= 0.8:
|
||
print("✅ 布局高度一致")
|
||
elif layout_result['layout_similarity'] >= 0.6:
|
||
print("⚠️ 布局基本一致,有少量差异")
|
||
else:
|
||
print("❌ 布局差异较大,建议检查")
|
||
|
||
# 显示未匹配的元素
|
||
if layout_result['unmatched_xml']:
|
||
print(f" 未匹配XML元素: {len(layout_result['unmatched_xml'])}个")
|
||
if layout_result['unmatched_svg']:
|
||
print(f" 未匹配SVG元素: {len(layout_result['unmatched_svg'])}个")
|
||
else:
|
||
print("\n💡 未发现SVG设计图,跳过布局比对分析")
|
||
print(f" 提示:将SVG设计图放入 {self.designs_dir} 目录可进行布局比对")
|
||
|
||
# 4. 生成报告
|
||
print("\n📋 生成测试报告...")
|
||
self.generate_test_report()
|
||
|
||
def generate_test_report(self):
|
||
"""生成测试报告"""
|
||
try:
|
||
# 添加分析数据到报告
|
||
self.test_reporter.add_xml_analysis(self.xml_analyzer)
|
||
self.test_reporter.add_visual_comparison(self.visual_comparator)
|
||
self.test_reporter.add_performance_data(self.performance_data)
|
||
|
||
# 生成报告
|
||
report_format = Config.REPORT['default_format']
|
||
report_path = self.test_reporter.generate_report(report_format)
|
||
|
||
# 如果配置了生成HTML报告,也生成HTML版本
|
||
if Config.REPORT['generate_html'] and report_format != 'html':
|
||
html_report = self.test_reporter.generate_report('html')
|
||
print(f"📊 HTML报告: {html_report}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 生成报告失败: {e}")
|
||
|
||
def find_and_click_element(self):
|
||
"""智能元素定位和点击"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接")
|
||
return
|
||
|
||
try:
|
||
# 获取所有可点击元素
|
||
clickable_elements = self.driver.find_elements(AppiumBy.XPATH, '//*[@clickable="true"]')
|
||
|
||
if not clickable_elements:
|
||
print("❌ 未找到可点击元素")
|
||
return
|
||
|
||
print(f"🔘 找到 {len(clickable_elements)} 个可点击元素:")
|
||
|
||
for i, element in enumerate(clickable_elements[:10]): # 显示前10个
|
||
try:
|
||
text = element.text or element.get_attribute('content-desc') or f"元素{i+1}"
|
||
bounds = element.get_attribute('bounds')
|
||
print(f" {i+1}. {text} - {bounds}")
|
||
except:
|
||
print(f" {i+1}. 未知元素")
|
||
|
||
# 让用户选择要点击的元素
|
||
try:
|
||
choice = input("\n请输入要点击的元素编号 (1-10, 或按Enter跳过): ").strip()
|
||
if choice and choice.isdigit():
|
||
index = int(choice) - 1
|
||
if 0 <= index < len(clickable_elements):
|
||
element = clickable_elements[index]
|
||
element.click()
|
||
print(f"✅ 已点击元素 {choice}")
|
||
time.sleep(1) # 等待页面响应
|
||
else:
|
||
print("❌ 无效的元素编号")
|
||
except:
|
||
pass
|
||
|
||
except Exception as e:
|
||
print(f"❌ 元素定位失败: {e}")
|
||
|
||
def monitor_performance(self):
|
||
"""性能监控"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接")
|
||
return
|
||
|
||
try:
|
||
# 获取设备信息
|
||
device_info = {
|
||
'platform': self.driver.capabilities.get('platformName'),
|
||
'device_name': self.driver.capabilities.get('deviceName'),
|
||
'app_package': self.driver.current_package,
|
||
'app_activity': self.driver.current_activity
|
||
}
|
||
|
||
# 计算运行时间
|
||
if self.performance_data['start_time']:
|
||
runtime = time.time() - self.performance_data['start_time']
|
||
print(f"⏱️ 性能监控:")
|
||
print(f" 运行时间: {runtime:.1f}秒")
|
||
print(f" 操作次数: {len(self.performance_data['operations'])}")
|
||
|
||
if self.performance_data['operations']:
|
||
avg_time = sum(op['time'] for op in self.performance_data['operations']) / len(self.performance_data['operations'])
|
||
print(f" 平均操作时间: {avg_time:.2f}秒")
|
||
|
||
print(f" 当前应用: {device_info['app_package']}")
|
||
print(f" 当前Activity: {device_info['app_activity']}")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 性能监控失败: {e}")
|
||
|
||
def get_current_activity(self):
|
||
"""获取当前Activity信息"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接")
|
||
return
|
||
|
||
try:
|
||
current_activity = self.driver.current_activity
|
||
current_package = self.driver.current_package
|
||
print(f"📱 当前应用: {current_package}")
|
||
print(f"📱 当前Activity: {current_activity}")
|
||
except Exception as e:
|
||
print(f"❌ 获取Activity信息失败: {e}")
|
||
|
||
def find_elements_info(self):
|
||
"""显示当前页面元素信息"""
|
||
if not self.driver:
|
||
print("❌ 设备未连接")
|
||
return
|
||
|
||
try:
|
||
# 查找所有可点击元素
|
||
clickable_elements = self.driver.find_elements(AppiumBy.XPATH, '//*[@clickable="true"]')
|
||
print(f"🔘 找到 {len(clickable_elements)} 个可点击元素")
|
||
|
||
# 查找所有文本元素
|
||
text_elements = self.driver.find_elements(AppiumBy.XPATH, '//*[@text!=""]')
|
||
print(f"📝 找到 {len(text_elements)} 个文本元素")
|
||
|
||
# 显示前5个文本元素
|
||
print("📝 前5个文本元素:")
|
||
for i, element in enumerate(text_elements[:5]):
|
||
try:
|
||
text = element.text
|
||
bounds = element.get_attribute('bounds')
|
||
print(f" {i+1}. '{text}' - 位置: {bounds}")
|
||
except:
|
||
pass
|
||
|
||
except Exception as e:
|
||
print(f"❌ 获取元素信息失败: {e}")
|
||
|
||
def setup_hotkeys(self):
|
||
"""设置热键监听"""
|
||
print("⌨️ 设置热键监听...")
|
||
|
||
# 从配置文件获取热键设置
|
||
hotkeys = Config.HOTKEYS
|
||
|
||
# 基础功能热键
|
||
keyboard.add_hotkey(hotkeys['screenshot'], self.take_screenshot)
|
||
print(f" {hotkeys['screenshot'].upper()} - 截图")
|
||
|
||
keyboard.add_hotkey(hotkeys['save_xml'], self.save_page_xml)
|
||
print(f" {hotkeys['save_xml'].upper()} - 保存XML布局")
|
||
|
||
keyboard.add_hotkey(hotkeys['get_activity'], self.get_current_activity)
|
||
print(f" {hotkeys['get_activity'].upper()} - 获取当前Activity")
|
||
|
||
keyboard.add_hotkey(hotkeys['element_info'], self.find_elements_info)
|
||
print(f" {hotkeys['element_info'].upper()} - 显示页面元素信息")
|
||
|
||
# 新增功能热键
|
||
keyboard.add_hotkey(hotkeys['visual_compare'], self.compare_with_design)
|
||
print(f" {hotkeys['visual_compare'].upper()} - 视觉比对")
|
||
|
||
keyboard.add_hotkey(hotkeys['analyze_page'], self.analyze_current_page)
|
||
print(f" {hotkeys['analyze_page'].upper()} - 综合页面分析")
|
||
|
||
keyboard.add_hotkey(hotkeys['monitor_performance'], self.monitor_performance)
|
||
print(f" {hotkeys['monitor_performance'].upper()} - 性能监控")
|
||
|
||
keyboard.add_hotkey(hotkeys['click_element'], self.find_and_click_element)
|
||
print(f" {hotkeys['click_element'].upper()} - 智能元素点击")
|
||
|
||
keyboard.add_hotkey(hotkeys['generate_report'], self.generate_test_report)
|
||
print(f" {hotkeys['generate_report'].upper()} - 生成测试报告")
|
||
|
||
# 退出热键
|
||
keyboard.add_hotkey(hotkeys['quit'], self.stop)
|
||
print(f" {hotkeys['quit'].upper()} - 退出程序")
|
||
|
||
def show_menu(self):
|
||
"""显示操作菜单"""
|
||
hotkeys = Config.HOTKEYS
|
||
supported_formats = Config.get_supported_design_formats()
|
||
|
||
print("\n" + "="*60)
|
||
print("🤖 Appium 交互式UI测试工具")
|
||
print("="*60)
|
||
print("热键操作:")
|
||
print(f" {hotkeys['screenshot'].upper()} - 截图")
|
||
print(f" {hotkeys['save_xml'].upper()} - 保存XML布局")
|
||
print(f" {hotkeys['get_activity'].upper()} - 获取当前Activity")
|
||
print(f" {hotkeys['element_info'].upper()} - 显示页面元素信息")
|
||
print(f" {hotkeys['visual_compare'].upper()} - 视觉比对(仅图像对比)")
|
||
print(f" {hotkeys['analyze_page'].upper()} - XML分析和布局比对")
|
||
print(f" {hotkeys['monitor_performance'].upper()} - 性能监控")
|
||
print(f" {hotkeys['click_element'].upper()} - 智能元素点击")
|
||
print(f" {hotkeys['generate_report'].upper()} - 生成测试报告")
|
||
print(f" {hotkeys['quit'].upper()} - 退出程序")
|
||
print("\n命令行操作:")
|
||
print(" help - 显示帮助")
|
||
print(" status - 显示连接状态")
|
||
print(" reconnect - 重新连接设备")
|
||
print(" compare - 视觉比对(仅图像对比)")
|
||
print(" layout - XML-SVG布局比对")
|
||
print(" analyze - XML分析和布局比对")
|
||
print(" performance - 性能监控")
|
||
print(" click - 智能元素点击")
|
||
print(" report - 生成测试报告")
|
||
print(" quit - 退出程序")
|
||
print("\n💡 使用提示:")
|
||
print(f" • F5/compare: 纯视觉图像比对,检查UI外观")
|
||
print(f" • F6/analyze: XML结构分析 + SVG布局比对")
|
||
print(f" • layout: 专门的XML-SVG布局结构比对")
|
||
print(f" • 将设计图放入 {self.designs_dir} 目录")
|
||
print(f" • 支持的设计图格式: {', '.join(supported_formats)}")
|
||
print(" • 测试报告会自动保存为JSON和HTML格式")
|
||
print("="*60)
|
||
|
||
def handle_command(self, command):
|
||
"""处理命令行输入"""
|
||
command = command.strip().lower()
|
||
|
||
if command == 'help':
|
||
self.show_menu()
|
||
elif command == 'status':
|
||
if self.driver:
|
||
print("✅ 设备已连接")
|
||
self.get_current_activity()
|
||
else:
|
||
print("❌ 设备未连接")
|
||
elif command == 'reconnect':
|
||
self.disconnect_device()
|
||
self.connect_device()
|
||
elif command in ['quit', 'exit', 'q']:
|
||
self.stop()
|
||
elif command == '1' or command == 'screenshot':
|
||
self.take_screenshot()
|
||
elif command == '2' or command == 'xml':
|
||
self.save_page_xml()
|
||
elif command == '3' or command == 'activity':
|
||
self.get_current_activity()
|
||
elif command == '4' or command == 'elements':
|
||
self.find_elements_info()
|
||
elif command == '5' or command == 'compare':
|
||
self.compare_with_design()
|
||
elif command == 'layout':
|
||
self.compare_layout_only()
|
||
elif command == '6' or command == 'analyze':
|
||
self.analyze_current_page()
|
||
elif command == '7' or command == 'performance':
|
||
self.monitor_performance()
|
||
elif command == '8' or command == 'click':
|
||
self.find_and_click_element()
|
||
elif command == '9' or command == 'report':
|
||
self.generate_test_report()
|
||
else:
|
||
print(f"❓ 未知命令: {command}")
|
||
print("输入 'help' 查看可用命令")
|
||
|
||
def command_loop(self):
|
||
"""命令行输入循环"""
|
||
while self.running:
|
||
try:
|
||
command = input("\n> ").strip()
|
||
if command:
|
||
self.handle_command(command)
|
||
except (KeyboardInterrupt, EOFError):
|
||
self.stop()
|
||
break
|
||
except Exception as e:
|
||
print(f"❌ 命令执行错误: {e}")
|
||
|
||
def stop(self):
|
||
"""停止程序"""
|
||
print("\n🛑 正在退出...")
|
||
self.running = False
|
||
self.disconnect_device()
|
||
|
||
def run(self):
|
||
"""运行交互式测试工具"""
|
||
print("🚀 启动Appium交互式UI测试工具")
|
||
|
||
# 连接设备
|
||
if not self.connect_device():
|
||
print("❌ 无法连接到设备,请检查Appium服务器是否启动")
|
||
return
|
||
|
||
# 设置热键
|
||
self.setup_hotkeys()
|
||
|
||
# 显示菜单
|
||
self.show_menu()
|
||
|
||
# 标记为运行状态
|
||
self.running = True
|
||
|
||
# 启动命令行输入线程
|
||
command_thread = threading.Thread(target=self.command_loop, daemon=True)
|
||
command_thread.start()
|
||
|
||
print("\n✅ 工具已启动! 使用热键或输入命令进行操作...")
|
||
print("💡 提示: 输入 'help' 查看所有可用命令")
|
||
|
||
try:
|
||
# 保持主线程运行
|
||
while self.running:
|
||
time.sleep(0.1)
|
||
except KeyboardInterrupt:
|
||
self.stop()
|
||
|
||
print("👋 程序已退出")
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
print("🎯 交互式UI测试工具")
|
||
print("📱 支持Android应用UI测试")
|
||
print("🔧 集成XML分析、视觉比对、性能监控等功能")
|
||
print("-" * 50)
|
||
|
||
tester = InteractiveUITester()
|
||
|
||
try:
|
||
tester.run()
|
||
except Exception as e:
|
||
print(f"❌ 程序运行出错: {e}")
|
||
finally:
|
||
print("🔚 程序已退出")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |