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
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
iOS设备连接管理 - 使用 tidevice + facebook-wda
|
|
5
|
+
|
|
6
|
+
优势:
|
|
7
|
+
1. API风格和 uiautomator2 几乎一样
|
|
8
|
+
2. 不需要启动 Appium Server
|
|
9
|
+
3. tidevice 简化设备管理
|
|
10
|
+
|
|
11
|
+
前置条件:
|
|
12
|
+
1. 安装 tidevice: pip install tidevice
|
|
13
|
+
2. 安装 facebook-wda: pip install facebook-wda
|
|
14
|
+
3. 首次需要用 Xcode 编译 WebDriverAgent 到设备上
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
manager = IOSDeviceManagerWDA()
|
|
18
|
+
devices = manager.list_devices()
|
|
19
|
+
client = manager.connect(device_id="xxx")
|
|
20
|
+
client(text="登录").click() # 和 uiautomator2 风格一样!
|
|
21
|
+
"""
|
|
22
|
+
import sys
|
|
23
|
+
import subprocess
|
|
24
|
+
from typing import List, Optional, Dict
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class IOSDeviceManagerWDA:
|
|
28
|
+
"""
|
|
29
|
+
iOS设备管理器 - 使用 tidevice + facebook-wda
|
|
30
|
+
|
|
31
|
+
用法:
|
|
32
|
+
manager = IOSDeviceManagerWDA()
|
|
33
|
+
devices = manager.list_devices()
|
|
34
|
+
client = manager.connect()
|
|
35
|
+
client(text="登录").click()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
"""初始化iOS设备管理器"""
|
|
40
|
+
self.client = None
|
|
41
|
+
self.current_device_id = None
|
|
42
|
+
self._wda_proxy_process = None
|
|
43
|
+
self._check_dependencies()
|
|
44
|
+
|
|
45
|
+
def _check_dependencies(self):
|
|
46
|
+
"""检查依赖是否安装"""
|
|
47
|
+
try:
|
|
48
|
+
import tidevice
|
|
49
|
+
import wda
|
|
50
|
+
except ImportError as e:
|
|
51
|
+
raise ImportError(
|
|
52
|
+
f"缺少iOS自动化依赖: {e}\n"
|
|
53
|
+
f"请运行以下命令安装:\n"
|
|
54
|
+
f" pip install tidevice facebook-wda\n"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def list_devices(self) -> List[Dict[str, str]]:
|
|
58
|
+
"""
|
|
59
|
+
列出所有连接的iOS设备
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
设备列表,每个设备包含 id, name, type 等信息
|
|
63
|
+
"""
|
|
64
|
+
devices = []
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# 优先使用 tidevice Python API
|
|
68
|
+
try:
|
|
69
|
+
import tidevice
|
|
70
|
+
for d in tidevice.Usbmux().device_list():
|
|
71
|
+
devices.append({
|
|
72
|
+
'id': d.udid,
|
|
73
|
+
'name': d.name if hasattr(d, 'name') else 'iOS Device',
|
|
74
|
+
'type': 'device',
|
|
75
|
+
'state': 'connected'
|
|
76
|
+
})
|
|
77
|
+
if devices:
|
|
78
|
+
return devices
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
# 回退:使用 subprocess 调用 tidevice
|
|
83
|
+
result = subprocess.run(
|
|
84
|
+
[sys.executable, '-m', 'tidevice', 'list', '--json'],
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
timeout=10
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
91
|
+
import json
|
|
92
|
+
try:
|
|
93
|
+
device_list = json.loads(result.stdout)
|
|
94
|
+
for device in device_list:
|
|
95
|
+
devices.append({
|
|
96
|
+
'id': device.get('udid', ''),
|
|
97
|
+
'name': device.get('name', 'iOS Device'),
|
|
98
|
+
'type': 'device',
|
|
99
|
+
'model': device.get('model', 'Unknown'),
|
|
100
|
+
'ios_version': device.get('version', 'Unknown'),
|
|
101
|
+
'state': 'connected'
|
|
102
|
+
})
|
|
103
|
+
except json.JSONDecodeError:
|
|
104
|
+
# 尝试纯文本解析
|
|
105
|
+
for line in result.stdout.strip().split('\n'):
|
|
106
|
+
if line.strip():
|
|
107
|
+
parts = line.split()
|
|
108
|
+
if len(parts) >= 1:
|
|
109
|
+
devices.append({
|
|
110
|
+
'id': parts[0],
|
|
111
|
+
'name': ' '.join(parts[1:]) if len(parts) > 1 else 'iOS Device',
|
|
112
|
+
'type': 'device',
|
|
113
|
+
'state': 'connected'
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
# 如果 tidevice 没有找到设备,尝试使用 xcrun simctl 列出模拟器
|
|
117
|
+
if not devices:
|
|
118
|
+
sim_result = subprocess.run(
|
|
119
|
+
['xcrun', 'simctl', 'list', 'devices', 'booted', '--json'],
|
|
120
|
+
capture_output=True,
|
|
121
|
+
text=True,
|
|
122
|
+
timeout=10
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if sim_result.returncode == 0 and sim_result.stdout.strip():
|
|
126
|
+
import json
|
|
127
|
+
sim_data = json.loads(sim_result.stdout)
|
|
128
|
+
for runtime, sims in sim_data.get('devices', {}).items():
|
|
129
|
+
for sim in sims:
|
|
130
|
+
if sim.get('state') == 'Booted':
|
|
131
|
+
devices.append({
|
|
132
|
+
'id': sim.get('udid', ''),
|
|
133
|
+
'name': sim.get('name', 'Simulator'),
|
|
134
|
+
'type': 'simulator',
|
|
135
|
+
'runtime': runtime,
|
|
136
|
+
'state': 'Booted'
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
return devices
|
|
140
|
+
|
|
141
|
+
except FileNotFoundError:
|
|
142
|
+
print("⚠️ tidevice 未安装,请运行: pip install tidevice", file=sys.stderr)
|
|
143
|
+
return []
|
|
144
|
+
except Exception as e:
|
|
145
|
+
print(f"⚠️ 获取设备列表失败: {e}", file=sys.stderr)
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
def start_wda_proxy(self, device_id: str, port: int = 8100) -> bool:
|
|
149
|
+
"""
|
|
150
|
+
启动 WDA 代理(如果尚未启动)
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
device_id: 设备UDID
|
|
154
|
+
port: WDA代理端口,默认8100
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
是否成功启动
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
import socket
|
|
161
|
+
|
|
162
|
+
# 检查端口是否已被占用(可能WDA已在运行)
|
|
163
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
164
|
+
result = sock.connect_ex(('localhost', port))
|
|
165
|
+
sock.close()
|
|
166
|
+
|
|
167
|
+
if result == 0:
|
|
168
|
+
print(f" ✅ WDA代理已在运行 (端口 {port})", file=sys.stderr)
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
# 启动 WDA 代理
|
|
172
|
+
print(f" 🚀 启动 WDA 代理...", file=sys.stderr)
|
|
173
|
+
|
|
174
|
+
# 使用 tidevice 启动 WDA(后台运行)
|
|
175
|
+
self._wda_proxy_process = subprocess.Popen(
|
|
176
|
+
[sys.executable, '-m', 'tidevice', '-u', device_id, 'wdaproxy', '-B',
|
|
177
|
+
'com.facebook.WebDriverAgentRunner.xctrunner', '--port', str(port)],
|
|
178
|
+
stdout=subprocess.DEVNULL,
|
|
179
|
+
stderr=subprocess.DEVNULL
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# 等待 WDA 启动
|
|
183
|
+
import time
|
|
184
|
+
for i in range(10): # 最多等待10秒
|
|
185
|
+
time.sleep(1)
|
|
186
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
187
|
+
result = sock.connect_ex(('localhost', port))
|
|
188
|
+
sock.close()
|
|
189
|
+
|
|
190
|
+
if result == 0:
|
|
191
|
+
print(f" ✅ WDA代理启动成功 (端口 {port})", file=sys.stderr)
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
print(f" ⏳ 等待WDA启动... ({i+1}/10)", file=sys.stderr)
|
|
195
|
+
|
|
196
|
+
print(f" ❌ WDA代理启动超时", file=sys.stderr)
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
except Exception as e:
|
|
200
|
+
print(f" ❌ 启动WDA代理失败: {e}", file=sys.stderr)
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def connect(self, device_id: Optional[str] = None, port: int = 8100) -> 'wda.Client':
|
|
204
|
+
"""
|
|
205
|
+
连接iOS设备
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
device_id: 设备UDID,None则自动选择第一个设备
|
|
209
|
+
port: WDA代理端口,默认8100
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
wda.Client 对象(API类似 uiautomator2)
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
import wda
|
|
216
|
+
|
|
217
|
+
# 如果没有指定设备ID,自动选择第一个
|
|
218
|
+
if device_id is None:
|
|
219
|
+
devices = self.list_devices()
|
|
220
|
+
if not devices:
|
|
221
|
+
raise RuntimeError(
|
|
222
|
+
"未找到连接的iOS设备\n"
|
|
223
|
+
"请确保:\n"
|
|
224
|
+
"1. iOS设备已通过USB连接\n"
|
|
225
|
+
"2. 设备已信任此电脑\n"
|
|
226
|
+
"3. tidevice已安装: pip install tidevice"
|
|
227
|
+
)
|
|
228
|
+
device_id = devices[0]['id']
|
|
229
|
+
print(f" 📱 自动选择设备: {device_id}", file=sys.stderr)
|
|
230
|
+
|
|
231
|
+
self.current_device_id = device_id
|
|
232
|
+
|
|
233
|
+
# 尝试启动 WDA 代理
|
|
234
|
+
self.start_wda_proxy(device_id, port)
|
|
235
|
+
|
|
236
|
+
# 连接到 WDA
|
|
237
|
+
self.client = wda.Client(f'http://localhost:{port}')
|
|
238
|
+
|
|
239
|
+
# 测试连接
|
|
240
|
+
try:
|
|
241
|
+
status = self.client.status()
|
|
242
|
+
print(f" ✅ iOS设备连接成功: {device_id}", file=sys.stderr)
|
|
243
|
+
print(f" iOS版本: {status.get('os', {}).get('version', 'Unknown')}", file=sys.stderr)
|
|
244
|
+
except Exception as e:
|
|
245
|
+
print(f" ⚠️ 连接可能不稳定: {e}", file=sys.stderr)
|
|
246
|
+
|
|
247
|
+
return self.client
|
|
248
|
+
|
|
249
|
+
except ImportError:
|
|
250
|
+
raise ImportError(
|
|
251
|
+
"facebook-wda 未安装\n"
|
|
252
|
+
"请运行: pip install facebook-wda"
|
|
253
|
+
)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
error_msg = str(e)
|
|
256
|
+
if "Connection refused" in error_msg:
|
|
257
|
+
raise RuntimeError(
|
|
258
|
+
f"无法连接到WDA (端口 {port})\n"
|
|
259
|
+
f"请确保:\n"
|
|
260
|
+
f"1. WebDriverAgent 已安装到设备上(需要用Xcode首次编译)\n"
|
|
261
|
+
f"2. 运行: tidevice -u {device_id} wdaproxy -B com.facebook.WebDriverAgentRunner.xctrunner\n"
|
|
262
|
+
f"3. 或者检查端口 {port} 是否被占用"
|
|
263
|
+
)
|
|
264
|
+
raise RuntimeError(f"连接iOS设备失败: {e}")
|
|
265
|
+
|
|
266
|
+
def check_device_status(self) -> Dict:
|
|
267
|
+
"""
|
|
268
|
+
检查设备连接状态
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
设备状态信息
|
|
272
|
+
"""
|
|
273
|
+
if not self.client:
|
|
274
|
+
return {'connected': False, 'reason': '设备未连接'}
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
status = self.client.status()
|
|
278
|
+
return {
|
|
279
|
+
'connected': True,
|
|
280
|
+
'device_id': self.current_device_id,
|
|
281
|
+
'ios_version': status.get('os', {}).get('version', 'Unknown'),
|
|
282
|
+
'wda_version': status.get('build', {}).get('productBundleIdentifier', 'Unknown'),
|
|
283
|
+
}
|
|
284
|
+
except Exception as e:
|
|
285
|
+
return {
|
|
286
|
+
'connected': False,
|
|
287
|
+
'reason': str(e)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
def disconnect(self):
|
|
291
|
+
"""断开设备连接"""
|
|
292
|
+
if self._wda_proxy_process:
|
|
293
|
+
try:
|
|
294
|
+
self._wda_proxy_process.terminate()
|
|
295
|
+
self._wda_proxy_process.wait(timeout=5)
|
|
296
|
+
except:
|
|
297
|
+
pass
|
|
298
|
+
self._wda_proxy_process = None
|
|
299
|
+
|
|
300
|
+
self.client = None
|
|
301
|
+
self.current_device_id = None
|
|
302
|
+
print(" 📱 iOS设备已断开连接", file=sys.stderr)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|