mobile-mcp-ai 2.1.2__py3-none-any.whl → 2.5.8__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/__init__.py +34 -0
- mobile_mcp/config.py +142 -0
- mobile_mcp/core/basic_tools_lite.py +3266 -0
- {core → mobile_mcp/core}/device_manager.py +2 -2
- mobile_mcp/core/dynamic_config.py +272 -0
- mobile_mcp/core/ios_client_wda.py +569 -0
- mobile_mcp/core/ios_device_manager_wda.py +306 -0
- {core → mobile_mcp/core}/mobile_client.py +279 -39
- mobile_mcp/core/template_matcher.py +429 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
- mobile_mcp/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {core → mobile_mcp/core}/utils/smart_wait.py +3 -3
- mobile_mcp/mcp_tools/__init__.py +10 -0
- mobile_mcp/mcp_tools/mcp_server.py +1071 -0
- mobile_mcp_ai-2.5.8.dist-info/METADATA +469 -0
- mobile_mcp_ai-2.5.8.dist-info/RECORD +32 -0
- mobile_mcp_ai-2.5.8.dist-info/entry_points.txt +2 -0
- mobile_mcp_ai-2.5.8.dist-info/licenses/LICENSE +201 -0
- mobile_mcp_ai-2.5.8.dist-info/top_level.txt +1 -0
- core/ai/__init__.py +0 -11
- core/ai/ai_analyzer.py +0 -197
- core/ai/ai_config.py +0 -116
- core/ai/ai_platform_adapter.py +0 -399
- core/ai/smart_test_executor.py +0 -520
- core/ai/test_generator.py +0 -365
- core/ai/test_generator_from_history.py +0 -391
- core/ai/test_generator_standalone.py +0 -293
- core/assertion/__init__.py +0 -9
- core/assertion/smart_assertion.py +0 -341
- core/basic_tools.py +0 -377
- core/h5/__init__.py +0 -10
- core/h5/h5_handler.py +0 -548
- core/ios_client.py +0 -219
- core/ios_device_manager.py +0 -252
- core/locator/__init__.py +0 -10
- core/locator/cursor_ai_auto_analyzer.py +0 -119
- core/locator/cursor_vision_helper.py +0 -414
- core/locator/mobile_smart_locator.py +0 -1640
- core/locator/position_analyzer.py +0 -813
- core/locator/script_updater.py +0 -157
- core/nl_test_runner.py +0 -585
- core/smart_app_launcher.py +0 -334
- core/smart_tools.py +0 -311
- mcp/__init__.py +0 -8
- mcp/mcp_server.py +0 -1919
- mcp/mcp_server_simple.py +0 -476
- mobile_mcp_ai-2.1.2.dist-info/METADATA +0 -567
- mobile_mcp_ai-2.1.2.dist-info/RECORD +0 -45
- mobile_mcp_ai-2.1.2.dist-info/entry_points.txt +0 -2
- mobile_mcp_ai-2.1.2.dist-info/top_level.txt +0 -4
- vision/__init__.py +0 -10
- vision/vision_locator.py +0 -404
- {core → mobile_mcp/core}/__init__.py +0 -0
- {core → mobile_mcp/core}/utils/__init__.py +0 -0
- {core → mobile_mcp/core}/utils/logger.py +0 -0
- {core → mobile_mcp/core}/utils/operation_history_manager.py +0 -0
- {utils → mobile_mcp/utils}/__init__.py +0 -0
- {utils → mobile_mcp/utils}/logger.py +0 -0
- {utils → mobile_mcp/utils}/xml_formatter.py +0 -0
- {utils → mobile_mcp/utils}/xml_parser.py +0 -0
- {mobile_mcp_ai-2.1.2.dist-info → mobile_mcp_ai-2.5.8.dist-info}/WHEEL +0 -0
core/assertion/__init__.py
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
智能断言系统 - XML分析 + AI视觉识别
|
|
5
|
-
|
|
6
|
-
策略:
|
|
7
|
-
1. 优先XML分析(快速+免费)
|
|
8
|
-
2. 失败时降级到AI视觉识别(智能+付费)
|
|
9
|
-
3. 支持多种断言类型
|
|
10
|
-
"""
|
|
11
|
-
from typing import Optional, Dict, Any
|
|
12
|
-
import time
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class SmartAssertion:
|
|
16
|
-
"""智能断言系统"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, mobile_client):
|
|
19
|
-
"""
|
|
20
|
-
初始化智能断言系统
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
mobile_client: MobileClient实例
|
|
24
|
-
"""
|
|
25
|
-
self.mobile_client = mobile_client
|
|
26
|
-
|
|
27
|
-
# 统计
|
|
28
|
-
self.stats = {
|
|
29
|
-
'total': 0,
|
|
30
|
-
'xml_success': 0,
|
|
31
|
-
'ai_success': 0,
|
|
32
|
-
'failed': 0,
|
|
33
|
-
'total_time': 0.0,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async def assert_text_exists(self, text: str, timeout: float = 5.0) -> bool:
|
|
37
|
-
"""
|
|
38
|
-
断言:文本存在
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
text: 要查找的文本
|
|
42
|
-
timeout: 超时时间(秒)
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
True: 找到文本
|
|
46
|
-
False: 未找到文本
|
|
47
|
-
"""
|
|
48
|
-
start_time = time.time()
|
|
49
|
-
self.stats['total'] += 1
|
|
50
|
-
|
|
51
|
-
print(f"\n🔍 断言:文本存在 - '{text}'")
|
|
52
|
-
|
|
53
|
-
# Level 1: XML文本查找(快速+免费)
|
|
54
|
-
print(f" 📋 Level 1: XML文本查找...")
|
|
55
|
-
xml_result = await self._xml_text_search(text, timeout)
|
|
56
|
-
|
|
57
|
-
if xml_result:
|
|
58
|
-
self.stats['xml_success'] += 1
|
|
59
|
-
elapsed = (time.time() - start_time) * 1000
|
|
60
|
-
self.stats['total_time'] += elapsed
|
|
61
|
-
print(f" ✅ XML查找成功!耗时: {elapsed:.2f}ms")
|
|
62
|
-
return True
|
|
63
|
-
|
|
64
|
-
# Level 2: AI视觉识别(智能+付费)
|
|
65
|
-
print(f" 🤖 Level 2: AI视觉识别...")
|
|
66
|
-
ai_result = await self._ai_visual_search(text)
|
|
67
|
-
|
|
68
|
-
if ai_result:
|
|
69
|
-
self.stats['ai_success'] += 1
|
|
70
|
-
elapsed = (time.time() - start_time) * 1000
|
|
71
|
-
self.stats['total_time'] += elapsed
|
|
72
|
-
print(f" ✅ AI识别成功!耗时: {elapsed:.2f}ms")
|
|
73
|
-
return True
|
|
74
|
-
|
|
75
|
-
# 断言失败
|
|
76
|
-
self.stats['failed'] += 1
|
|
77
|
-
elapsed = (time.time() - start_time) * 1000
|
|
78
|
-
self.stats['total_time'] += elapsed
|
|
79
|
-
print(f" ❌ 断言失败:未找到文本 '{text}',耗时: {elapsed:.2f}ms")
|
|
80
|
-
return False
|
|
81
|
-
|
|
82
|
-
async def assert_element_exists(self, query: str, timeout: float = 5.0) -> bool:
|
|
83
|
-
"""
|
|
84
|
-
断言:元素存在
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
query: 元素查询(自然语言)
|
|
88
|
-
timeout: 超时时间(秒)
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
True: 找到元素
|
|
92
|
-
False: 未找到元素
|
|
93
|
-
"""
|
|
94
|
-
start_time = time.time()
|
|
95
|
-
self.stats['total'] += 1
|
|
96
|
-
|
|
97
|
-
print(f"\n🔍 断言:元素存在 - '{query}'")
|
|
98
|
-
|
|
99
|
-
# 使用SmartLocator定位元素
|
|
100
|
-
try:
|
|
101
|
-
from ..locator.mobile_smart_locator import MobileSmartLocator
|
|
102
|
-
|
|
103
|
-
locator = MobileSmartLocator(self.mobile_client)
|
|
104
|
-
result = await locator.locate(query)
|
|
105
|
-
|
|
106
|
-
if result:
|
|
107
|
-
self.stats['xml_success'] += 1 # 简化统计,实际可能是AI
|
|
108
|
-
elapsed = (time.time() - start_time) * 1000
|
|
109
|
-
self.stats['total_time'] += elapsed
|
|
110
|
-
print(f" ✅ 元素存在!耗时: {elapsed:.2f}ms")
|
|
111
|
-
return True
|
|
112
|
-
else:
|
|
113
|
-
self.stats['failed'] += 1
|
|
114
|
-
elapsed = (time.time() - start_time) * 1000
|
|
115
|
-
self.stats['total_time'] += elapsed
|
|
116
|
-
print(f" ❌ 断言失败:未找到元素 '{query}',耗时: {elapsed:.2f}ms")
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
except Exception as e:
|
|
120
|
-
self.stats['failed'] += 1
|
|
121
|
-
elapsed = (time.time() - start_time) * 1000
|
|
122
|
-
self.stats['total_time'] += elapsed
|
|
123
|
-
print(f" ❌ 断言异常: {e},耗时: {elapsed:.2f}ms")
|
|
124
|
-
return False
|
|
125
|
-
|
|
126
|
-
async def assert_visual_exists(self, description: str) -> bool:
|
|
127
|
-
"""
|
|
128
|
-
断言:视觉元素存在(纯AI识别)
|
|
129
|
-
|
|
130
|
-
适用场景:
|
|
131
|
-
- 图标、图片
|
|
132
|
-
- 视觉状态(如"选中"、"高亮")
|
|
133
|
-
- 布局检查(如"底部有4个图标")
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
description: 视觉描述
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
True: 找到元素
|
|
140
|
-
False: 未找到元素
|
|
141
|
-
"""
|
|
142
|
-
start_time = time.time()
|
|
143
|
-
self.stats['total'] += 1
|
|
144
|
-
|
|
145
|
-
print(f"\n🔍 断言:视觉元素存在 - '{description}'")
|
|
146
|
-
|
|
147
|
-
# 直接使用AI视觉识别
|
|
148
|
-
ai_result = await self._ai_visual_search(description)
|
|
149
|
-
|
|
150
|
-
if ai_result:
|
|
151
|
-
self.stats['ai_success'] += 1
|
|
152
|
-
elapsed = (time.time() - start_time) * 1000
|
|
153
|
-
self.stats['total_time'] += elapsed
|
|
154
|
-
print(f" ✅ AI识别成功!耗时: {elapsed:.2f}ms")
|
|
155
|
-
return True
|
|
156
|
-
else:
|
|
157
|
-
self.stats['failed'] += 1
|
|
158
|
-
elapsed = (time.time() - start_time) * 1000
|
|
159
|
-
self.stats['total_time'] += elapsed
|
|
160
|
-
print(f" ❌ 断言失败:未找到视觉元素 '{description}',耗时: {elapsed:.2f}ms")
|
|
161
|
-
return False
|
|
162
|
-
|
|
163
|
-
async def assert_element_enabled(self, query: str) -> bool:
|
|
164
|
-
"""
|
|
165
|
-
断言:元素可用(enabled=true)
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
query: 元素查询
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
True: 元素可用
|
|
172
|
-
False: 元素不可用或不存在
|
|
173
|
-
"""
|
|
174
|
-
start_time = time.time()
|
|
175
|
-
self.stats['total'] += 1
|
|
176
|
-
|
|
177
|
-
print(f"\n🔍 断言:元素可用 - '{query}'")
|
|
178
|
-
|
|
179
|
-
# 读取XML
|
|
180
|
-
xml_string = self.mobile_client.u2.dump_hierarchy()
|
|
181
|
-
elements = self.mobile_client.xml_parser.parse(xml_string)
|
|
182
|
-
|
|
183
|
-
# 查找元素
|
|
184
|
-
query_lower = query.lower()
|
|
185
|
-
for elem in elements:
|
|
186
|
-
text = elem.get('text', '').lower()
|
|
187
|
-
desc = elem.get('content_desc', '').lower()
|
|
188
|
-
|
|
189
|
-
if query_lower in text or query_lower in desc:
|
|
190
|
-
enabled = elem.get('enabled', False)
|
|
191
|
-
|
|
192
|
-
if enabled:
|
|
193
|
-
self.stats['xml_success'] += 1
|
|
194
|
-
elapsed = (time.time() - start_time) * 1000
|
|
195
|
-
self.stats['total_time'] += elapsed
|
|
196
|
-
print(f" ✅ 元素可用!耗时: {elapsed:.2f}ms")
|
|
197
|
-
return True
|
|
198
|
-
else:
|
|
199
|
-
self.stats['failed'] += 1
|
|
200
|
-
elapsed = (time.time() - start_time) * 1000
|
|
201
|
-
self.stats['total_time'] += elapsed
|
|
202
|
-
print(f" ❌ 断言失败:元素不可用,耗时: {elapsed:.2f}ms")
|
|
203
|
-
return False
|
|
204
|
-
|
|
205
|
-
# 未找到元素
|
|
206
|
-
self.stats['failed'] += 1
|
|
207
|
-
elapsed = (time.time() - start_time) * 1000
|
|
208
|
-
self.stats['total_time'] += elapsed
|
|
209
|
-
print(f" ❌ 断言失败:未找到元素 '{query}',耗时: {elapsed:.2f}ms")
|
|
210
|
-
return False
|
|
211
|
-
|
|
212
|
-
async def assert_element_count(self, query: str, expected_count: int) -> bool:
|
|
213
|
-
"""
|
|
214
|
-
断言:元素数量
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
query: 元素查询
|
|
218
|
-
expected_count: 期望数量
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
True: 数量匹配
|
|
222
|
-
False: 数量不匹配
|
|
223
|
-
"""
|
|
224
|
-
start_time = time.time()
|
|
225
|
-
self.stats['total'] += 1
|
|
226
|
-
|
|
227
|
-
print(f"\n🔍 断言:元素数量 - '{query}' (期望: {expected_count})")
|
|
228
|
-
|
|
229
|
-
# 读取XML
|
|
230
|
-
xml_string = self.mobile_client.u2.dump_hierarchy()
|
|
231
|
-
elements = self.mobile_client.xml_parser.parse(xml_string)
|
|
232
|
-
|
|
233
|
-
# 查找所有匹配元素
|
|
234
|
-
query_lower = query.lower()
|
|
235
|
-
matched = []
|
|
236
|
-
|
|
237
|
-
for elem in elements:
|
|
238
|
-
text = elem.get('text', '').lower()
|
|
239
|
-
desc = elem.get('content_desc', '').lower()
|
|
240
|
-
|
|
241
|
-
if query_lower in text or query_lower in desc:
|
|
242
|
-
matched.append(elem)
|
|
243
|
-
|
|
244
|
-
actual_count = len(matched)
|
|
245
|
-
|
|
246
|
-
if actual_count == expected_count:
|
|
247
|
-
self.stats['xml_success'] += 1
|
|
248
|
-
elapsed = (time.time() - start_time) * 1000
|
|
249
|
-
self.stats['total_time'] += elapsed
|
|
250
|
-
print(f" ✅ 数量匹配!实际: {actual_count},耗时: {elapsed:.2f}ms")
|
|
251
|
-
return True
|
|
252
|
-
else:
|
|
253
|
-
self.stats['failed'] += 1
|
|
254
|
-
elapsed = (time.time() - start_time) * 1000
|
|
255
|
-
self.stats['total_time'] += elapsed
|
|
256
|
-
print(f" ❌ 断言失败:数量不匹配!期望: {expected_count},实际: {actual_count},耗时: {elapsed:.2f}ms")
|
|
257
|
-
return False
|
|
258
|
-
|
|
259
|
-
# ========================================
|
|
260
|
-
# 内部方法
|
|
261
|
-
# ========================================
|
|
262
|
-
|
|
263
|
-
async def _xml_text_search(self, text: str, timeout: float) -> bool:
|
|
264
|
-
"""
|
|
265
|
-
XML文本查找
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
text: 要查找的文本
|
|
269
|
-
timeout: 超时时间(秒)
|
|
270
|
-
|
|
271
|
-
Returns:
|
|
272
|
-
True: 找到文本
|
|
273
|
-
False: 未找到文本
|
|
274
|
-
"""
|
|
275
|
-
start_time = time.time()
|
|
276
|
-
text_lower = text.lower()
|
|
277
|
-
|
|
278
|
-
while time.time() - start_time < timeout:
|
|
279
|
-
# 读取XML
|
|
280
|
-
xml_string = self.mobile_client.u2.dump_hierarchy()
|
|
281
|
-
elements = self.mobile_client.xml_parser.parse(xml_string)
|
|
282
|
-
|
|
283
|
-
# 查找文本
|
|
284
|
-
for elem in elements:
|
|
285
|
-
elem_text = elem.get('text', '').lower()
|
|
286
|
-
elem_desc = elem.get('content_desc', '').lower()
|
|
287
|
-
|
|
288
|
-
if text_lower in elem_text or text_lower in elem_desc:
|
|
289
|
-
print(f" ✅ 找到文本: {elem.get('text') or elem.get('content_desc')}")
|
|
290
|
-
return True
|
|
291
|
-
|
|
292
|
-
# 未找到,等待100ms后重试
|
|
293
|
-
await self.mobile_client.wait(0.1)
|
|
294
|
-
|
|
295
|
-
print(f" ❌ 超时未找到文本")
|
|
296
|
-
return False
|
|
297
|
-
|
|
298
|
-
async def _ai_visual_search(self, description: str) -> bool:
|
|
299
|
-
"""
|
|
300
|
-
AI视觉识别
|
|
301
|
-
|
|
302
|
-
Args:
|
|
303
|
-
description: 视觉描述
|
|
304
|
-
|
|
305
|
-
Returns:
|
|
306
|
-
True: 找到元素
|
|
307
|
-
False: 未找到元素
|
|
308
|
-
"""
|
|
309
|
-
try:
|
|
310
|
-
from ...vision.vision_locator import MobileVisionLocator
|
|
311
|
-
|
|
312
|
-
vision_locator = MobileVisionLocator(self.mobile_client)
|
|
313
|
-
result = await vision_locator.locate_element_by_vision(description)
|
|
314
|
-
|
|
315
|
-
if result and result.get('found'):
|
|
316
|
-
print(f" ✅ AI识别成功: {description}")
|
|
317
|
-
return True
|
|
318
|
-
else:
|
|
319
|
-
print(f" ❌ AI未识别到: {description}")
|
|
320
|
-
return False
|
|
321
|
-
|
|
322
|
-
except ImportError:
|
|
323
|
-
print(f" ⚠️ 视觉识别模块未安装")
|
|
324
|
-
return False
|
|
325
|
-
except Exception as e:
|
|
326
|
-
print(f" ⚠️ AI识别失败: {e}")
|
|
327
|
-
return False
|
|
328
|
-
|
|
329
|
-
def print_stats(self):
|
|
330
|
-
"""打印统计信息"""
|
|
331
|
-
print("\n" + "=" * 80)
|
|
332
|
-
print("📊 断言统计")
|
|
333
|
-
print("=" * 80)
|
|
334
|
-
print(f" 总断言次数: {self.stats['total']}")
|
|
335
|
-
print(f" XML成功: {self.stats['xml_success']} ({self.stats['xml_success']/max(1, self.stats['total'])*100:.1f}%)")
|
|
336
|
-
print(f" AI成功: {self.stats['ai_success']} ({self.stats['ai_success']/max(1, self.stats['total'])*100:.1f}%)")
|
|
337
|
-
print(f" 失败: {self.stats['failed']} ({self.stats['failed']/max(1, self.stats['total'])*100:.1f}%)")
|
|
338
|
-
print(f" 总耗时: {self.stats['total_time']:.2f}ms")
|
|
339
|
-
print(f" 平均耗时: {self.stats['total_time']/max(1, self.stats['total']):.2f}ms")
|
|
340
|
-
print("=" * 80)
|
|
341
|
-
|