mobile-mcp-ai 2.5.5__tar.gz → 2.5.8__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.5/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.5.8}/PKG-INFO +1 -1
  2. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/__init__.py +1 -1
  3. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/basic_tools_lite.py +61 -23
  4. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/device_manager.py +2 -2
  5. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/mobile_client.py +9 -9
  6. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/utils/smart_wait.py +3 -3
  7. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mcp_tools/mcp_server.py +8 -3
  8. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8/mobile_mcp_ai.egg-info}/PKG-INFO +1 -1
  9. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mobile_mcp_ai.egg-info/SOURCES.txt +10 -0
  10. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/setup.py +1 -1
  11. mobile_mcp_ai-2.5.8/tests/test_mind_cloud_my_space.py +80 -0
  12. mobile_mcp_ai-2.5.8/tests/test_mind_correct.py +73 -0
  13. mobile_mcp_ai-2.5.8/tests/test_mind_improved.py +83 -0
  14. mobile_mcp_ai-2.5.8/tests/test_mind_optimized.py +77 -0
  15. mobile_mcp_ai-2.5.8/tests/test_open_mind.py +37 -0
  16. mobile_mcp_ai-2.5.8/tests/test_priority_demo.py +81 -0
  17. mobile_mcp_ai-2.5.8/tests/test_simple.py +76 -0
  18. mobile_mcp_ai-2.5.8/tests/test_/344/270/276/346/212/245.py +136 -0
  19. mobile_mcp_ai-2.5.8/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py +158 -0
  20. mobile_mcp_ai-2.5.8/tests/test_/346/265/213/350/257/225.py +114 -0
  21. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/LICENSE +0 -0
  22. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/MANIFEST.in +0 -0
  23. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/README.md +0 -0
  24. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/config.py +0 -0
  25. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/__init__.py +0 -0
  26. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/dynamic_config.py +0 -0
  27. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/ios_client_wda.py +0 -0
  28. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/ios_device_manager_wda.py +0 -0
  29. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/template_matcher.py +0 -0
  30. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  31. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  32. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  33. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  34. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  35. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  36. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/utils/__init__.py +0 -0
  37. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/utils/logger.py +0 -0
  38. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/core/utils/operation_history_manager.py +0 -0
  39. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/docs/iOS_SETUP_GUIDE.md +0 -0
  40. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mcp_tools/__init__.py +0 -0
  41. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
  42. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
  43. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
  44. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mobile_mcp_ai.egg-info/requires.txt +0 -0
  45. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
  46. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/requirements.txt +0 -0
  47. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/setup.cfg +0 -0
  48. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/templates/close_buttons/auto_x_0112_151217.png +0 -0
  49. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/templates/close_buttons/auto_x_0112_152037.png +0 -0
  50. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/templates/close_buttons/auto_x_0112_152840.png +0 -0
  51. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/templates/close_buttons/auto_x_0112_153256.png +0 -0
  52. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/templates/close_buttons/auto_x_0112_154847.png +0 -0
  53. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/templates/close_buttons/gray_x_stock_ad.png +0 -0
  54. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/utils/__init__.py +0 -0
  55. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/utils/logger.py +0 -0
  56. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/utils/xml_formatter.py +0 -0
  57. {mobile_mcp_ai-2.5.5 → mobile_mcp_ai-2.5.8}/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.5
3
+ Version: 2.5.8
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
@@ -22,7 +22,7 @@
22
22
  await client.type_text("用户名输入框", "test@example.com")
23
23
  """
24
24
 
25
- __version__ = "2.5.5"
25
+ __version__ = "1.0.0"
26
26
 
27
27
  from .core.mobile_client import MobileClient
28
28
  from .core.device_manager import DeviceManager
@@ -354,7 +354,7 @@ class BasicMobileToolsLite:
354
354
  if show_popup_hints and not self._is_ios():
355
355
  try:
356
356
  import xml.etree.ElementTree as ET
357
- xml_string = self.client.u2.dump_hierarchy()
357
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
358
358
  root = ET.fromstring(xml_string)
359
359
 
360
360
  # 检测弹窗区域
@@ -531,7 +531,7 @@ class BasicMobileToolsLite:
531
531
  else:
532
532
  try:
533
533
  import xml.etree.ElementTree as ET
534
- xml_string = self.client.u2.dump_hierarchy()
534
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
535
535
  root = ET.fromstring(xml_string)
536
536
 
537
537
  for elem in root.iter():
@@ -1089,7 +1089,7 @@ class BasicMobileToolsLite:
1089
1089
  def _find_element_in_tree(self, text: str) -> Optional[Dict]:
1090
1090
  """在 XML 树中查找包含指定文本的元素"""
1091
1091
  try:
1092
- xml = self.client.u2.dump_hierarchy()
1092
+ xml = self.client.u2.dump_hierarchy(compressed=False)
1093
1093
  import xml.etree.ElementTree as ET
1094
1094
  root = ET.fromstring(xml)
1095
1095
 
@@ -1126,9 +1126,16 @@ class BasicMobileToolsLite:
1126
1126
  except Exception:
1127
1127
  return None
1128
1128
 
1129
- def click_by_id(self, resource_id: str) -> Dict:
1130
- """通过 resource-id 点击"""
1129
+ def click_by_id(self, resource_id: str, index: int = 0) -> Dict:
1130
+ """通过 resource-id 点击(支持点击第 N 个元素)
1131
+
1132
+ Args:
1133
+ resource_id: 元素的 resource-id
1134
+ index: 第几个元素(从 0 开始),默认 0 表示第一个
1135
+ """
1131
1136
  try:
1137
+ index_desc = f"[{index}]" if index > 0 else ""
1138
+
1132
1139
  if self._is_ios():
1133
1140
  ios_client = self._get_ios_client()
1134
1141
  if ios_client and hasattr(ios_client, 'wda'):
@@ -1136,18 +1143,28 @@ class BasicMobileToolsLite:
1136
1143
  if not elem.exists:
1137
1144
  elem = ios_client.wda(name=resource_id)
1138
1145
  if elem.exists:
1139
- elem.click()
1140
- time.sleep(0.3)
1141
- self._record_operation('click', element=resource_id, ref=resource_id)
1142
- return {"success": True, "message": f"✅ 点击成功: {resource_id}"}
1146
+ # 获取所有匹配的元素
1147
+ elements = elem.find_elements()
1148
+ if index < len(elements):
1149
+ elements[index].click()
1150
+ time.sleep(0.3)
1151
+ self._record_operation('click', element=f"{resource_id}{index_desc}", ref=resource_id, index=index)
1152
+ return {"success": True, "message": f"✅ 点击成功: {resource_id}{index_desc}"}
1153
+ else:
1154
+ return {"success": False, "message": f"❌ 索引超出范围: 找到 {len(elements)} 个元素,但请求索引 {index}"}
1143
1155
  return {"success": False, "message": f"❌ 元素不存在: {resource_id}"}
1144
1156
  else:
1145
1157
  elem = self.client.u2(resourceId=resource_id)
1146
1158
  if elem.exists(timeout=0.5):
1147
- elem.click()
1148
- time.sleep(0.3)
1149
- self._record_operation('click', element=resource_id, ref=resource_id)
1150
- return {"success": True, "message": f"✅ 点击成功: {resource_id}"}
1159
+ # 获取匹配元素数量
1160
+ count = elem.count
1161
+ if index < count:
1162
+ elem[index].click()
1163
+ time.sleep(0.3)
1164
+ self._record_operation('click', element=f"{resource_id}{index_desc}", ref=resource_id, index=index)
1165
+ return {"success": True, "message": f"✅ 点击成功: {resource_id}{index_desc}" + (f" (共 {count} 个)" if count > 1 else "")}
1166
+ else:
1167
+ return {"success": False, "message": f"❌ 索引超出范围: 找到 {count} 个元素,但请求索引 {index}"}
1151
1168
  return {"success": False, "message": f"❌ 元素不存在: {resource_id}"}
1152
1169
  except Exception as e:
1153
1170
  return {"success": False, "message": f"❌ 点击失败: {e}"}
@@ -1814,7 +1831,7 @@ class BasicMobileToolsLite:
1814
1831
  return ios_client.list_elements()
1815
1832
  return [{"error": "iOS 暂不支持元素列表,建议使用截图"}]
1816
1833
  else:
1817
- xml_string = self.client.u2.dump_hierarchy()
1834
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
1818
1835
  elements = self.client.xml_parser.parse(xml_string)
1819
1836
 
1820
1837
  result = []
@@ -1851,7 +1868,7 @@ class BasicMobileToolsLite:
1851
1868
  screen_height = self.client.u2.info.get('displayHeight', 1280)
1852
1869
 
1853
1870
  # 获取元素列表
1854
- xml_string = self.client.u2.dump_hierarchy()
1871
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
1855
1872
  import xml.etree.ElementTree as ET
1856
1873
  root = ET.fromstring(xml_string)
1857
1874
 
@@ -2005,7 +2022,7 @@ class BasicMobileToolsLite:
2005
2022
  screen_height = self.client.u2.info.get('displayHeight', 1280)
2006
2023
 
2007
2024
  # 获取原始 XML
2008
- xml_string = self.client.u2.dump_hierarchy()
2025
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
2009
2026
 
2010
2027
  # 关闭按钮的文本特征
2011
2028
  close_texts = ['×', 'X', 'x', '关闭', '取消', 'close', 'Close', 'CLOSE', '跳过', '知道了']
@@ -2360,22 +2377,43 @@ class BasicMobileToolsLite:
2360
2377
  return 0.5
2361
2378
 
2362
2379
  def assert_text(self, text: str) -> Dict:
2363
- """检查页面是否包含文本"""
2380
+ """检查页面是否包含文本(支持精确匹配和包含匹配)"""
2364
2381
  try:
2382
+ exists = False
2383
+ match_type = ""
2384
+
2365
2385
  if self._is_ios():
2366
2386
  ios_client = self._get_ios_client()
2367
2387
  if ios_client and hasattr(ios_client, 'wda'):
2368
- exists = ios_client.wda(name=text).exists or ios_client.wda(label=text).exists
2369
- else:
2370
- exists = False
2388
+ # 先尝试精确匹配
2389
+ if ios_client.wda(name=text).exists or ios_client.wda(label=text).exists:
2390
+ exists = True
2391
+ match_type = "精确匹配"
2392
+ # 再尝试包含匹配
2393
+ elif ios_client.wda(nameContains=text).exists or ios_client.wda(labelContains=text).exists:
2394
+ exists = True
2395
+ match_type = "包含匹配"
2396
+ else:
2397
+ # Android: 先尝试精确匹配
2398
+ if self.client.u2(text=text).exists():
2399
+ exists = True
2400
+ match_type = "精确匹配"
2401
+ # 再尝试包含匹配
2402
+ elif self.client.u2(textContains=text).exists():
2403
+ exists = True
2404
+ match_type = "包含匹配"
2405
+
2406
+ if exists:
2407
+ message = f"✅ 文本'{text}' 存在({match_type})"
2371
2408
  else:
2372
- exists = self.client.u2(text=text).exists()
2409
+ message = f"❌ 文本'{text}' 不存在"
2373
2410
 
2374
2411
  return {
2375
2412
  "success": True,
2376
2413
  "found": exists,
2377
2414
  "text": text,
2378
- "message": f"✅ 文本'{text}' {'存在' if exists else '不存在'}"
2415
+ "match_type": match_type if exists else None,
2416
+ "message": message
2379
2417
  }
2380
2418
  except Exception as e:
2381
2419
  return {"success": False, "message": f"❌ 断言失败: {e}"}
@@ -2856,7 +2894,7 @@ class BasicMobileToolsLite:
2856
2894
  import xml.etree.ElementTree as ET
2857
2895
 
2858
2896
  # ========== 第1步:控件树查找关闭按钮 ==========
2859
- xml_string = self.client.u2.dump_hierarchy()
2897
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
2860
2898
  root = ET.fromstring(xml_string)
2861
2899
 
2862
2900
  # 关闭按钮的常见特征
@@ -208,7 +208,7 @@ class DeviceManager:
208
208
 
209
209
  try:
210
210
  # 尝试获取页面结构,如果失败可能是无障碍服务未启用
211
- xml = self.u2.dump_hierarchy()
211
+ xml = self.u2.dump_hierarchy(compressed=False)
212
212
  if xml and len(xml) > 100: # 有内容说明无障碍服务正常
213
213
  print(f" ✅ 无障碍服务: 已启用", file=sys.stderr)
214
214
  return
@@ -235,7 +235,7 @@ class DeviceManager:
235
235
 
236
236
  try:
237
237
  # 尝试获取页面结构
238
- xml = self.u2.dump_hierarchy()
238
+ xml = self.u2.dump_hierarchy(compressed=False)
239
239
  if xml and len(xml) > 100:
240
240
  return {
241
241
  'enabled': True,
@@ -190,7 +190,7 @@ class MobileClient:
190
190
 
191
191
  # Android平台
192
192
  # 获取XML
193
- xml_string = self.u2.dump_hierarchy()
193
+ xml_string = self.u2.dump_hierarchy(compressed=False)
194
194
 
195
195
  # 确保xml_string是字符串类型
196
196
  if not isinstance(xml_string, str):
@@ -321,7 +321,7 @@ class MobileClient:
321
321
  # 🎯 改进:尝试模糊匹配(忽略空格、括号)
322
322
  ref_normalized = ref.replace(' ', '').replace('(', '').replace(')', '').replace('(', '').replace(')', '')
323
323
  # 获取所有元素,手动匹配
324
- xml_string = self.u2.dump_hierarchy()
324
+ xml_string = self.u2.dump_hierarchy(compressed=False)
325
325
  elements = self.xml_parser.parse(xml_string)
326
326
  for elem in elements:
327
327
  elem_desc = elem.get('content_desc', '')
@@ -434,7 +434,7 @@ class MobileClient:
434
434
  if verify:
435
435
  # 获取点击前页面状态
436
436
  try:
437
- initial_xml = self.u2.dump_hierarchy()
437
+ initial_xml = self.u2.dump_hierarchy(compressed=False)
438
438
  initial_length = len(initial_xml)
439
439
 
440
440
  # 等待页面变化
@@ -757,7 +757,7 @@ class MobileClient:
757
757
  initial_length = 0
758
758
  if verify:
759
759
  try:
760
- initial_xml = self.u2.dump_hierarchy()
760
+ initial_xml = self.u2.dump_hierarchy(compressed=False)
761
761
  initial_length = len(initial_xml)
762
762
  except Exception as e:
763
763
  print(f" ⚠️ 获取初始页面状态失败: {e}", file=sys.stderr)
@@ -1008,7 +1008,7 @@ class MobileClient:
1008
1008
  try:
1009
1009
  if verify:
1010
1010
  # 获取操作前页面状态
1011
- initial_xml = self.u2.dump_hierarchy()
1011
+ initial_xml = self.u2.dump_hierarchy(compressed=False)
1012
1012
  initial_length = len(initial_xml)
1013
1013
 
1014
1014
  self.u2.press(key.lower())
@@ -1037,7 +1037,7 @@ class MobileClient:
1037
1037
  # 标准按键处理
1038
1038
  if verify:
1039
1039
  # 获取操作前页面状态
1040
- initial_xml = self.u2.dump_hierarchy()
1040
+ initial_xml = self.u2.dump_hierarchy(compressed=False)
1041
1041
  initial_length = len(initial_xml)
1042
1042
 
1043
1043
  # 使用keycode按键 - uiautomator2使用shell命令
@@ -1099,7 +1099,7 @@ class MobileClient:
1099
1099
  print(f" 🔍 智能搜索键:先尝试SEARCH键...", file=sys.stderr)
1100
1100
 
1101
1101
  # 获取初始页面状态
1102
- initial_xml = self.u2.dump_hierarchy()
1102
+ initial_xml = self.u2.dump_hierarchy(compressed=False)
1103
1103
  initial_length = len(initial_xml)
1104
1104
 
1105
1105
  # 方案1: 尝试 SEARCH 键 (keycode=84)
@@ -1126,7 +1126,7 @@ class MobileClient:
1126
1126
 
1127
1127
  # 方案2: 尝试 ENTER 键 (keycode=66)
1128
1128
  # 重新获取当前页面状态(因为可能有轻微变化)
1129
- current_xml = self.u2.dump_hierarchy()
1129
+ current_xml = self.u2.dump_hierarchy(compressed=False)
1130
1130
  current_length = len(current_xml)
1131
1131
 
1132
1132
  self.u2.shell('input keyevent 66')
@@ -1184,7 +1184,7 @@ class MobileClient:
1184
1184
  await asyncio.sleep(0.1) # 每100ms检查一次
1185
1185
 
1186
1186
  try:
1187
- current_xml = self.u2.dump_hierarchy()
1187
+ current_xml = self.u2.dump_hierarchy(compressed=False)
1188
1188
  current_length = len(current_xml)
1189
1189
 
1190
1190
  # 计算变化百分比
@@ -61,7 +61,7 @@ class SmartWait:
61
61
  while time.time() - start_time < timeout:
62
62
  try:
63
63
  # 获取当前页面快照(只获取元素数量,不解析详细内容)
64
- xml = self.client.u2.dump_hierarchy()
64
+ xml = self.client.u2.dump_hierarchy(compressed=False)
65
65
  current_snapshot = len(xml) # 使用XML长度作为简单的页面状态标识
66
66
 
67
67
  if last_snapshot is not None:
@@ -137,14 +137,14 @@ class SmartWait:
137
137
 
138
138
  try:
139
139
  # 获取初始页面状态
140
- initial_xml = self.client.u2.dump_hierarchy()
140
+ initial_xml = self.client.u2.dump_hierarchy(compressed=False)
141
141
  initial_length = len(initial_xml)
142
142
 
143
143
  while time.time() - start_time < timeout:
144
144
  await asyncio.sleep(self.poll_interval)
145
145
 
146
146
  try:
147
- current_xml = self.client.u2.dump_hierarchy()
147
+ current_xml = self.client.u2.dump_hierarchy(compressed=False)
148
148
  current_length = len(current_xml)
149
149
 
150
150
  # 页面变化超过5%认为有变化
@@ -331,11 +331,13 @@ class MobileMCPServer:
331
331
  description="👆 通过 resource-id 点击元素(最推荐)\n\n"
332
332
  "✅ 最稳定的定位方式\n"
333
333
  "✅ 实时检测元素是否存在,元素不存在会报错\n"
334
- "📋 使用前先调用 mobile_list_elements 获取元素 ID",
334
+ "📋 使用前先调用 mobile_list_elements 获取元素 ID\n"
335
+ "💡 当有多个相同 ID 的元素时,用 index 指定第几个(从 0 开始)",
335
336
  inputSchema={
336
337
  "type": "object",
337
338
  "properties": {
338
- "resource_id": {"type": "string", "description": "元素的 resource-id"}
339
+ "resource_id": {"type": "string", "description": "元素的 resource-id"},
340
+ "index": {"type": "integer", "description": "第几个元素(从 0 开始),默认 0 表示第一个", "default": 0}
339
341
  },
340
342
  "required": ["resource_id"]
341
343
  }
@@ -860,7 +862,10 @@ class MobileMCPServer:
860
862
  return [TextContent(type="text", text=self.format_response(result))]
861
863
 
862
864
  elif name == "mobile_click_by_id":
863
- result = self.tools.click_by_id(arguments["resource_id"])
865
+ result = self.tools.click_by_id(
866
+ arguments["resource_id"],
867
+ arguments.get("index", 0)
868
+ )
864
869
  return [TextContent(type="text", text=self.format_response(result))]
865
870
 
866
871
  elif name == "mobile_click_by_percent":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.5.5
3
+ Version: 2.5.8
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
@@ -65,6 +65,16 @@ templates/close_buttons/auto_x_0112_152840.png
65
65
  templates/close_buttons/auto_x_0112_153256.png
66
66
  templates/close_buttons/auto_x_0112_154847.png
67
67
  templates/close_buttons/gray_x_stock_ad.png
68
+ tests/test_mind_cloud_my_space.py
69
+ tests/test_mind_correct.py
70
+ tests/test_mind_improved.py
71
+ tests/test_mind_optimized.py
72
+ tests/test_open_mind.py
73
+ tests/test_priority_demo.py
74
+ tests/test_simple.py
75
+ tests/test_举报.py
76
+ tests/test_切换语言到English.py
77
+ tests/test_测试.py
68
78
  utils/__init__.py
69
79
  utils/logger.py
70
80
  utils/xml_formatter.py
@@ -25,7 +25,7 @@ if requirements_file.exists():
25
25
 
26
26
  setup(
27
27
  name="mobile-mcp-ai",
28
- version="2.5.5", # 支持左右滑动指定高度坐标和百分比
28
+ version="2.5.8", # dump_hierarchy 使用 compressed=False 获取完整 XML 树
29
29
  author="douzi",
30
30
  author_email="1492994674@qq.com",
31
31
  description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 测试用例: Mind云文档我的空间
5
+ 生成时间: 2025-12-17 11:00:00
6
+ """
7
+ import time
8
+ import uiautomator2 as u2
9
+
10
+ PACKAGE_NAME = "com.im30.mind"
11
+
12
+ # 广告关闭按钮关键词(可自定义)
13
+ AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
14
+
15
+
16
+ def smart_wait(d, timeout=10):
17
+ """智能等待页面稳定"""
18
+ d.implicitly_wait(timeout)
19
+ time.sleep(0.5) # 额外等待动画
20
+
21
+
22
+ def close_ad_if_exists(d):
23
+ """尝试关闭广告弹窗"""
24
+ for keyword in AD_CLOSE_KEYWORDS:
25
+ elem = d(textContains=keyword)
26
+ if elem.exists(timeout=0.5):
27
+ try:
28
+ elem.click()
29
+ print(f' 📢 关闭广告: {keyword}')
30
+ time.sleep(0.5)
31
+ return True
32
+ except:
33
+ pass
34
+ return False
35
+
36
+
37
+ def safe_click(d, selector, timeout=5):
38
+ """安全点击(带等待和重试)"""
39
+ try:
40
+ if selector.exists(timeout=timeout):
41
+ selector.click()
42
+ return True
43
+ return False
44
+ except Exception as e:
45
+ print(f' ⚠️ 点击失败: {e}')
46
+ return False
47
+
48
+
49
+ def test_main():
50
+ # 连接设备
51
+ d = u2.connect()
52
+ d.implicitly_wait(10) # 设置全局等待
53
+
54
+ # 启动应用
55
+ d.app_start(PACKAGE_NAME)
56
+ smart_wait(d)
57
+
58
+ # 尝试关闭启动广告
59
+ close_ad_if_exists(d)
60
+
61
+ # 步骤1: 点击文本 'Mind'
62
+ safe_click(d, d(text='Mind'))
63
+ smart_wait(d)
64
+ close_ad_if_exists(d) # 检查广告
65
+
66
+ # 步骤2: 点击坐标 (756, 2277)
67
+ d.click(756, 2277)
68
+ smart_wait(d)
69
+ close_ad_if_exists(d) # 检查广告
70
+
71
+ # 步骤3: 点击坐标 (815, 285)
72
+ d.click(815, 285)
73
+ smart_wait(d)
74
+ close_ad_if_exists(d) # 检查广告
75
+
76
+ print('✅ 测试完成')
77
+
78
+
79
+ if __name__ == '__main__':
80
+ test_main()
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 测试用例: Mind云文档我的空间_正确版
5
+ 生成时间: 2025-12-17 11:08:10
6
+ """
7
+ import time
8
+ import uiautomator2 as u2
9
+
10
+ PACKAGE_NAME = "com.im30.mind"
11
+
12
+ # 广告关闭按钮关键词(可自定义)
13
+ AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
14
+
15
+
16
+ def smart_wait(d, seconds=1):
17
+ """等待页面稳定"""
18
+ time.sleep(seconds)
19
+
20
+
21
+ def close_ad_if_exists(d, quick=False):
22
+ """尝试关闭广告弹窗(quick=True 时只检查常见的)"""
23
+ keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
24
+ for keyword in keywords:
25
+ elem = d(textContains=keyword)
26
+ if elem.exists(timeout=0.3): # 缩短超时
27
+ try:
28
+ elem.click()
29
+ print(f' 📢 关闭广告: {keyword}')
30
+ time.sleep(0.3)
31
+ return True
32
+ except:
33
+ pass
34
+ return False
35
+
36
+
37
+ def safe_click(d, selector, timeout=3):
38
+ """安全点击(带等待)"""
39
+ try:
40
+ if selector.exists(timeout=timeout):
41
+ selector.click()
42
+ return True
43
+ return False
44
+ except Exception as e:
45
+ print(f' ⚠️ 点击失败: {e}')
46
+ return False
47
+
48
+
49
+ def test_main():
50
+ # 连接设备
51
+ d = u2.connect()
52
+ d.implicitly_wait(10) # 设置全局等待
53
+
54
+ # 启动应用
55
+ d.app_start(PACKAGE_NAME)
56
+ smart_wait(d)
57
+
58
+ # 尝试关闭启动广告
59
+ close_ad_if_exists(d)
60
+
61
+ # 步骤1: 点击坐标 (756, 2277)
62
+ d.click(756, 2277)
63
+ time.sleep(0.5) # 等待响应
64
+
65
+ # 步骤2: 点击坐标 (815, 285)
66
+ d.click(815, 285)
67
+ time.sleep(0.5) # 等待响应
68
+
69
+ print('✅ 测试完成')
70
+
71
+
72
+ if __name__ == '__main__':
73
+ test_main()
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 测试用例: 打开Mind应用测试
5
+ 生成时间: 2025-12-17 10:52:37
6
+ """
7
+ import time
8
+ import uiautomator2 as u2
9
+
10
+ PACKAGE_NAME = "com.im30.mind"
11
+
12
+ # 广告关闭按钮关键词(可自定义)
13
+ AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
14
+
15
+
16
+ def smart_wait(d, timeout=10):
17
+ """智能等待页面稳定"""
18
+ d.implicitly_wait(timeout)
19
+ time.sleep(0.5) # 额外等待动画
20
+
21
+
22
+ def close_ad_if_exists(d):
23
+ """尝试关闭广告弹窗"""
24
+ for keyword in AD_CLOSE_KEYWORDS:
25
+ elem = d(textContains=keyword)
26
+ if elem.exists(timeout=0.5):
27
+ try:
28
+ elem.click()
29
+ print(f' 📢 关闭广告: {keyword}')
30
+ time.sleep(0.5)
31
+ return True
32
+ except:
33
+ pass
34
+ return False
35
+
36
+
37
+ def safe_click(d, selector, timeout=5):
38
+ """安全点击(带等待和重试)"""
39
+ try:
40
+ if selector.exists(timeout=timeout):
41
+ selector.click()
42
+ return True
43
+ return False
44
+ except Exception as e:
45
+ print(f' ⚠️ 点击失败: {e}')
46
+ return False
47
+
48
+
49
+ def test_main():
50
+ # 连接设备
51
+ d = u2.connect()
52
+ d.implicitly_wait(10) # 设置全局等待
53
+
54
+ # 启动应用
55
+ d.app_start(PACKAGE_NAME)
56
+ smart_wait(d)
57
+
58
+ # 尝试关闭启动广告
59
+ close_ad_if_exists(d)
60
+
61
+ # 步骤1: 点击文本 'Mind'
62
+ safe_click(d, d(text='Mind'))
63
+ smart_wait(d)
64
+ close_ad_if_exists(d) # 检查广告
65
+
66
+ # 步骤2: 点击坐标 (540, 1200)
67
+ d.click(540, 1200)
68
+ smart_wait(d)
69
+ close_ad_if_exists(d) # 检查广告
70
+
71
+ # 步骤3: 输入文本 '测试'
72
+ d(resourceId='com.im30.mind:id/search').set_text('测试')
73
+ smart_wait(d)
74
+
75
+ # 步骤4: 滑动 up
76
+ d.swipe_ext('up')
77
+ smart_wait(d)
78
+
79
+ print('✅ 测试完成')
80
+
81
+
82
+ if __name__ == '__main__':
83
+ test_main()