mobile-mcp-ai 2.5.10__py3-none-any.whl → 2.5.11__py3-none-any.whl
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.
- mobile_mcp/core/basic_tools_lite.py +112 -481
- mobile_mcp/mcp_tools/mcp_server.py +22 -17
- {mobile_mcp_ai-2.5.10.dist-info → mobile_mcp_ai-2.5.11.dist-info}/METADATA +1 -1
- {mobile_mcp_ai-2.5.10.dist-info → mobile_mcp_ai-2.5.11.dist-info}/RECORD +8 -8
- {mobile_mcp_ai-2.5.10.dist-info → mobile_mcp_ai-2.5.11.dist-info}/WHEEL +0 -0
- {mobile_mcp_ai-2.5.10.dist-info → mobile_mcp_ai-2.5.11.dist-info}/entry_points.txt +0 -0
- {mobile_mcp_ai-2.5.10.dist-info → mobile_mcp_ai-2.5.11.dist-info}/licenses/LICENSE +0 -0
- {mobile_mcp_ai-2.5.10.dist-info → mobile_mcp_ai-2.5.11.dist-info}/top_level.txt +0 -0
|
@@ -31,9 +31,6 @@ class BasicMobileToolsLite:
|
|
|
31
31
|
|
|
32
32
|
# 操作历史(用于生成 pytest 脚本)
|
|
33
33
|
self.operation_history: List[Dict] = []
|
|
34
|
-
|
|
35
|
-
# 目标应用包名(用于监测应用跳转)
|
|
36
|
-
self.target_package: Optional[str] = None
|
|
37
34
|
|
|
38
35
|
def _is_ios(self) -> bool:
|
|
39
36
|
"""判断当前是否为 iOS 平台"""
|
|
@@ -56,157 +53,34 @@ class BasicMobileToolsLite:
|
|
|
56
53
|
}
|
|
57
54
|
self.operation_history.append(record)
|
|
58
55
|
|
|
59
|
-
def
|
|
60
|
-
"""
|
|
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
|
-
"""检查是否已跳出目标应用
|
|
56
|
+
def _get_full_hierarchy(self) -> str:
|
|
57
|
+
"""获取完整的 UI 层级 XML(包含 NAF 元素)
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
{
|
|
78
|
-
'switched': bool, # 是否跳转
|
|
79
|
-
'current_package': str, # 当前应用包名
|
|
80
|
-
'target_package': str, # 目标应用包名
|
|
81
|
-
'message': str # 提示信息
|
|
82
|
-
}
|
|
59
|
+
优先使用 ADB 直接 dump,比 uiautomator2.dump_hierarchy 更完整
|
|
83
60
|
"""
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
'switched': False,
|
|
87
|
-
'current_package': None,
|
|
88
|
-
'target_package': None,
|
|
89
|
-
'message': '⚠️ 未设置目标应用,无法监测应用跳转'
|
|
90
|
-
}
|
|
91
|
-
|
|
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
|
-
}
|
|
61
|
+
import sys
|
|
108
62
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
'
|
|
113
|
-
|
|
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
|
-
}
|
|
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 ""
|
|
137
69
|
|
|
70
|
+
# Android: 优先使用 ADB 直接 dump
|
|
138
71
|
try:
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
}
|
|
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
|
|
203
79
|
except Exception as e:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
80
|
+
print(f" ⚠️ ADB dump 失败: {e}", file=sys.stderr)
|
|
81
|
+
|
|
82
|
+
# 方法2: 回退到 uiautomator2
|
|
83
|
+
return self.client.u2.dump_hierarchy(compressed=False)
|
|
210
84
|
|
|
211
85
|
# ==================== 截图 ====================
|
|
212
86
|
|
|
@@ -507,7 +381,7 @@ class BasicMobileToolsLite:
|
|
|
507
381
|
if show_popup_hints and not self._is_ios():
|
|
508
382
|
try:
|
|
509
383
|
import xml.etree.ElementTree as ET
|
|
510
|
-
xml_string = self.
|
|
384
|
+
xml_string = self._get_full_hierarchy()
|
|
511
385
|
root = ET.fromstring(xml_string)
|
|
512
386
|
|
|
513
387
|
# 检测弹窗区域
|
|
@@ -684,7 +558,7 @@ class BasicMobileToolsLite:
|
|
|
684
558
|
else:
|
|
685
559
|
try:
|
|
686
560
|
import xml.etree.ElementTree as ET
|
|
687
|
-
xml_string = self.
|
|
561
|
+
xml_string = self._get_full_hierarchy()
|
|
688
562
|
root = ET.fromstring(xml_string)
|
|
689
563
|
|
|
690
564
|
for elem in root.iter():
|
|
@@ -1089,41 +963,25 @@ class BasicMobileToolsLite:
|
|
|
1089
963
|
ref=f"coords_{x}_{y}"
|
|
1090
964
|
)
|
|
1091
965
|
|
|
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
|
-
# 构建返回消息
|
|
1101
966
|
if converted:
|
|
1102
967
|
if conversion_type == "crop_offset":
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
+
}
|
|
1105
973
|
else:
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
+
}
|
|
1109
980
|
else:
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
}
|
|
981
|
+
return {
|
|
982
|
+
"success": True,
|
|
983
|
+
"message": f"✅ 点击成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%]"
|
|
984
|
+
}
|
|
1127
985
|
except Exception as e:
|
|
1128
986
|
return {"success": False, "message": f"❌ 点击失败: {e}"}
|
|
1129
987
|
|
|
@@ -1256,9 +1114,9 @@ class BasicMobileToolsLite:
|
|
|
1256
1114
|
return {"success": False, "message": f"❌ 点击失败: {e}"}
|
|
1257
1115
|
|
|
1258
1116
|
def _find_element_in_tree(self, text: str) -> Optional[Dict]:
|
|
1259
|
-
"""在 XML
|
|
1117
|
+
"""在 XML 树中查找包含指定文本的元素(使用完整 UI 层级)"""
|
|
1260
1118
|
try:
|
|
1261
|
-
xml = self.
|
|
1119
|
+
xml = self._get_full_hierarchy()
|
|
1262
1120
|
import xml.etree.ElementTree as ET
|
|
1263
1121
|
root = ET.fromstring(xml)
|
|
1264
1122
|
|
|
@@ -1652,28 +1510,7 @@ class BasicMobileToolsLite:
|
|
|
1652
1510
|
elem.set_text(text)
|
|
1653
1511
|
time.sleep(0.3)
|
|
1654
1512
|
self._record_operation('input', element=resource_id, ref=resource_id, text=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
|
-
}
|
|
1513
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
1677
1514
|
return {"success": False, "message": f"❌ 输入框不存在: {resource_id}"}
|
|
1678
1515
|
else:
|
|
1679
1516
|
elements = self.client.u2(resourceId=resource_id)
|
|
@@ -1687,28 +1524,7 @@ class BasicMobileToolsLite:
|
|
|
1687
1524
|
elements.set_text(text)
|
|
1688
1525
|
time.sleep(0.3)
|
|
1689
1526
|
self._record_operation('input', element=resource_id, ref=resource_id, text=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
|
-
}
|
|
1527
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
1712
1528
|
|
|
1713
1529
|
# 多个相同 ID(<=5个),尝试智能选择
|
|
1714
1530
|
if count <= 5:
|
|
@@ -1721,56 +1537,14 @@ class BasicMobileToolsLite:
|
|
|
1721
1537
|
elem.set_text(text)
|
|
1722
1538
|
time.sleep(0.3)
|
|
1723
1539
|
self._record_operation('input', element=resource_id, ref=resource_id, text=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
|
-
}
|
|
1540
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
1746
1541
|
except:
|
|
1747
1542
|
continue
|
|
1748
1543
|
# 没找到可编辑的,用第一个
|
|
1749
1544
|
elements[0].set_text(text)
|
|
1750
1545
|
time.sleep(0.3)
|
|
1751
1546
|
self._record_operation('input', element=resource_id, ref=resource_id, text=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
|
-
}
|
|
1547
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
1774
1548
|
|
|
1775
1549
|
# ID 不可靠(不存在或太多),改用 EditText 类型定位
|
|
1776
1550
|
edit_texts = self.client.u2(className='android.widget.EditText')
|
|
@@ -1780,28 +1554,7 @@ class BasicMobileToolsLite:
|
|
|
1780
1554
|
edit_texts.set_text(text)
|
|
1781
1555
|
time.sleep(0.3)
|
|
1782
1556
|
self._record_operation('input', element='EditText', ref='EditText', text=text)
|
|
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
|
-
}
|
|
1557
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}' (通过 EditText 定位)"}
|
|
1805
1558
|
|
|
1806
1559
|
# 多个 EditText,选择最靠上的
|
|
1807
1560
|
best_elem = None
|
|
@@ -1820,28 +1573,7 @@ class BasicMobileToolsLite:
|
|
|
1820
1573
|
best_elem.set_text(text)
|
|
1821
1574
|
time.sleep(0.3)
|
|
1822
1575
|
self._record_operation('input', element='EditText', ref='EditText', text=text)
|
|
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
|
-
}
|
|
1576
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}' (通过 EditText 定位,选择最顶部的)"}
|
|
1845
1577
|
|
|
1846
1578
|
return {"success": False, "message": f"❌ 输入框不存在: {resource_id}"}
|
|
1847
1579
|
|
|
@@ -1893,29 +1625,7 @@ class BasicMobileToolsLite:
|
|
|
1893
1625
|
text=text
|
|
1894
1626
|
)
|
|
1895
1627
|
|
|
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
|
-
}
|
|
1628
|
+
return {"success": True, "message": f"✅ 输入成功: ({x}, {y}) [相对位置: {x_percent}%, {y_percent}%] -> '{text}'"}
|
|
1919
1629
|
except Exception as e:
|
|
1920
1630
|
return {"success": False, "message": f"❌ 输入失败: {e}"}
|
|
1921
1631
|
|
|
@@ -1982,14 +1692,6 @@ class BasicMobileToolsLite:
|
|
|
1982
1692
|
record_info['y_percent'] = y_percent
|
|
1983
1693
|
self._record_operation('swipe', **record_info)
|
|
1984
1694
|
|
|
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
|
-
|
|
1993
1695
|
# 构建返回消息
|
|
1994
1696
|
msg = f"✅ 滑动成功: {direction}"
|
|
1995
1697
|
if direction in ['left', 'right']:
|
|
@@ -1998,21 +1700,7 @@ class BasicMobileToolsLite:
|
|
|
1998
1700
|
elif y is not None:
|
|
1999
1701
|
msg += f" (高度: {y}px)"
|
|
2000
1702
|
|
|
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
|
-
}
|
|
1703
|
+
return {"success": True, "message": msg}
|
|
2016
1704
|
except Exception as e:
|
|
2017
1705
|
return {"success": False, "message": f"❌ 滑动失败: {e}"}
|
|
2018
1706
|
|
|
@@ -2068,22 +1756,11 @@ class BasicMobileToolsLite:
|
|
|
2068
1756
|
|
|
2069
1757
|
await asyncio.sleep(2)
|
|
2070
1758
|
|
|
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
|
-
|
|
2082
1759
|
self._record_operation('launch_app', package_name=package_name)
|
|
2083
1760
|
|
|
2084
1761
|
return {
|
|
2085
1762
|
"success": True,
|
|
2086
|
-
"message": f"✅ 已启动: {package_name}\n💡 建议等待 2-3
|
|
1763
|
+
"message": f"✅ 已启动: {package_name}\n💡 建议等待 2-3 秒让页面加载"
|
|
2087
1764
|
}
|
|
2088
1765
|
except Exception as e:
|
|
2089
1766
|
return {"success": False, "message": f"❌ 启动失败: {e}"}
|
|
@@ -2181,15 +1858,12 @@ class BasicMobileToolsLite:
|
|
|
2181
1858
|
return ios_client.list_elements()
|
|
2182
1859
|
return [{"error": "iOS 暂不支持元素列表,建议使用截图"}]
|
|
2183
1860
|
else:
|
|
2184
|
-
xml_string = self.
|
|
1861
|
+
xml_string = self._get_full_hierarchy()
|
|
2185
1862
|
elements = self.client.xml_parser.parse(xml_string)
|
|
2186
1863
|
|
|
2187
1864
|
result = []
|
|
2188
1865
|
for elem in elements:
|
|
2189
|
-
|
|
2190
|
-
text = elem.get('text', '').strip()
|
|
2191
|
-
# 保留:可点击、可focus或有文本的元素
|
|
2192
|
-
if elem.get('clickable') or elem.get('focusable') or text:
|
|
1866
|
+
if elem.get('clickable') or elem.get('focusable'):
|
|
2193
1867
|
result.append({
|
|
2194
1868
|
'resource_id': elem.get('resource_id', ''),
|
|
2195
1869
|
'text': elem.get('text', ''),
|
|
@@ -2220,8 +1894,8 @@ class BasicMobileToolsLite:
|
|
|
2220
1894
|
screen_width = self.client.u2.info.get('displayWidth', 720)
|
|
2221
1895
|
screen_height = self.client.u2.info.get('displayHeight', 1280)
|
|
2222
1896
|
|
|
2223
|
-
#
|
|
2224
|
-
xml_string = self.
|
|
1897
|
+
# 获取元素列表(使用完整 UI 层级)
|
|
1898
|
+
xml_string = self._get_full_hierarchy()
|
|
2225
1899
|
import xml.etree.ElementTree as ET
|
|
2226
1900
|
root = ET.fromstring(xml_string)
|
|
2227
1901
|
|
|
@@ -2232,6 +1906,7 @@ class BasicMobileToolsLite:
|
|
|
2232
1906
|
for elem in root.iter():
|
|
2233
1907
|
text = elem.attrib.get('text', '')
|
|
2234
1908
|
content_desc = elem.attrib.get('content-desc', '')
|
|
1909
|
+
resource_id = elem.attrib.get('resource-id', '')
|
|
2235
1910
|
bounds_str = elem.attrib.get('bounds', '')
|
|
2236
1911
|
class_name = elem.attrib.get('class', '')
|
|
2237
1912
|
clickable = elem.attrib.get('clickable', 'false') == 'true'
|
|
@@ -2266,6 +1941,13 @@ class BasicMobileToolsLite:
|
|
|
2266
1941
|
score = 90
|
|
2267
1942
|
reason = f"描述='{content_desc}'"
|
|
2268
1943
|
|
|
1944
|
+
# 策略2.5:resource-id 包含关闭关键词(如 close_icon, ad_close 等)
|
|
1945
|
+
elif resource_id and any(kw in resource_id.lower() for kw in ['close', 'dismiss', 'skip', 'cancel']):
|
|
1946
|
+
score = 95
|
|
1947
|
+
# 提取简短的 id 名
|
|
1948
|
+
short_id = resource_id.split('/')[-1] if '/' in resource_id else resource_id
|
|
1949
|
+
reason = f"resource-id='{short_id}'"
|
|
1950
|
+
|
|
2269
1951
|
# 策略3:小尺寸的 clickable 元素(可能是 X 图标)
|
|
2270
1952
|
elif clickable:
|
|
2271
1953
|
min_size = max(20, int(screen_width * 0.03))
|
|
@@ -2300,7 +1982,9 @@ class BasicMobileToolsLite:
|
|
|
2300
1982
|
'center_y': center_y,
|
|
2301
1983
|
'x_percent': x_percent,
|
|
2302
1984
|
'y_percent': y_percent,
|
|
2303
|
-
'size': f"{width}x{height}"
|
|
1985
|
+
'size': f"{width}x{height}",
|
|
1986
|
+
'resource_id': resource_id,
|
|
1987
|
+
'text': text
|
|
2304
1988
|
})
|
|
2305
1989
|
|
|
2306
1990
|
if not candidates:
|
|
@@ -2326,7 +2010,16 @@ class BasicMobileToolsLite:
|
|
|
2326
2010
|
candidates.sort(key=lambda x: x['score'], reverse=True)
|
|
2327
2011
|
best = candidates[0]
|
|
2328
2012
|
|
|
2329
|
-
|
|
2013
|
+
# 生成推荐的点击命令(优先使用 resource-id)
|
|
2014
|
+
if best.get('resource_id'):
|
|
2015
|
+
short_id = best['resource_id'].split('/')[-1] if '/' in best['resource_id'] else best['resource_id']
|
|
2016
|
+
click_cmd = f"mobile_click_by_id('{best['resource_id']}')"
|
|
2017
|
+
elif best.get('text') and best['text'] in ['×', 'X', 'x', '关闭', '取消', '跳过', '知道了']:
|
|
2018
|
+
click_cmd = f"mobile_click_by_text('{best['text']}')"
|
|
2019
|
+
else:
|
|
2020
|
+
click_cmd = f"mobile_click_by_percent({best['x_percent']}, {best['y_percent']})"
|
|
2021
|
+
|
|
2022
|
+
result = {
|
|
2330
2023
|
"success": True,
|
|
2331
2024
|
"message": f"✅ 找到可能的关闭按钮",
|
|
2332
2025
|
"best_candidate": {
|
|
@@ -2337,7 +2030,7 @@ class BasicMobileToolsLite:
|
|
|
2337
2030
|
"size": best['size'],
|
|
2338
2031
|
"score": best['score']
|
|
2339
2032
|
},
|
|
2340
|
-
"click_command":
|
|
2033
|
+
"click_command": click_cmd,
|
|
2341
2034
|
"other_candidates": [
|
|
2342
2035
|
{"reason": c['reason'], "percent": f"({c['x_percent']}%, {c['y_percent']}%)", "score": c['score']}
|
|
2343
2036
|
for c in candidates[1:4]
|
|
@@ -2345,6 +2038,14 @@ class BasicMobileToolsLite:
|
|
|
2345
2038
|
"screen_size": {"width": screen_width, "height": screen_height}
|
|
2346
2039
|
}
|
|
2347
2040
|
|
|
2041
|
+
# 如果有 resource-id,额外提供
|
|
2042
|
+
if best.get('resource_id'):
|
|
2043
|
+
result["best_candidate"]["resource_id"] = best['resource_id']
|
|
2044
|
+
if best.get('text'):
|
|
2045
|
+
result["best_candidate"]["text"] = best['text']
|
|
2046
|
+
|
|
2047
|
+
return result
|
|
2048
|
+
|
|
2348
2049
|
except Exception as e:
|
|
2349
2050
|
return {"success": False, "message": f"❌ 查找关闭按钮失败: {e}"}
|
|
2350
2051
|
|
|
@@ -2374,8 +2075,8 @@ class BasicMobileToolsLite:
|
|
|
2374
2075
|
screen_width = self.client.u2.info.get('displayWidth', 720)
|
|
2375
2076
|
screen_height = self.client.u2.info.get('displayHeight', 1280)
|
|
2376
2077
|
|
|
2377
|
-
# 获取原始 XML
|
|
2378
|
-
xml_string = self.
|
|
2078
|
+
# 获取原始 XML(使用完整 UI 层级)
|
|
2079
|
+
xml_string = self._get_full_hierarchy()
|
|
2379
2080
|
|
|
2380
2081
|
# 关闭按钮的文本特征
|
|
2381
2082
|
close_texts = ['×', 'X', 'x', '关闭', '取消', 'close', 'Close', 'CLOSE', '跳过', '知道了']
|
|
@@ -2604,33 +2305,13 @@ class BasicMobileToolsLite:
|
|
|
2604
2305
|
self.client.u2.click(try_x, try_y)
|
|
2605
2306
|
time.sleep(0.3)
|
|
2606
2307
|
|
|
2607
|
-
# 🎯 关键步骤:检查应用是否跳转,如果跳转说明弹窗去除失败,需要返回目标应用
|
|
2608
|
-
app_check = self._check_app_switched()
|
|
2609
|
-
return_result = None
|
|
2610
|
-
|
|
2611
|
-
if app_check['switched']:
|
|
2612
|
-
# 应用已跳转,说明弹窗去除失败,尝试返回目标应用
|
|
2613
|
-
return_result = self._return_to_target_app()
|
|
2614
|
-
|
|
2615
2308
|
# 尝试后截图,让 AI 判断是否成功
|
|
2616
2309
|
screenshot_result = self.take_screenshot("尝试关闭后")
|
|
2617
|
-
|
|
2618
|
-
msg = f"✅ 已尝试点击常见关闭按钮位置"
|
|
2619
|
-
if app_check['switched']:
|
|
2620
|
-
msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
|
|
2621
|
-
if return_result:
|
|
2622
|
-
if return_result['success']:
|
|
2623
|
-
msg += f"\n{return_result['message']}"
|
|
2624
|
-
else:
|
|
2625
|
-
msg += f"\n❌ 自动返回失败: {return_result['message']}"
|
|
2626
|
-
|
|
2627
2310
|
return {
|
|
2628
2311
|
"success": True,
|
|
2629
|
-
"message":
|
|
2312
|
+
"message": f"✅ 已尝试点击常见关闭按钮位置",
|
|
2630
2313
|
"tried_positions": [p[2] for p in try_positions],
|
|
2631
2314
|
"screenshot": screenshot_result.get("screenshot_path", ""),
|
|
2632
|
-
"app_check": app_check,
|
|
2633
|
-
"return_to_app": return_result,
|
|
2634
2315
|
"tip": "请查看截图确认弹窗是否已关闭。如果还在,可手动分析截图找到关闭按钮位置。"
|
|
2635
2316
|
}
|
|
2636
2317
|
|
|
@@ -2663,14 +2344,6 @@ class BasicMobileToolsLite:
|
|
|
2663
2344
|
self.client.u2.click(best['center_x'], best['center_y'])
|
|
2664
2345
|
time.sleep(0.5)
|
|
2665
2346
|
|
|
2666
|
-
# 🎯 关键步骤:检查应用是否跳转,如果跳转说明弹窗去除失败,需要返回目标应用
|
|
2667
|
-
app_check = self._check_app_switched()
|
|
2668
|
-
return_result = None
|
|
2669
|
-
|
|
2670
|
-
if app_check['switched']:
|
|
2671
|
-
# 应用已跳转,说明弹窗去除失败,尝试返回目标应用
|
|
2672
|
-
return_result = self._return_to_target_app()
|
|
2673
|
-
|
|
2674
2347
|
# 点击后截图,让 AI 判断是否成功
|
|
2675
2348
|
screenshot_result = self.take_screenshot("关闭弹窗后")
|
|
2676
2349
|
|
|
@@ -2686,21 +2359,11 @@ class BasicMobileToolsLite:
|
|
|
2686
2359
|
ref=f"close_popup_{best['position']}"
|
|
2687
2360
|
)
|
|
2688
2361
|
|
|
2689
|
-
# 构建返回消息
|
|
2690
|
-
msg = f"✅ 已点击关闭按钮 ({best['position']}): ({best['center_x']}, {best['center_y']})"
|
|
2691
|
-
if app_check['switched']:
|
|
2692
|
-
msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
|
|
2693
|
-
if return_result:
|
|
2694
|
-
if return_result['success']:
|
|
2695
|
-
msg += f"\n{return_result['message']}"
|
|
2696
|
-
else:
|
|
2697
|
-
msg += f"\n❌ 自动返回失败: {return_result['message']}"
|
|
2698
|
-
|
|
2699
2362
|
# 返回候选按钮列表,让 AI 看截图判断
|
|
2700
2363
|
# 如果弹窗还在,AI 可以选择点击其他候选按钮
|
|
2701
2364
|
return {
|
|
2702
2365
|
"success": True,
|
|
2703
|
-
"message":
|
|
2366
|
+
"message": f"✅ 已点击关闭按钮 ({best['position']}): ({best['center_x']}, {best['center_y']})",
|
|
2704
2367
|
"clicked": {
|
|
2705
2368
|
"position": best['position'],
|
|
2706
2369
|
"match_type": best['match_type'],
|
|
@@ -2710,8 +2373,6 @@ class BasicMobileToolsLite:
|
|
|
2710
2373
|
"screenshot": screenshot_result.get("screenshot_path", ""),
|
|
2711
2374
|
"popup_detected": popup_bounds is not None,
|
|
2712
2375
|
"popup_bounds": f"[{popup_bounds[0]},{popup_bounds[1]}][{popup_bounds[2]},{popup_bounds[3]}]" if popup_bounds else None,
|
|
2713
|
-
"app_check": app_check,
|
|
2714
|
-
"return_to_app": return_result,
|
|
2715
2376
|
"other_candidates": [
|
|
2716
2377
|
{
|
|
2717
2378
|
"position": c['position'],
|
|
@@ -2721,7 +2382,7 @@ class BasicMobileToolsLite:
|
|
|
2721
2382
|
}
|
|
2722
2383
|
for c in close_candidates[1:4] # 返回其他3个候选,AI 可以选择
|
|
2723
2384
|
],
|
|
2724
|
-
"tip": "请查看截图判断弹窗是否已关闭。如果弹窗还在,可以尝试点击 other_candidates
|
|
2385
|
+
"tip": "请查看截图判断弹窗是否已关闭。如果弹窗还在,可以尝试点击 other_candidates 中的其他位置;如果误点跳转了,请按返回键"
|
|
2725
2386
|
}
|
|
2726
2387
|
|
|
2727
2388
|
except Exception as e:
|
|
@@ -3286,8 +2947,8 @@ class BasicMobileToolsLite:
|
|
|
3286
2947
|
try:
|
|
3287
2948
|
import xml.etree.ElementTree as ET
|
|
3288
2949
|
|
|
3289
|
-
# ========== 第1
|
|
3290
|
-
xml_string = self.
|
|
2950
|
+
# ========== 第1步:控件树查找关闭按钮(使用完整 UI 层级)==========
|
|
2951
|
+
xml_string = self._get_full_hierarchy()
|
|
3291
2952
|
root = ET.fromstring(xml_string)
|
|
3292
2953
|
|
|
3293
2954
|
# 关闭按钮的常见特征
|
|
@@ -3325,6 +2986,16 @@ class BasicMobileToolsLite:
|
|
|
3325
2986
|
reason = f"文本含'{kw}'"
|
|
3326
2987
|
break
|
|
3327
2988
|
|
|
2989
|
+
# resource-id 匹配(如 close_icon, ad_close 等)
|
|
2990
|
+
if resource_id:
|
|
2991
|
+
res_id_lower = resource_id.lower()
|
|
2992
|
+
for kw in ['close', 'dismiss', 'skip', 'cancel']:
|
|
2993
|
+
if kw in res_id_lower:
|
|
2994
|
+
score += 9
|
|
2995
|
+
short_id = resource_id.split('/')[-1] if '/' in resource_id else resource_id
|
|
2996
|
+
reason = f"resource-id='{short_id}'"
|
|
2997
|
+
break
|
|
2998
|
+
|
|
3328
2999
|
# content-desc 匹配
|
|
3329
3000
|
for kw in close_content_desc:
|
|
3330
3001
|
if kw.lower() in content_desc.lower():
|
|
@@ -3374,35 +3045,15 @@ class BasicMobileToolsLite:
|
|
|
3374
3045
|
pre_result = self.take_screenshot(description="关闭前", compress=False)
|
|
3375
3046
|
pre_screenshot = pre_result.get("screenshot_path")
|
|
3376
3047
|
|
|
3377
|
-
#
|
|
3378
|
-
|
|
3048
|
+
# 点击
|
|
3049
|
+
self.click_at_coords(cx, cy)
|
|
3379
3050
|
time.sleep(0.5)
|
|
3380
3051
|
|
|
3381
|
-
# 🎯 再次检查应用状态(确保弹窗去除没有导致应用跳转)
|
|
3382
|
-
app_check = self._check_app_switched()
|
|
3383
|
-
return_result = None
|
|
3384
|
-
|
|
3385
|
-
if app_check['switched']:
|
|
3386
|
-
# 应用已跳转,说明弹窗去除失败,尝试返回目标应用
|
|
3387
|
-
return_result = self._return_to_target_app()
|
|
3388
|
-
|
|
3389
3052
|
result["success"] = True
|
|
3390
3053
|
result["method"] = "控件树"
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
if app_check['switched']:
|
|
3396
|
-
msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
|
|
3397
|
-
if return_result:
|
|
3398
|
-
if return_result['success']:
|
|
3399
|
-
msg += f"\n{return_result['message']}"
|
|
3400
|
-
else:
|
|
3401
|
-
msg += f"\n❌ 自动返回失败: {return_result['message']}"
|
|
3402
|
-
|
|
3403
|
-
result["message"] = msg
|
|
3404
|
-
result["app_check"] = app_check
|
|
3405
|
-
result["return_to_app"] = return_result
|
|
3054
|
+
result["message"] = f"✅ 通过控件树找到关闭按钮并点击\n" \
|
|
3055
|
+
f" 位置: ({cx}, {cy})\n" \
|
|
3056
|
+
f" 原因: {best['reason']}"
|
|
3406
3057
|
|
|
3407
3058
|
# 自动学习:检查这个 X 是否已在模板库,不在就添加
|
|
3408
3059
|
if auto_learn and pre_screenshot:
|
|
@@ -3432,36 +3083,16 @@ class BasicMobileToolsLite:
|
|
|
3432
3083
|
x_pct = best["percent"]["x"]
|
|
3433
3084
|
y_pct = best["percent"]["y"]
|
|
3434
3085
|
|
|
3435
|
-
#
|
|
3436
|
-
|
|
3086
|
+
# 点击
|
|
3087
|
+
self.click_by_percent(x_pct, y_pct)
|
|
3437
3088
|
time.sleep(0.5)
|
|
3438
3089
|
|
|
3439
|
-
# 🎯 再次检查应用状态(确保弹窗去除没有导致应用跳转)
|
|
3440
|
-
app_check = self._check_app_switched()
|
|
3441
|
-
return_result = None
|
|
3442
|
-
|
|
3443
|
-
if app_check['switched']:
|
|
3444
|
-
# 应用已跳转,说明弹窗去除失败,尝试返回目标应用
|
|
3445
|
-
return_result = self._return_to_target_app()
|
|
3446
|
-
|
|
3447
3090
|
result["success"] = True
|
|
3448
3091
|
result["method"] = "模板匹配"
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
if app_check['switched']:
|
|
3455
|
-
msg += f"\n⚠️ 应用已跳转,说明弹窗去除失败"
|
|
3456
|
-
if return_result:
|
|
3457
|
-
if return_result['success']:
|
|
3458
|
-
msg += f"\n{return_result['message']}"
|
|
3459
|
-
else:
|
|
3460
|
-
msg += f"\n❌ 自动返回失败: {return_result['message']}"
|
|
3461
|
-
|
|
3462
|
-
result["message"] = msg
|
|
3463
|
-
result["app_check"] = app_check
|
|
3464
|
-
result["return_to_app"] = return_result
|
|
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}%)"
|
|
3465
3096
|
return result
|
|
3466
3097
|
|
|
3467
3098
|
except ImportError:
|
|
@@ -610,22 +610,25 @@ class MobileMCPServer:
|
|
|
610
610
|
name="mobile_find_close_button",
|
|
611
611
|
description="""🔍 智能查找关闭按钮(只找不点,返回位置)
|
|
612
612
|
|
|
613
|
-
|
|
613
|
+
⚡ 【推荐首选】遇到弹窗时优先调用此工具!无需先截图。
|
|
614
|
+
|
|
615
|
+
从元素树中找最可能的关闭按钮,返回坐标和推荐的点击命令。
|
|
614
616
|
|
|
615
617
|
🎯 识别策略(优先级):
|
|
616
|
-
1. 文本匹配:×、X、关闭、取消、跳过
|
|
617
|
-
2.
|
|
618
|
-
3.
|
|
618
|
+
1. 文本匹配:×、X、关闭、取消、跳过 等(得分100)
|
|
619
|
+
2. resource-id 匹配:包含 close/dismiss/skip(得分95)
|
|
620
|
+
3. content-desc 匹配:包含 close/关闭(得分90)
|
|
621
|
+
4. 小尺寸 clickable 元素(右上角优先,得分70+)
|
|
619
622
|
|
|
620
623
|
✅ 返回内容:
|
|
621
624
|
- 坐标 (x, y) 和百分比 (x%, y%)
|
|
622
|
-
-
|
|
623
|
-
-
|
|
625
|
+
- resource-id(如果有)
|
|
626
|
+
- 推荐的点击命令(优先 click_by_id,其次 click_by_text,最后 click_by_percent)
|
|
624
627
|
|
|
625
628
|
💡 使用流程:
|
|
626
|
-
1.
|
|
627
|
-
2.
|
|
628
|
-
3.
|
|
629
|
+
1. 直接调用此工具(无需先截图/列元素)
|
|
630
|
+
2. 根据返回的 click_command 执行点击
|
|
631
|
+
3. 如果返回 success=false,才需要截图分析""",
|
|
629
632
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
630
633
|
))
|
|
631
634
|
|
|
@@ -711,24 +714,26 @@ class MobileMCPServer:
|
|
|
711
714
|
name="mobile_close_ad",
|
|
712
715
|
description="""🚫 【推荐】智能关闭广告弹窗
|
|
713
716
|
|
|
714
|
-
|
|
717
|
+
⚡ 直接调用即可,无需先截图!会自动按优先级尝试:
|
|
715
718
|
|
|
716
|
-
1️⃣
|
|
717
|
-
- 自动查找
|
|
719
|
+
1️⃣ **控件树查找**(最可靠,优先)
|
|
720
|
+
- 自动查找 resource-id 包含 close/dismiss
|
|
721
|
+
- 查找文本"关闭"、"跳过"、"×"等
|
|
718
722
|
- 找到直接点击,实时可靠
|
|
719
723
|
|
|
720
724
|
2️⃣ **模板匹配**(次优)
|
|
721
725
|
- 用 OpenCV 匹配已保存的 X 按钮模板
|
|
722
|
-
-
|
|
726
|
+
- 模板越多成功率越高
|
|
723
727
|
|
|
724
728
|
3️⃣ **返回截图供 AI 分析**(兜底)
|
|
725
|
-
-
|
|
729
|
+
- 前两步都失败才截图
|
|
726
730
|
- AI 分析后用 mobile_click_by_percent 点击
|
|
727
|
-
- 点击成功后用 mobile_template_add
|
|
731
|
+
- 点击成功后用 mobile_template_add 添加模板
|
|
728
732
|
|
|
729
|
-
💡
|
|
730
|
-
1. 遇到广告弹窗 →
|
|
733
|
+
💡 正确流程:
|
|
734
|
+
1. 遇到广告弹窗 → 直接调用此工具
|
|
731
735
|
2. 如果成功 → 完成
|
|
736
|
+
3. 只有失败时才需要截图分析
|
|
732
737
|
3. 如果失败 → 看截图找 X → 点击 → 添加模板""",
|
|
733
738
|
inputSchema={
|
|
734
739
|
"type": "object",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
|
|
2
2
|
mobile_mcp/config.py,sha256=yaFLAV4bc2wX0GQPtZDo7OYF9E88tXV-av41fQsJwK4,4480
|
|
3
3
|
mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
|
|
4
|
-
mobile_mcp/core/basic_tools_lite.py,sha256=
|
|
4
|
+
mobile_mcp/core/basic_tools_lite.py,sha256=kVmfiaNyO33SFf9ijvK9tO4ltPOI4A8D1ANho6DqNNI,156767
|
|
5
5
|
mobile_mcp/core/device_manager.py,sha256=xG5DoeNFs45pl-FTEhEWblqVwxtFK-FmVEGlNL6EqRI,8798
|
|
6
6
|
mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
|
|
7
7
|
mobile_mcp/core/ios_client_wda.py,sha256=Nq9WxevhTWpVpolM-Ymp-b0nUQV3tXLFszmJHbDC4wA,18770
|
|
@@ -19,14 +19,14 @@ mobile_mcp/core/utils/logger.py,sha256=XXQAHUwT1jc70pq_tYFmL6f_nKrFlYm3hcgl-5RYR
|
|
|
19
19
|
mobile_mcp/core/utils/operation_history_manager.py,sha256=gi8S8HJAMqvkUrY7_-kVbko3Xt7c4GAUziEujRd-N-Y,4792
|
|
20
20
|
mobile_mcp/core/utils/smart_wait.py,sha256=N5wKTUYrNWPruBILqrAjpvtso8Z3GRWCfMIR_aZxPLg,8649
|
|
21
21
|
mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
|
|
22
|
-
mobile_mcp/mcp_tools/mcp_server.py,sha256=
|
|
22
|
+
mobile_mcp/mcp_tools/mcp_server.py,sha256=T6H3jAEfxQzGeQgiTg9ROn2GpgonARrrlFWrzVfxmKU,50135
|
|
23
23
|
mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,158
|
|
24
24
|
mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
|
|
25
25
|
mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
|
|
26
26
|
mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
|
|
27
|
-
mobile_mcp_ai-2.5.
|
|
28
|
-
mobile_mcp_ai-2.5.
|
|
29
|
-
mobile_mcp_ai-2.5.
|
|
30
|
-
mobile_mcp_ai-2.5.
|
|
31
|
-
mobile_mcp_ai-2.5.
|
|
32
|
-
mobile_mcp_ai-2.5.
|
|
27
|
+
mobile_mcp_ai-2.5.11.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
28
|
+
mobile_mcp_ai-2.5.11.dist-info/METADATA,sha256=0iJCScP_ZkC5ZBZFTJ7ZPJJr4N0h9gwuTanhc81CIkM,10496
|
|
29
|
+
mobile_mcp_ai-2.5.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
30
|
+
mobile_mcp_ai-2.5.11.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
|
|
31
|
+
mobile_mcp_ai-2.5.11.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
|
|
32
|
+
mobile_mcp_ai-2.5.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|