mobile-mcp-ai 2.5.11__tar.gz → 2.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {mobile_mcp_ai-2.5.11/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.6.1}/PKG-INFO +1 -1
  2. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/basic_tools_lite.py +601 -78
  3. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1/mobile_mcp_ai.egg-info}/PKG-INFO +1 -1
  4. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/setup.py +1 -1
  5. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/LICENSE +0 -0
  6. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/MANIFEST.in +0 -0
  7. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/README.md +0 -0
  8. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/__init__.py +0 -0
  9. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/config.py +0 -0
  10. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/__init__.py +0 -0
  11. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/device_manager.py +0 -0
  12. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/dynamic_config.py +0 -0
  13. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/ios_client_wda.py +0 -0
  14. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/ios_device_manager_wda.py +0 -0
  15. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/mobile_client.py +0 -0
  16. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/template_matcher.py +0 -0
  17. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  18. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  19. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  20. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  21. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  22. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  23. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/utils/__init__.py +0 -0
  24. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/utils/logger.py +0 -0
  25. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/utils/operation_history_manager.py +0 -0
  26. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/core/utils/smart_wait.py +0 -0
  27. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/docs/iOS_SETUP_GUIDE.md +0 -0
  28. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mcp_tools/__init__.py +0 -0
  29. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mcp_tools/mcp_server.py +0 -0
  30. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/SOURCES.txt +0 -0
  31. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
  32. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
  33. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
  34. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/requires.txt +0 -0
  35. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
  36. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/requirements.txt +0 -0
  37. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/setup.cfg +0 -0
  38. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_151217.png +0 -0
  39. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_152037.png +0 -0
  40. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_152840.png +0 -0
  41. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_153256.png +0 -0
  42. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_154847.png +0 -0
  43. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/templates/close_buttons/gray_x_stock_ad.png +0 -0
  44. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_mind_cloud_my_space.py +0 -0
  45. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_mind_correct.py +0 -0
  46. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_mind_improved.py +0 -0
  47. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_mind_optimized.py +0 -0
  48. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_open_mind.py +0 -0
  49. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_priority_demo.py +0 -0
  50. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_simple.py +0 -0
  51. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_/344/270/276/346/212/245.py" +0 -0
  52. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py" +0 -0
  53. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/tests/test_/346/265/213/350/257/225.py" +0 -0
  54. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/utils/__init__.py +0 -0
  55. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/utils/logger.py +0 -0
  56. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/utils/xml_formatter.py +0 -0
  57. {mobile_mcp_ai-2.5.11 → mobile_mcp_ai-2.6.1}/utils/xml_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.5.11
3
+ Version: 2.6.1
4
4
  Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
5
5
  Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
6
6
  Author: douzi
@@ -31,6 +31,9 @@ class BasicMobileToolsLite:
31
31
 
32
32
  # 操作历史(用于生成 pytest 脚本)
33
33
  self.operation_history: List[Dict] = []
34
+
35
+ # 目标应用包名(用于监测应用跳转)
36
+ self.target_package: Optional[str] = None
34
37
 
35
38
  def _is_ios(self) -> bool:
36
39
  """判断当前是否为 iOS 平台"""
@@ -53,34 +56,157 @@ class BasicMobileToolsLite:
53
56
  }
54
57
  self.operation_history.append(record)
55
58
 
56
- def _get_full_hierarchy(self) -> str:
57
- """获取完整的 UI 层级 XML(包含 NAF 元素)
59
+ def _get_current_package(self) -> Optional[str]:
60
+ """获取当前前台应用的包名/Bundle ID"""
61
+ try:
62
+ if self._is_ios():
63
+ ios_client = self._get_ios_client()
64
+ if ios_client and hasattr(ios_client, 'wda'):
65
+ app_info = ios_client.wda.session().app_current()
66
+ return app_info.get('bundleId')
67
+ else:
68
+ info = self.client.u2.app_current()
69
+ return info.get('package')
70
+ except Exception:
71
+ return None
72
+
73
+ def _check_app_switched(self) -> Dict:
74
+ """检查是否已跳出目标应用
58
75
 
59
- 优先使用 ADB 直接 dump,比 uiautomator2.dump_hierarchy 更完整
76
+ Returns:
77
+ {
78
+ 'switched': bool, # 是否跳转
79
+ 'current_package': str, # 当前应用包名
80
+ 'target_package': str, # 目标应用包名
81
+ 'message': str # 提示信息
82
+ }
60
83
  """
61
- import sys
84
+ if not self.target_package:
85
+ return {
86
+ 'switched': False,
87
+ 'current_package': None,
88
+ 'target_package': None,
89
+ 'message': '⚠️ 未设置目标应用,无法监测应用跳转'
90
+ }
62
91
 
63
- if self._is_ios():
64
- # iOS 使用 page_source
65
- ios_client = self._get_ios_client()
66
- if ios_client and hasattr(ios_client, 'wda'):
67
- return ios_client.wda.source()
68
- return ""
92
+ current = self._get_current_package()
93
+ if not current:
94
+ return {
95
+ 'switched': False,
96
+ 'current_package': None,
97
+ 'target_package': self.target_package,
98
+ 'message': '⚠️ 无法获取当前应用包名'
99
+ }
100
+
101
+ if current != self.target_package:
102
+ return {
103
+ 'switched': True,
104
+ 'current_package': current,
105
+ 'target_package': self.target_package,
106
+ 'message': f'⚠️ 应用已跳转!当前应用: {current},目标应用: {self.target_package}'
107
+ }
108
+
109
+ return {
110
+ 'switched': False,
111
+ 'current_package': current,
112
+ 'target_package': self.target_package,
113
+ 'message': f'✅ 仍在目标应用: {current}'
114
+ }
115
+
116
+ def _return_to_target_app(self) -> Dict:
117
+ """返回到目标应用
118
+
119
+ 策略:
120
+ 1. 先按返回键(可能关闭弹窗或返回上一页)
121
+ 2. 如果还在其他应用,启动目标应用
122
+ 3. 验证是否成功返回
123
+
124
+ Returns:
125
+ {
126
+ 'success': bool,
127
+ 'message': str,
128
+ 'method': str # 使用的返回方法
129
+ }
130
+ """
131
+ if not self.target_package:
132
+ return {
133
+ 'success': False,
134
+ 'message': '❌ 未设置目标应用,无法返回',
135
+ 'method': None
136
+ }
69
137
 
70
- # Android: 优先使用 ADB 直接 dump
71
138
  try:
72
- # 方法1: ADB dump(获取最完整的 UI 树,包括 NAF 元素)
73
- self.client.u2.shell('uiautomator dump /sdcard/ui_dump.xml')
74
- result = self.client.u2.shell('cat /sdcard/ui_dump.xml')
75
- if result and isinstance(result, str) and result.strip().startswith('<?xml'):
76
- xml_string = result.strip()
77
- self.client.u2.shell('rm /sdcard/ui_dump.xml')
78
- return xml_string
139
+ # 先检查当前应用
140
+ current = self._get_current_package()
141
+ if not current:
142
+ return {
143
+ 'success': False,
144
+ 'message': '❌ 无法获取当前应用包名',
145
+ 'method': None
146
+ }
147
+
148
+ # 如果已经在目标应用,不需要返回
149
+ if current == self.target_package:
150
+ return {
151
+ 'success': True,
152
+ 'message': f'✅ 已在目标应用: {self.target_package}',
153
+ 'method': 'already_in_target'
154
+ }
155
+
156
+ # 策略1: 先按返回键(可能关闭弹窗或返回)
157
+ if self._is_ios():
158
+ ios_client = self._get_ios_client()
159
+ if ios_client and hasattr(ios_client, 'wda'):
160
+ # iOS 返回键
161
+ ios_client.wda.press('home') # iOS 先按 home
162
+ time.sleep(0.5)
163
+ # 然后启动目标应用
164
+ ios_client.wda.app_activate(self.target_package)
165
+ else:
166
+ return {
167
+ 'success': False,
168
+ 'message': '❌ iOS 客户端未初始化',
169
+ 'method': None
170
+ }
171
+ else:
172
+ # Android: 先按返回键
173
+ self.client.u2.press('back')
174
+ time.sleep(0.5)
175
+
176
+ # 检查是否已返回
177
+ current = self._get_current_package()
178
+ if current == self.target_package:
179
+ return {
180
+ 'success': True,
181
+ 'message': f'✅ 已返回目标应用: {self.target_package}(通过返回键)',
182
+ 'method': 'back_key'
183
+ }
184
+
185
+ # 如果还在其他应用,启动目标应用
186
+ self.client.u2.app_start(self.target_package)
187
+ time.sleep(1)
188
+
189
+ # 验证是否成功返回
190
+ current = self._get_current_package()
191
+ if current == self.target_package:
192
+ return {
193
+ 'success': True,
194
+ 'message': f'✅ 已返回目标应用: {self.target_package}',
195
+ 'method': 'app_start'
196
+ }
197
+ else:
198
+ return {
199
+ 'success': False,
200
+ 'message': f'❌ 返回失败:当前应用仍为 {current},期望 {self.target_package}',
201
+ 'method': 'app_start'
202
+ }
79
203
  except Exception as e:
80
- print(f" ⚠️ ADB dump 失败: {e}", file=sys.stderr)
81
-
82
- # 方法2: 回退到 uiautomator2
83
- return self.client.u2.dump_hierarchy(compressed=False)
204
+ return {
205
+ 'success': False,
206
+ 'message': f'❌ 返回目标应用失败: {e}',
207
+ 'method': None
208
+ }
209
+
84
210
 
85
211
  # ==================== 截图 ====================
86
212
 
@@ -381,7 +507,7 @@ class BasicMobileToolsLite:
381
507
  if show_popup_hints and not self._is_ios():
382
508
  try:
383
509
  import xml.etree.ElementTree as ET
384
- xml_string = self._get_full_hierarchy()
510
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
385
511
  root = ET.fromstring(xml_string)
386
512
 
387
513
  # 检测弹窗区域
@@ -558,7 +684,7 @@ class BasicMobileToolsLite:
558
684
  else:
559
685
  try:
560
686
  import xml.etree.ElementTree as ET
561
- xml_string = self._get_full_hierarchy()
687
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
562
688
  root = ET.fromstring(xml_string)
563
689
 
564
690
  for elem in root.iter():
@@ -963,25 +1089,41 @@ class BasicMobileToolsLite:
963
1089
  ref=f"coords_{x}_{y}"
964
1090
  )
965
1091
 
1092
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1093
+ app_check = self._check_app_switched()
1094
+ return_result = None
1095
+
1096
+ if app_check['switched']:
1097
+ # 应用已跳转,尝试返回目标应用
1098
+ return_result = self._return_to_target_app()
1099
+
1100
+ # 构建返回消息
966
1101
  if converted:
967
1102
  if conversion_type == "crop_offset":
968
- return {
969
- "success": True,
970
- "message": f"✅ 点击成功: ({x}, {y})\n"
971
- f" 🔍 局部截图坐标转换: ({original_x},{original_y}) + 偏移({crop_offset_x},{crop_offset_y}) → ({x},{y})"
972
- }
1103
+ msg = f"✅ 点击成功: ({x}, {y})\n" \
1104
+ f" 🔍 局部截图坐标转换: ({original_x},{original_y}) + 偏移({crop_offset_x},{crop_offset_y}) → ({x},{y})"
973
1105
  else:
974
- return {
975
- "success": True,
976
- "message": f" 点击成功: ({x}, {y})\n"
977
- f" 📐 坐标已转换: ({original_x},{original_y}) → ({x},{y})\n"
978
- f" 🖼️ 图片尺寸: {image_width}x{image_height} → 屏幕: {screen_width}x{screen_height}"
979
- }
1106
+ msg = f"✅ 点击成功: ({x}, {y})\n" \
1107
+ f" 📐 坐标已转换: ({original_x},{original_y}) → ({x},{y})\n" \
1108
+ f" 🖼️ 图片尺寸: {image_width}x{image_height} → 屏幕: {screen_width}x{screen_height}"
980
1109
  else:
981
- return {
982
- "success": True,
983
- "message": f"✅ 点击成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%]"
984
- }
1110
+ msg = f"✅ 点击成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%]"
1111
+
1112
+ # 如果检测到应用跳转,添加警告和返回结果
1113
+ if app_check['switched']:
1114
+ msg += f"\n{app_check['message']}"
1115
+ if return_result:
1116
+ if return_result['success']:
1117
+ msg += f"\n{return_result['message']}"
1118
+ else:
1119
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1120
+
1121
+ return {
1122
+ "success": True,
1123
+ "message": msg,
1124
+ "app_check": app_check,
1125
+ "return_to_app": return_result
1126
+ }
985
1127
  except Exception as e:
986
1128
  return {"success": False, "message": f"❌ 点击失败: {e}"}
987
1129
 
@@ -1114,9 +1256,9 @@ class BasicMobileToolsLite:
1114
1256
  return {"success": False, "message": f"❌ 点击失败: {e}"}
1115
1257
 
1116
1258
  def _find_element_in_tree(self, text: str) -> Optional[Dict]:
1117
- """在 XML 树中查找包含指定文本的元素(使用完整 UI 层级)"""
1259
+ """在 XML 树中查找包含指定文本的元素"""
1118
1260
  try:
1119
- xml = self._get_full_hierarchy()
1261
+ xml = self.client.u2.dump_hierarchy(compressed=False)
1120
1262
  import xml.etree.ElementTree as ET
1121
1263
  root = ET.fromstring(xml)
1122
1264
 
@@ -1510,7 +1652,28 @@ class BasicMobileToolsLite:
1510
1652
  elem.set_text(text)
1511
1653
  time.sleep(0.3)
1512
1654
  self._record_operation('input', element=resource_id, ref=resource_id, text=text)
1513
- return {"success": True, "message": f"✅ 输入成功: '{text}'"}
1655
+
1656
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1657
+ app_check = self._check_app_switched()
1658
+ return_result = None
1659
+ if app_check['switched']:
1660
+ return_result = self._return_to_target_app()
1661
+
1662
+ msg = f"✅ 输入成功: '{text}'"
1663
+ if app_check['switched']:
1664
+ msg += f"\n{app_check['message']}"
1665
+ if return_result:
1666
+ if return_result['success']:
1667
+ msg += f"\n{return_result['message']}"
1668
+ else:
1669
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1670
+
1671
+ return {
1672
+ "success": True,
1673
+ "message": msg,
1674
+ "app_check": app_check,
1675
+ "return_to_app": return_result
1676
+ }
1514
1677
  return {"success": False, "message": f"❌ 输入框不存在: {resource_id}"}
1515
1678
  else:
1516
1679
  elements = self.client.u2(resourceId=resource_id)
@@ -1524,7 +1687,28 @@ class BasicMobileToolsLite:
1524
1687
  elements.set_text(text)
1525
1688
  time.sleep(0.3)
1526
1689
  self._record_operation('input', element=resource_id, ref=resource_id, text=text)
1527
- return {"success": True, "message": f"✅ 输入成功: '{text}'"}
1690
+
1691
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1692
+ app_check = self._check_app_switched()
1693
+ return_result = None
1694
+ if app_check['switched']:
1695
+ return_result = self._return_to_target_app()
1696
+
1697
+ msg = f"✅ 输入成功: '{text}'"
1698
+ if app_check['switched']:
1699
+ msg += f"\n{app_check['message']}"
1700
+ if return_result:
1701
+ if return_result['success']:
1702
+ msg += f"\n{return_result['message']}"
1703
+ else:
1704
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1705
+
1706
+ return {
1707
+ "success": True,
1708
+ "message": msg,
1709
+ "app_check": app_check,
1710
+ "return_to_app": return_result
1711
+ }
1528
1712
 
1529
1713
  # 多个相同 ID(<=5个),尝试智能选择
1530
1714
  if count <= 5:
@@ -1537,14 +1721,56 @@ class BasicMobileToolsLite:
1537
1721
  elem.set_text(text)
1538
1722
  time.sleep(0.3)
1539
1723
  self._record_operation('input', element=resource_id, ref=resource_id, text=text)
1540
- return {"success": True, "message": f"✅ 输入成功: '{text}'"}
1724
+
1725
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1726
+ app_check = self._check_app_switched()
1727
+ return_result = None
1728
+ if app_check['switched']:
1729
+ return_result = self._return_to_target_app()
1730
+
1731
+ msg = f"✅ 输入成功: '{text}'"
1732
+ if app_check['switched']:
1733
+ msg += f"\n{app_check['message']}"
1734
+ if return_result:
1735
+ if return_result['success']:
1736
+ msg += f"\n{return_result['message']}"
1737
+ else:
1738
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1739
+
1740
+ return {
1741
+ "success": True,
1742
+ "message": msg,
1743
+ "app_check": app_check,
1744
+ "return_to_app": return_result
1745
+ }
1541
1746
  except:
1542
1747
  continue
1543
1748
  # 没找到可编辑的,用第一个
1544
1749
  elements[0].set_text(text)
1545
1750
  time.sleep(0.3)
1546
1751
  self._record_operation('input', element=resource_id, ref=resource_id, text=text)
1547
- return {"success": True, "message": f"✅ 输入成功: '{text}'"}
1752
+
1753
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1754
+ app_check = self._check_app_switched()
1755
+ return_result = None
1756
+ if app_check['switched']:
1757
+ return_result = self._return_to_target_app()
1758
+
1759
+ msg = f"✅ 输入成功: '{text}'"
1760
+ if app_check['switched']:
1761
+ msg += f"\n{app_check['message']}"
1762
+ if return_result:
1763
+ if return_result['success']:
1764
+ msg += f"\n{return_result['message']}"
1765
+ else:
1766
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1767
+
1768
+ return {
1769
+ "success": True,
1770
+ "message": msg,
1771
+ "app_check": app_check,
1772
+ "return_to_app": return_result
1773
+ }
1548
1774
 
1549
1775
  # ID 不可靠(不存在或太多),改用 EditText 类型定位
1550
1776
  edit_texts = self.client.u2(className='android.widget.EditText')
@@ -1554,7 +1780,28 @@ class BasicMobileToolsLite:
1554
1780
  edit_texts.set_text(text)
1555
1781
  time.sleep(0.3)
1556
1782
  self._record_operation('input', element='EditText', ref='EditText', text=text)
1557
- return {"success": True, "message": f"✅ 输入成功: '{text}' (通过 EditText 定位)"}
1783
+
1784
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1785
+ app_check = self._check_app_switched()
1786
+ return_result = None
1787
+ if app_check['switched']:
1788
+ return_result = self._return_to_target_app()
1789
+
1790
+ msg = f"✅ 输入成功: '{text}' (通过 EditText 定位)"
1791
+ if app_check['switched']:
1792
+ msg += f"\n{app_check['message']}"
1793
+ if return_result:
1794
+ if return_result['success']:
1795
+ msg += f"\n{return_result['message']}"
1796
+ else:
1797
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1798
+
1799
+ return {
1800
+ "success": True,
1801
+ "message": msg,
1802
+ "app_check": app_check,
1803
+ "return_to_app": return_result
1804
+ }
1558
1805
 
1559
1806
  # 多个 EditText,选择最靠上的
1560
1807
  best_elem = None
@@ -1573,7 +1820,28 @@ class BasicMobileToolsLite:
1573
1820
  best_elem.set_text(text)
1574
1821
  time.sleep(0.3)
1575
1822
  self._record_operation('input', element='EditText', ref='EditText', text=text)
1576
- return {"success": True, "message": f"✅ 输入成功: '{text}' (通过 EditText 定位,选择最顶部的)"}
1823
+
1824
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1825
+ app_check = self._check_app_switched()
1826
+ return_result = None
1827
+ if app_check['switched']:
1828
+ return_result = self._return_to_target_app()
1829
+
1830
+ msg = f"✅ 输入成功: '{text}' (通过 EditText 定位,选择最顶部的)"
1831
+ if app_check['switched']:
1832
+ msg += f"\n{app_check['message']}"
1833
+ if return_result:
1834
+ if return_result['success']:
1835
+ msg += f"\n{return_result['message']}"
1836
+ else:
1837
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1838
+
1839
+ return {
1840
+ "success": True,
1841
+ "message": msg,
1842
+ "app_check": app_check,
1843
+ "return_to_app": return_result
1844
+ }
1577
1845
 
1578
1846
  return {"success": False, "message": f"❌ 输入框不存在: {resource_id}"}
1579
1847
 
@@ -1625,7 +1893,29 @@ class BasicMobileToolsLite:
1625
1893
  text=text
1626
1894
  )
1627
1895
 
1628
- return {"success": True, "message": f"✅ 输入成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%] -> '{text}'"}
1896
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1897
+ app_check = self._check_app_switched()
1898
+ return_result = None
1899
+
1900
+ if app_check['switched']:
1901
+ # 应用已跳转,尝试返回目标应用
1902
+ return_result = self._return_to_target_app()
1903
+
1904
+ msg = f"✅ 输入成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%] -> '{text}'"
1905
+ if app_check['switched']:
1906
+ msg += f"\n{app_check['message']}"
1907
+ if return_result:
1908
+ if return_result['success']:
1909
+ msg += f"\n{return_result['message']}"
1910
+ else:
1911
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
1912
+
1913
+ return {
1914
+ "success": True,
1915
+ "message": msg,
1916
+ "app_check": app_check,
1917
+ "return_to_app": return_result
1918
+ }
1629
1919
  except Exception as e:
1630
1920
  return {"success": False, "message": f"❌ 输入失败: {e}"}
1631
1921
 
@@ -1692,6 +1982,14 @@ class BasicMobileToolsLite:
1692
1982
  record_info['y_percent'] = y_percent
1693
1983
  self._record_operation('swipe', **record_info)
1694
1984
 
1985
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转则自动返回目标应用
1986
+ app_check = self._check_app_switched()
1987
+ return_result = None
1988
+
1989
+ if app_check['switched']:
1990
+ # 应用已跳转,尝试返回目标应用
1991
+ return_result = self._return_to_target_app()
1992
+
1695
1993
  # 构建返回消息
1696
1994
  msg = f"✅ 滑动成功: {direction}"
1697
1995
  if direction in ['left', 'right']:
@@ -1700,7 +1998,21 @@ class BasicMobileToolsLite:
1700
1998
  elif y is not None:
1701
1999
  msg += f" (高度: {y}px)"
1702
2000
 
1703
- return {"success": True, "message": msg}
2001
+ # 如果检测到应用跳转,添加警告和返回结果
2002
+ if app_check['switched']:
2003
+ msg += f"\n{app_check['message']}"
2004
+ if return_result:
2005
+ if return_result['success']:
2006
+ msg += f"\n{return_result['message']}"
2007
+ else:
2008
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
2009
+
2010
+ return {
2011
+ "success": True,
2012
+ "message": msg,
2013
+ "app_check": app_check,
2014
+ "return_to_app": return_result
2015
+ }
1704
2016
  except Exception as e:
1705
2017
  return {"success": False, "message": f"❌ 滑动失败: {e}"}
1706
2018
 
@@ -1756,11 +2068,22 @@ class BasicMobileToolsLite:
1756
2068
 
1757
2069
  await asyncio.sleep(2)
1758
2070
 
2071
+ # 记录目标应用包名(用于后续监测应用跳转)
2072
+ self.target_package = package_name
2073
+
2074
+ # 验证是否成功启动到目标应用
2075
+ current = self._get_current_package()
2076
+ if current and current != package_name:
2077
+ return {
2078
+ "success": False,
2079
+ "message": f"❌ 启动失败:当前应用为 {current},期望 {package_name}"
2080
+ }
2081
+
1759
2082
  self._record_operation('launch_app', package_name=package_name)
1760
2083
 
1761
2084
  return {
1762
2085
  "success": True,
1763
- "message": f"✅ 已启动: {package_name}\n💡 建议等待 2-3 秒让页面加载"
2086
+ "message": f"✅ 已启动: {package_name}\n💡 建议等待 2-3 秒让页面加载\n📱 已设置应用状态监测"
1764
2087
  }
1765
2088
  except Exception as e:
1766
2089
  return {"success": False, "message": f"❌ 启动失败: {e}"}
@@ -1850,7 +2173,7 @@ class BasicMobileToolsLite:
1850
2173
  # ==================== 辅助工具 ====================
1851
2174
 
1852
2175
  def list_elements(self) -> List[Dict]:
1853
- """列出页面元素"""
2176
+ """列出页面元素(已优化:过滤排版容器,保留功能控件)"""
1854
2177
  try:
1855
2178
  if self._is_ios():
1856
2179
  ios_client = self._get_ios_client()
@@ -1858,23 +2181,143 @@ class BasicMobileToolsLite:
1858
2181
  return ios_client.list_elements()
1859
2182
  return [{"error": "iOS 暂不支持元素列表,建议使用截图"}]
1860
2183
  else:
1861
- xml_string = self._get_full_hierarchy()
2184
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
1862
2185
  elements = self.client.xml_parser.parse(xml_string)
1863
2186
 
2187
+ # 功能控件类型(需要保留)
2188
+ FUNCTIONAL_WIDGETS = {
2189
+ 'TextView', 'Text', 'Label', # 文本类
2190
+ 'ImageView', 'Image', 'ImageButton', # 图片类
2191
+ 'Button', 'CheckBox', 'RadioButton', 'Switch', # 交互类
2192
+ 'SeekBar', 'ProgressBar', 'RatingBar', # 滑动/进度类
2193
+ 'EditText', 'TextInput', # 输入类
2194
+ 'VideoView', 'WebView', # 特殊功能类
2195
+ 'RecyclerView', 'ListView', 'GridView', # 列表类
2196
+ 'ScrollView', 'NestedScrollView', # 滚动容器(有实际功能)
2197
+ }
2198
+
2199
+ # 容器控件类型(需要过滤,除非有业务ID)
2200
+ CONTAINER_WIDGETS = {
2201
+ 'FrameLayout', 'LinearLayout', 'RelativeLayout',
2202
+ 'ViewGroup', 'ConstraintLayout', 'CoordinatorLayout',
2203
+ 'CardView', 'View', # 基础View也可能只是容器
2204
+ }
2205
+
2206
+ # 装饰类控件关键词(resource_id中包含这些关键词的通常可以过滤)
2207
+ # 支持匹配如 qylt_item_short_video_shadow_one 这样的命名
2208
+ DECORATIVE_KEYWORDS = {
2209
+ 'shadow', 'divider', 'separator', 'line', 'border',
2210
+ 'background', 'bg_', '_bg', 'decorative', 'decoration',
2211
+ '_shadow', 'shadow_', '_divider', 'divider_', '_line', 'line_'
2212
+ }
2213
+
1864
2214
  result = []
1865
2215
  for elem in elements:
1866
- if elem.get('clickable') or elem.get('focusable'):
2216
+ # 获取元素属性
2217
+ class_name = elem.get('class_name', '')
2218
+ resource_id = elem.get('resource_id', '').strip()
2219
+ text = elem.get('text', '').strip()
2220
+ content_desc = elem.get('content_desc', '').strip()
2221
+ bounds = elem.get('bounds', '')
2222
+ clickable = elem.get('clickable', False)
2223
+ focusable = elem.get('focusable', False)
2224
+ scrollable = elem.get('scrollable', False)
2225
+ enabled = elem.get('enabled', True)
2226
+
2227
+ # 1. 过滤 bounds="[0,0][0,0]" 的视觉隐藏元素
2228
+ if bounds == '[0,0][0,0]':
2229
+ continue
2230
+
2231
+ # 2. 检查是否是功能控件(直接保留)
2232
+ if class_name in FUNCTIONAL_WIDGETS:
2233
+ result.append({
2234
+ 'resource_id': resource_id,
2235
+ 'text': text,
2236
+ 'content_desc': content_desc,
2237
+ 'bounds': bounds,
2238
+ 'clickable': clickable,
2239
+ 'class': class_name
2240
+ })
2241
+ continue
2242
+
2243
+ # 3. 检查是否是容器控件
2244
+ if class_name in CONTAINER_WIDGETS:
2245
+ # 容器控件需要检查是否有业务相关的ID
2246
+ has_business_id = self._has_business_id(resource_id)
2247
+ if not has_business_id:
2248
+ # 无业务ID的容器控件,检查是否有其他有意义属性
2249
+ if not (clickable or focusable or scrollable or text or content_desc):
2250
+ # 所有属性都是默认值,过滤掉
2251
+ continue
2252
+ # 有业务ID或其他有意义属性,保留
1867
2253
  result.append({
1868
- 'resource_id': elem.get('resource_id', ''),
1869
- 'text': elem.get('text', ''),
1870
- 'content_desc': elem.get('content_desc', ''),
1871
- 'bounds': elem.get('bounds', ''),
1872
- 'clickable': elem.get('clickable', False)
2254
+ 'resource_id': resource_id,
2255
+ 'text': text,
2256
+ 'content_desc': content_desc,
2257
+ 'bounds': bounds,
2258
+ 'clickable': clickable,
2259
+ 'class': class_name
1873
2260
  })
2261
+ continue
2262
+
2263
+ # 4. 检查是否是装饰类控件
2264
+ if resource_id:
2265
+ resource_id_lower = resource_id.lower()
2266
+ if any(keyword in resource_id_lower for keyword in DECORATIVE_KEYWORDS):
2267
+ # 是装饰类控件,且没有交互属性,过滤掉
2268
+ if not (clickable or focusable or text or content_desc):
2269
+ continue
2270
+
2271
+ # 5. 检查是否所有属性均为默认值
2272
+ if not (text or content_desc or resource_id or clickable or focusable or scrollable):
2273
+ # 所有属性都是默认值,过滤掉
2274
+ continue
2275
+
2276
+ # 6. 其他情况:有意义的元素保留
2277
+ result.append({
2278
+ 'resource_id': resource_id,
2279
+ 'text': text,
2280
+ 'content_desc': content_desc,
2281
+ 'bounds': bounds,
2282
+ 'clickable': clickable,
2283
+ 'class': class_name
2284
+ })
2285
+
1874
2286
  return result
1875
2287
  except Exception as e:
1876
2288
  return [{"error": f"获取元素失败: {e}"}]
1877
2289
 
2290
+ def _has_business_id(self, resource_id: str) -> bool:
2291
+ """
2292
+ 判断resource_id是否是业务相关的ID
2293
+
2294
+ 业务相关的ID通常包含:
2295
+ - 有意义的命名(不是自动生成的)
2296
+ - 不包含常见的自动生成模式
2297
+ """
2298
+ if not resource_id:
2299
+ return False
2300
+
2301
+ # 自动生成的ID模式(通常可以忽略)
2302
+ auto_generated_patterns = [
2303
+ r'^android:id/', # 系统ID
2304
+ r':id/\d+', # 数字ID
2305
+ r':id/view_\d+', # view_数字
2306
+ r':id/item_\d+', # item_数字
2307
+ ]
2308
+
2309
+ for pattern in auto_generated_patterns:
2310
+ if re.search(pattern, resource_id):
2311
+ return False
2312
+
2313
+ # 如果resource_id有实际内容且不是自动生成的,认为是业务ID
2314
+ # 排除一些常见的系统ID
2315
+ system_ids = ['android:id/content', 'android:id/statusBarBackground']
2316
+ if resource_id in system_ids:
2317
+ return False
2318
+
2319
+ return True
2320
+
1878
2321
  def find_close_button(self) -> Dict:
1879
2322
  """智能查找关闭按钮(不点击,只返回位置)
1880
2323
 
@@ -1894,8 +2337,8 @@ class BasicMobileToolsLite:
1894
2337
  screen_width = self.client.u2.info.get('displayWidth', 720)
1895
2338
  screen_height = self.client.u2.info.get('displayHeight', 1280)
1896
2339
 
1897
- # 获取元素列表(使用完整 UI 层级)
1898
- xml_string = self._get_full_hierarchy()
2340
+ # 获取元素列表
2341
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
1899
2342
  import xml.etree.ElementTree as ET
1900
2343
  root = ET.fromstring(xml_string)
1901
2344
 
@@ -2075,8 +2518,8 @@ class BasicMobileToolsLite:
2075
2518
  screen_width = self.client.u2.info.get('displayWidth', 720)
2076
2519
  screen_height = self.client.u2.info.get('displayHeight', 1280)
2077
2520
 
2078
- # 获取原始 XML(使用完整 UI 层级)
2079
- xml_string = self._get_full_hierarchy()
2521
+ # 获取原始 XML
2522
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
2080
2523
 
2081
2524
  # 关闭按钮的文本特征
2082
2525
  close_texts = ['×', 'X', 'x', '关闭', '取消', 'close', 'Close', 'CLOSE', '跳过', '知道了']
@@ -2305,13 +2748,33 @@ class BasicMobileToolsLite:
2305
2748
  self.client.u2.click(try_x, try_y)
2306
2749
  time.sleep(0.3)
2307
2750
 
2751
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转说明弹窗去除失败,需要返回目标应用
2752
+ app_check = self._check_app_switched()
2753
+ return_result = None
2754
+
2755
+ if app_check['switched']:
2756
+ # 应用已跳转,说明弹窗去除失败,尝试返回目标应用
2757
+ return_result = self._return_to_target_app()
2758
+
2308
2759
  # 尝试后截图,让 AI 判断是否成功
2309
2760
  screenshot_result = self.take_screenshot("尝试关闭后")
2761
+
2762
+ msg = f"✅ 已尝试点击常见关闭按钮位置"
2763
+ if app_check['switched']:
2764
+ msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
2765
+ if return_result:
2766
+ if return_result['success']:
2767
+ msg += f"\n{return_result['message']}"
2768
+ else:
2769
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
2770
+
2310
2771
  return {
2311
2772
  "success": True,
2312
- "message": f"✅ 已尝试点击常见关闭按钮位置",
2773
+ "message": msg,
2313
2774
  "tried_positions": [p[2] for p in try_positions],
2314
2775
  "screenshot": screenshot_result.get("screenshot_path", ""),
2776
+ "app_check": app_check,
2777
+ "return_to_app": return_result,
2315
2778
  "tip": "请查看截图确认弹窗是否已关闭。如果还在,可手动分析截图找到关闭按钮位置。"
2316
2779
  }
2317
2780
 
@@ -2344,6 +2807,14 @@ class BasicMobileToolsLite:
2344
2807
  self.client.u2.click(best['center_x'], best['center_y'])
2345
2808
  time.sleep(0.5)
2346
2809
 
2810
+ # 🎯 关键步骤:检查应用是否跳转,如果跳转说明弹窗去除失败,需要返回目标应用
2811
+ app_check = self._check_app_switched()
2812
+ return_result = None
2813
+
2814
+ if app_check['switched']:
2815
+ # 应用已跳转,说明弹窗去除失败,尝试返回目标应用
2816
+ return_result = self._return_to_target_app()
2817
+
2347
2818
  # 点击后截图,让 AI 判断是否成功
2348
2819
  screenshot_result = self.take_screenshot("关闭弹窗后")
2349
2820
 
@@ -2359,11 +2830,21 @@ class BasicMobileToolsLite:
2359
2830
  ref=f"close_popup_{best['position']}"
2360
2831
  )
2361
2832
 
2833
+ # 构建返回消息
2834
+ msg = f"✅ 已点击关闭按钮 ({best['position']}): ({best['center_x']}, {best['center_y']})"
2835
+ if app_check['switched']:
2836
+ msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
2837
+ if return_result:
2838
+ if return_result['success']:
2839
+ msg += f"\n{return_result['message']}"
2840
+ else:
2841
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
2842
+
2362
2843
  # 返回候选按钮列表,让 AI 看截图判断
2363
2844
  # 如果弹窗还在,AI 可以选择点击其他候选按钮
2364
2845
  return {
2365
2846
  "success": True,
2366
- "message": f"✅ 已点击关闭按钮 ({best['position']}): ({best['center_x']}, {best['center_y']})",
2847
+ "message": msg,
2367
2848
  "clicked": {
2368
2849
  "position": best['position'],
2369
2850
  "match_type": best['match_type'],
@@ -2373,6 +2854,8 @@ class BasicMobileToolsLite:
2373
2854
  "screenshot": screenshot_result.get("screenshot_path", ""),
2374
2855
  "popup_detected": popup_bounds is not None,
2375
2856
  "popup_bounds": f"[{popup_bounds[0]},{popup_bounds[1]}][{popup_bounds[2]},{popup_bounds[3]}]" if popup_bounds else None,
2857
+ "app_check": app_check,
2858
+ "return_to_app": return_result,
2376
2859
  "other_candidates": [
2377
2860
  {
2378
2861
  "position": c['position'],
@@ -2382,7 +2865,7 @@ class BasicMobileToolsLite:
2382
2865
  }
2383
2866
  for c in close_candidates[1:4] # 返回其他3个候选,AI 可以选择
2384
2867
  ],
2385
- "tip": "请查看截图判断弹窗是否已关闭。如果弹窗还在,可以尝试点击 other_candidates 中的其他位置;如果误点跳转了,请按返回键"
2868
+ "tip": "请查看截图判断弹窗是否已关闭。如果弹窗还在,可以尝试点击 other_candidates 中的其他位置"
2386
2869
  }
2387
2870
 
2388
2871
  except Exception as e:
@@ -2947,8 +3430,8 @@ class BasicMobileToolsLite:
2947
3430
  try:
2948
3431
  import xml.etree.ElementTree as ET
2949
3432
 
2950
- # ========== 第1步:控件树查找关闭按钮(使用完整 UI 层级)==========
2951
- xml_string = self._get_full_hierarchy()
3433
+ # ========== 第1步:控件树查找关闭按钮 ==========
3434
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
2952
3435
  root = ET.fromstring(xml_string)
2953
3436
 
2954
3437
  # 关闭按钮的常见特征
@@ -3045,15 +3528,35 @@ class BasicMobileToolsLite:
3045
3528
  pre_result = self.take_screenshot(description="关闭前", compress=False)
3046
3529
  pre_screenshot = pre_result.get("screenshot_path")
3047
3530
 
3048
- # 点击
3049
- self.click_at_coords(cx, cy)
3531
+ # 点击(click_at_coords 内部已包含应用状态检查和自动返回)
3532
+ click_result = self.click_at_coords(cx, cy)
3050
3533
  time.sleep(0.5)
3051
3534
 
3535
+ # 🎯 再次检查应用状态(确保弹窗去除没有导致应用跳转)
3536
+ app_check = self._check_app_switched()
3537
+ return_result = None
3538
+
3539
+ if app_check['switched']:
3540
+ # 应用已跳转,说明弹窗去除失败,尝试返回目标应用
3541
+ return_result = self._return_to_target_app()
3542
+
3052
3543
  result["success"] = True
3053
3544
  result["method"] = "控件树"
3054
- result["message"] = f"✅ 通过控件树找到关闭按钮并点击\n" \
3055
- f" 位置: ({cx}, {cy})\n" \
3056
- f" 原因: {best['reason']}"
3545
+ msg = f"✅ 通过控件树找到关闭按钮并点击\n" \
3546
+ f" 位置: ({cx}, {cy})\n" \
3547
+ f" 原因: {best['reason']}"
3548
+
3549
+ if app_check['switched']:
3550
+ msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
3551
+ if return_result:
3552
+ if return_result['success']:
3553
+ msg += f"\n{return_result['message']}"
3554
+ else:
3555
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
3556
+
3557
+ result["message"] = msg
3558
+ result["app_check"] = app_check
3559
+ result["return_to_app"] = return_result
3057
3560
 
3058
3561
  # 自动学习:检查这个 X 是否已在模板库,不在就添加
3059
3562
  if auto_learn and pre_screenshot:
@@ -3083,16 +3586,36 @@ class BasicMobileToolsLite:
3083
3586
  x_pct = best["percent"]["x"]
3084
3587
  y_pct = best["percent"]["y"]
3085
3588
 
3086
- # 点击
3087
- self.click_by_percent(x_pct, y_pct)
3589
+ # 点击(click_by_percent 内部已包含应用状态检查和自动返回)
3590
+ click_result = self.click_by_percent(x_pct, y_pct)
3088
3591
  time.sleep(0.5)
3089
3592
 
3593
+ # 🎯 再次检查应用状态(确保弹窗去除没有导致应用跳转)
3594
+ app_check = self._check_app_switched()
3595
+ return_result = None
3596
+
3597
+ if app_check['switched']:
3598
+ # 应用已跳转,说明弹窗去除失败,尝试返回目标应用
3599
+ return_result = self._return_to_target_app()
3600
+
3090
3601
  result["success"] = True
3091
3602
  result["method"] = "模板匹配"
3092
- result["message"] = f"✅ 通过模板匹配找到关闭按钮并点击\n" \
3093
- f" 模板: {best.get('template', 'unknown')}\n" \
3094
- f" 置信度: {best.get('confidence', 'N/A')}%\n" \
3095
- f" 位置: ({x_pct:.1f}%, {y_pct:.1f}%)"
3603
+ msg = f"✅ 通过模板匹配找到关闭按钮并点击\n" \
3604
+ f" 模板: {best.get('template', 'unknown')}\n" \
3605
+ f" 置信度: {best.get('confidence', 'N/A')}%\n" \
3606
+ f" 位置: ({x_pct:.1f}%, {y_pct:.1f}%)"
3607
+
3608
+ if app_check['switched']:
3609
+ msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
3610
+ if return_result:
3611
+ if return_result['success']:
3612
+ msg += f"\n{return_result['message']}"
3613
+ else:
3614
+ msg += f"\n❌ 自动返回失败: {return_result['message']}"
3615
+
3616
+ result["message"] = msg
3617
+ result["app_check"] = app_check
3618
+ result["return_to_app"] = return_result
3096
3619
  return result
3097
3620
 
3098
3621
  except ImportError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.5.11
3
+ Version: 2.6.1
4
4
  Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
5
5
  Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
6
6
  Author: douzi
@@ -25,7 +25,7 @@ if requirements_file.exists():
25
25
 
26
26
  setup(
27
27
  name="mobile-mcp-ai",
28
- version="2.5.11", # find_close_button 增加 resource-id 匹配
28
+ version="2.6.1", # find_close_button 增加 resource-id 匹配 + list_elements 文本过滤
29
29
  author="douzi",
30
30
  author_email="1492994674@qq.com",
31
31
  description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
File without changes
File without changes
File without changes
File without changes