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/ios_client.py
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
iOS客户端 - 类似Android的MobileClient
|
|
5
|
-
|
|
6
|
-
功能:
|
|
7
|
-
1. 设备连接管理
|
|
8
|
-
2. 页面结构获取(snapshot)
|
|
9
|
-
3. 元素操作(click, type, swipe等)
|
|
10
|
-
4. App管理(launch, stop等)
|
|
11
|
-
|
|
12
|
-
用法:
|
|
13
|
-
client = IOSClient(device_id=None)
|
|
14
|
-
await client.launch_app("com.example.app")
|
|
15
|
-
await client.click("登录按钮")
|
|
16
|
-
"""
|
|
17
|
-
import asyncio
|
|
18
|
-
from typing import Dict, Optional, List
|
|
19
|
-
|
|
20
|
-
from .ios_device_manager import IOSDeviceManager
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class IOSClient:
|
|
24
|
-
"""
|
|
25
|
-
iOS客户端 - 类似Android的MobileClient
|
|
26
|
-
|
|
27
|
-
用法:
|
|
28
|
-
client = IOSClient(device_id=None)
|
|
29
|
-
await client.launch_app("com.example.app")
|
|
30
|
-
await client.click("登录按钮")
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
def __init__(self, device_id: Optional[str] = None):
|
|
34
|
-
"""
|
|
35
|
-
初始化iOS客户端
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
device_id: 设备ID,None则自动选择第一个设备
|
|
39
|
-
"""
|
|
40
|
-
self.device_manager = IOSDeviceManager()
|
|
41
|
-
self.driver = self.device_manager.connect(device_id)
|
|
42
|
-
|
|
43
|
-
# 操作历史(用于录制)
|
|
44
|
-
self.operation_history: List[Dict] = []
|
|
45
|
-
|
|
46
|
-
async def snapshot(self) -> str:
|
|
47
|
-
"""
|
|
48
|
-
获取页面XML结构(类似Android的snapshot)
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
格式化后的页面结构字符串
|
|
52
|
-
"""
|
|
53
|
-
try:
|
|
54
|
-
# 获取页面源码
|
|
55
|
-
source = self.driver.page_source
|
|
56
|
-
|
|
57
|
-
# 简单的格式化(可以后续优化)
|
|
58
|
-
return source
|
|
59
|
-
except Exception as e:
|
|
60
|
-
raise RuntimeError(f"获取页面结构失败: {e}")
|
|
61
|
-
|
|
62
|
-
async def click(self, element_desc: str, ref: Optional[str] = None):
|
|
63
|
-
"""
|
|
64
|
-
点击元素
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
element_desc: 元素描述(自然语言)
|
|
68
|
-
ref: 元素定位器(XPath、accessibility_id等)
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
操作结果
|
|
72
|
-
"""
|
|
73
|
-
try:
|
|
74
|
-
from selenium.webdriver.common.by import By
|
|
75
|
-
|
|
76
|
-
# 如果提供了ref,直接使用
|
|
77
|
-
if ref:
|
|
78
|
-
if ref.startswith('//') or ref.startswith('/'):
|
|
79
|
-
# XPath
|
|
80
|
-
element = self.driver.find_element(By.XPATH, ref)
|
|
81
|
-
elif ref.startswith('id='):
|
|
82
|
-
# accessibility_id
|
|
83
|
-
element = self.driver.find_element(By.ID, ref.replace('id=', ''))
|
|
84
|
-
else:
|
|
85
|
-
# 默认作为accessibility_id
|
|
86
|
-
element = self.driver.find_element(By.ID, ref)
|
|
87
|
-
else:
|
|
88
|
-
# 尝试多种定位方式
|
|
89
|
-
selectors = [
|
|
90
|
-
(By.XPATH, f"//*[@name='{element_desc}']"),
|
|
91
|
-
(By.XPATH, f"//*[@label='{element_desc}']"),
|
|
92
|
-
(By.XPATH, f"//*[contains(@name, '{element_desc}')]"),
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
element = None
|
|
96
|
-
for by, selector in selectors:
|
|
97
|
-
try:
|
|
98
|
-
element = self.driver.find_element(by, selector)
|
|
99
|
-
break
|
|
100
|
-
except:
|
|
101
|
-
continue
|
|
102
|
-
|
|
103
|
-
if not element:
|
|
104
|
-
raise ValueError(f"未找到元素: {element_desc}")
|
|
105
|
-
|
|
106
|
-
element.click()
|
|
107
|
-
|
|
108
|
-
# 记录操作
|
|
109
|
-
self.operation_history.append({
|
|
110
|
-
'action': 'click',
|
|
111
|
-
'element': element_desc,
|
|
112
|
-
'ref': ref or 'auto',
|
|
113
|
-
'success': True
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
return {"success": True}
|
|
117
|
-
|
|
118
|
-
except Exception as e:
|
|
119
|
-
return {"success": False, "reason": str(e)}
|
|
120
|
-
|
|
121
|
-
async def type_text(self, element_desc: str, text: str, ref: Optional[str] = None):
|
|
122
|
-
"""
|
|
123
|
-
输入文本
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
element_desc: 元素描述
|
|
127
|
-
text: 要输入的文本
|
|
128
|
-
ref: 元素定位器
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
操作结果
|
|
132
|
-
"""
|
|
133
|
-
try:
|
|
134
|
-
from selenium.webdriver.common.by import By
|
|
135
|
-
|
|
136
|
-
# 定位输入框
|
|
137
|
-
if ref:
|
|
138
|
-
if ref.startswith('//'):
|
|
139
|
-
element = self.driver.find_element(By.XPATH, ref)
|
|
140
|
-
else:
|
|
141
|
-
element = self.driver.find_element(By.ID, ref)
|
|
142
|
-
else:
|
|
143
|
-
# 查找第一个输入框
|
|
144
|
-
element = self.driver.find_element(By.XPATH, "//XCUIElementTypeTextField | //XCUIElementTypeSecureTextField")
|
|
145
|
-
|
|
146
|
-
element.clear()
|
|
147
|
-
element.send_keys(text)
|
|
148
|
-
|
|
149
|
-
# 记录操作
|
|
150
|
-
self.operation_history.append({
|
|
151
|
-
'action': 'type',
|
|
152
|
-
'element': element_desc,
|
|
153
|
-
'text': text,
|
|
154
|
-
'ref': ref or 'auto',
|
|
155
|
-
'success': True
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
return {"success": True}
|
|
159
|
-
|
|
160
|
-
except Exception as e:
|
|
161
|
-
return {"success": False, "reason": str(e)}
|
|
162
|
-
|
|
163
|
-
async def swipe(self, direction: str):
|
|
164
|
-
"""
|
|
165
|
-
滑动操作
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
direction: 滑动方向 ('up', 'down', 'left', 'right')
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
操作结果
|
|
172
|
-
"""
|
|
173
|
-
try:
|
|
174
|
-
size = self.driver.get_window_size()
|
|
175
|
-
width = size['width']
|
|
176
|
-
height = size['height']
|
|
177
|
-
|
|
178
|
-
if direction == 'up':
|
|
179
|
-
self.driver.swipe(width // 2, int(height * 0.8), width // 2, int(height * 0.2))
|
|
180
|
-
elif direction == 'down':
|
|
181
|
-
self.driver.swipe(width // 2, int(height * 0.2), width // 2, int(height * 0.8))
|
|
182
|
-
elif direction == 'left':
|
|
183
|
-
self.driver.swipe(int(width * 0.8), height // 2, int(width * 0.2), height // 2)
|
|
184
|
-
elif direction == 'right':
|
|
185
|
-
self.driver.swipe(int(width * 0.2), height // 2, int(width * 0.8), height // 2)
|
|
186
|
-
else:
|
|
187
|
-
return {"success": False, "reason": f"不支持的滑动方向: {direction}"}
|
|
188
|
-
|
|
189
|
-
return {"success": True}
|
|
190
|
-
|
|
191
|
-
except Exception as e:
|
|
192
|
-
return {"success": False, "reason": str(e)}
|
|
193
|
-
|
|
194
|
-
async def launch_app(self, bundle_id: str, wait_time: int = 3):
|
|
195
|
-
"""
|
|
196
|
-
启动应用
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
bundle_id: 应用Bundle ID,如 'com.example.app'
|
|
200
|
-
wait_time: 等待时间(秒)
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
操作结果
|
|
204
|
-
"""
|
|
205
|
-
try:
|
|
206
|
-
self.driver.activate_app(bundle_id)
|
|
207
|
-
await asyncio.sleep(wait_time)
|
|
208
|
-
|
|
209
|
-
return {"success": True}
|
|
210
|
-
except Exception as e:
|
|
211
|
-
return {"success": False, "reason": str(e)}
|
|
212
|
-
|
|
213
|
-
def get_current_package(self) -> Optional[str]:
|
|
214
|
-
"""获取当前前台应用的Bundle ID"""
|
|
215
|
-
try:
|
|
216
|
-
return self.driver.current_package
|
|
217
|
-
except:
|
|
218
|
-
return None
|
|
219
|
-
|
core/ios_device_manager.py
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
iOS设备连接管理 - WebDriverAgent
|
|
5
|
-
|
|
6
|
-
功能:
|
|
7
|
-
1. 列出所有连接的iOS设备(模拟器和真机)
|
|
8
|
-
2. 连接指定设备
|
|
9
|
-
3. 检查设备状态
|
|
10
|
-
4. 管理WebDriverAgent服务
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
import sys
|
|
15
|
-
import subprocess
|
|
16
|
-
import os
|
|
17
|
-
import json
|
|
18
|
-
from typing import List, Optional, Dict
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class IOSDeviceManager:
|
|
23
|
-
"""
|
|
24
|
-
iOS设备连接管理器
|
|
25
|
-
|
|
26
|
-
用法:
|
|
27
|
-
manager = IOSDeviceManager()
|
|
28
|
-
devices = manager.list_devices()
|
|
29
|
-
driver = manager.connect(device_id="iPhone 15")
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def __init__(self):
|
|
33
|
-
"""初始化iOS设备管理器"""
|
|
34
|
-
self.xcrun_path = self._find_xcrun()
|
|
35
|
-
self.driver = None
|
|
36
|
-
self.current_device_id = None
|
|
37
|
-
|
|
38
|
-
def _find_xcrun(self) -> str:
|
|
39
|
-
"""
|
|
40
|
-
查找xcrun路径
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
xcrun可执行文件路径
|
|
44
|
-
"""
|
|
45
|
-
# xcrun通常在Xcode中,检查常见路径
|
|
46
|
-
common_paths = [
|
|
47
|
-
'/usr/bin/xcrun',
|
|
48
|
-
'/usr/local/bin/xcrun',
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
for path in common_paths:
|
|
52
|
-
if Path(path).exists():
|
|
53
|
-
return path
|
|
54
|
-
|
|
55
|
-
# 尝试直接调用xcrun(可能在PATH中)
|
|
56
|
-
try:
|
|
57
|
-
result = subprocess.run(['xcrun', '--version'], capture_output=True, timeout=2)
|
|
58
|
-
if result.returncode == 0:
|
|
59
|
-
return 'xcrun'
|
|
60
|
-
except:
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
raise FileNotFoundError(
|
|
64
|
-
"未找到xcrun,请安装Xcode Command Line Tools\n"
|
|
65
|
-
"安装命令: xcode-select --install"
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
def list_devices(self) -> List[Dict[str, str]]:
|
|
69
|
-
"""
|
|
70
|
-
列出所有可用的iOS设备(模拟器和真机)
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
设备列表,每个设备包含id、name、type等信息
|
|
74
|
-
"""
|
|
75
|
-
devices = []
|
|
76
|
-
|
|
77
|
-
try:
|
|
78
|
-
# 列出模拟器
|
|
79
|
-
result = subprocess.run(
|
|
80
|
-
[self.xcrun_path, 'simctl', 'list', 'devices', '--json'],
|
|
81
|
-
capture_output=True,
|
|
82
|
-
text=True,
|
|
83
|
-
timeout=10
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if result.returncode == 0:
|
|
87
|
-
sim_data = json.loads(result.stdout)
|
|
88
|
-
for runtime, sims in sim_data.get('devices', {}).items():
|
|
89
|
-
for sim in sims:
|
|
90
|
-
if sim.get('state') == 'Booted' or sim.get('isAvailable', False):
|
|
91
|
-
devices.append({
|
|
92
|
-
'id': sim.get('udid', ''),
|
|
93
|
-
'name': sim.get('name', 'Unknown'),
|
|
94
|
-
'type': 'simulator',
|
|
95
|
-
'runtime': runtime,
|
|
96
|
-
'state': sim.get('state', 'unknown')
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
# 列出真机(通过idevice_id,需要libimobiledevice)
|
|
100
|
-
try:
|
|
101
|
-
result = subprocess.run(
|
|
102
|
-
['idevice_id', '-l'],
|
|
103
|
-
capture_output=True,
|
|
104
|
-
text=True,
|
|
105
|
-
timeout=5
|
|
106
|
-
)
|
|
107
|
-
if result.returncode == 0:
|
|
108
|
-
for udid in result.stdout.strip().split('\n'):
|
|
109
|
-
if udid.strip():
|
|
110
|
-
devices.append({
|
|
111
|
-
'id': udid.strip(),
|
|
112
|
-
'name': 'iOS Device',
|
|
113
|
-
'type': 'device',
|
|
114
|
-
'state': 'connected'
|
|
115
|
-
})
|
|
116
|
-
except FileNotFoundError:
|
|
117
|
-
# libimobiledevice未安装,跳过真机检测
|
|
118
|
-
pass
|
|
119
|
-
|
|
120
|
-
return devices
|
|
121
|
-
|
|
122
|
-
except subprocess.TimeoutExpired:
|
|
123
|
-
raise RuntimeError("获取设备列表超时")
|
|
124
|
-
except Exception as e:
|
|
125
|
-
raise RuntimeError(f"获取设备列表失败: {e}")
|
|
126
|
-
|
|
127
|
-
def connect(self, device_id: Optional[str] = None, use_webdriveragent: bool = True) -> 'webdriver.Remote':
|
|
128
|
-
"""
|
|
129
|
-
连接iOS设备
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
device_id: 设备ID,None则自动选择第一个设备
|
|
133
|
-
use_webdriveragent: 是否使用WebDriverAgent(默认True)
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
WebDriver对象
|
|
137
|
-
"""
|
|
138
|
-
if use_webdriveragent:
|
|
139
|
-
return self._connect_with_webdriveragent(device_id)
|
|
140
|
-
else:
|
|
141
|
-
# 使用Appium(备选方案)
|
|
142
|
-
return self._connect_with_appium(device_id)
|
|
143
|
-
|
|
144
|
-
def _connect_with_webdriveragent(self, device_id: Optional[str] = None) -> 'webdriver.Remote':
|
|
145
|
-
"""使用WebDriverAgent连接"""
|
|
146
|
-
try:
|
|
147
|
-
from appium import webdriver
|
|
148
|
-
from appium.options.ios import XCUITestOptions
|
|
149
|
-
|
|
150
|
-
# 如果没有指定设备ID,自动选择第一个
|
|
151
|
-
if device_id is None:
|
|
152
|
-
devices = self.list_devices()
|
|
153
|
-
if len(devices) == 0:
|
|
154
|
-
raise RuntimeError("未找到连接的设备,请连接设备后重试")
|
|
155
|
-
device_id = devices[0]['id']
|
|
156
|
-
print(f"📱 自动选择设备: {device_id}", file=sys.stderr)
|
|
157
|
-
|
|
158
|
-
# 配置WebDriverAgent
|
|
159
|
-
options = XCUITestOptions()
|
|
160
|
-
options.platform_name = 'iOS'
|
|
161
|
-
options.device_name = device_id
|
|
162
|
-
options.automation_name = 'XCUITest'
|
|
163
|
-
|
|
164
|
-
# WebDriverAgent默认端口
|
|
165
|
-
wda_port = 8100
|
|
166
|
-
|
|
167
|
-
# 连接WebDriverAgent
|
|
168
|
-
self.driver = webdriver.Remote(
|
|
169
|
-
f'http://localhost:{wda_port}',
|
|
170
|
-
options=options
|
|
171
|
-
)
|
|
172
|
-
self.current_device_id = device_id
|
|
173
|
-
|
|
174
|
-
print(f"✅ iOS设备连接成功: {device_id}", file=sys.stderr)
|
|
175
|
-
|
|
176
|
-
return self.driver
|
|
177
|
-
|
|
178
|
-
except ImportError:
|
|
179
|
-
raise ImportError(
|
|
180
|
-
"Appium未安装,请运行: pip install Appium-Python-Client\n"
|
|
181
|
-
"iOS自动化还需要安装WebDriverAgent"
|
|
182
|
-
)
|
|
183
|
-
except Exception as e:
|
|
184
|
-
raise RuntimeError(f"连接iOS设备失败: {e}\n"
|
|
185
|
-
f"请确保WebDriverAgent已启动: brew install ios-deploy\n"
|
|
186
|
-
f"然后运行: xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'id={device_id}' test")
|
|
187
|
-
|
|
188
|
-
def _connect_with_appium(self, device_id: Optional[str] = None) -> 'webdriver.Remote':
|
|
189
|
-
"""使用Appium连接(备选方案)"""
|
|
190
|
-
try:
|
|
191
|
-
from appium import webdriver
|
|
192
|
-
from appium.options.ios import XCUITestOptions
|
|
193
|
-
|
|
194
|
-
if device_id is None:
|
|
195
|
-
devices = self.list_devices()
|
|
196
|
-
if len(devices) == 0:
|
|
197
|
-
raise RuntimeError("未找到连接的设备")
|
|
198
|
-
device_id = devices[0]['id']
|
|
199
|
-
|
|
200
|
-
options = XCUITestOptions()
|
|
201
|
-
options.platform_name = 'iOS'
|
|
202
|
-
options.device_name = device_id
|
|
203
|
-
options.automation_name = 'XCUITest'
|
|
204
|
-
|
|
205
|
-
# Appium Server默认端口
|
|
206
|
-
self.driver = webdriver.Remote(
|
|
207
|
-
'http://localhost:4723',
|
|
208
|
-
options=options
|
|
209
|
-
)
|
|
210
|
-
self.current_device_id = device_id
|
|
211
|
-
|
|
212
|
-
return self.driver
|
|
213
|
-
|
|
214
|
-
except Exception as e:
|
|
215
|
-
raise RuntimeError(f"Appium连接失败: {e}")
|
|
216
|
-
|
|
217
|
-
def check_device_status(self) -> Dict[str, any]:
|
|
218
|
-
"""
|
|
219
|
-
检查设备状态
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
设备状态信息
|
|
223
|
-
"""
|
|
224
|
-
if not self.driver:
|
|
225
|
-
return {'connected': False, 'reason': '设备未连接'}
|
|
226
|
-
|
|
227
|
-
try:
|
|
228
|
-
# 获取设备信息
|
|
229
|
-
capabilities = self.driver.capabilities
|
|
230
|
-
return {
|
|
231
|
-
'connected': True,
|
|
232
|
-
'device_id': self.current_device_id,
|
|
233
|
-
'platform_version': capabilities.get('platformVersion', 'Unknown'),
|
|
234
|
-
'device_name': capabilities.get('deviceName', 'Unknown'),
|
|
235
|
-
}
|
|
236
|
-
except Exception as e:
|
|
237
|
-
return {
|
|
238
|
-
'connected': False,
|
|
239
|
-
'reason': str(e)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
def disconnect(self):
|
|
243
|
-
"""断开设备连接"""
|
|
244
|
-
if self.driver:
|
|
245
|
-
try:
|
|
246
|
-
self.driver.quit()
|
|
247
|
-
except:
|
|
248
|
-
pass
|
|
249
|
-
self.driver = None
|
|
250
|
-
self.current_device_id = None
|
|
251
|
-
print("📱 iOS设备已断开连接", file=sys.stderr)
|
|
252
|
-
|
core/locator/__init__.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
Cursor AI 自动分析器
|
|
5
|
-
|
|
6
|
-
当检测到请求文件时,自动调用Cursor AI分析截图并写入结果文件。
|
|
7
|
-
这个模块可以在后台运行,监控请求文件并自动处理。
|
|
8
|
-
"""
|
|
9
|
-
import asyncio
|
|
10
|
-
import json
|
|
11
|
-
import time
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Dict, Optional
|
|
14
|
-
import tempfile
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CursorAIAutoAnalyzer:
|
|
18
|
-
"""
|
|
19
|
-
Cursor AI 自动分析器
|
|
20
|
-
|
|
21
|
-
功能:
|
|
22
|
-
1. 监控请求文件目录
|
|
23
|
-
2. 检测到新请求时,自动调用Cursor AI分析
|
|
24
|
-
3. 将结果写入结果文件
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self):
|
|
28
|
-
"""初始化自动分析器"""
|
|
29
|
-
self.request_dir = Path(tempfile.gettempdir()) / "mobile_screenshots" / "requests"
|
|
30
|
-
self.result_dir = Path(tempfile.gettempdir()) / "mobile_screenshots" / "results"
|
|
31
|
-
self.request_dir.mkdir(parents=True, exist_ok=True)
|
|
32
|
-
self.result_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
-
self.processed_requests = set()
|
|
34
|
-
|
|
35
|
-
def check_requests(self) -> list[Path]:
|
|
36
|
-
"""
|
|
37
|
-
检查是否有新的请求文件
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
新的请求文件列表
|
|
41
|
-
"""
|
|
42
|
-
if not self.request_dir.exists():
|
|
43
|
-
return []
|
|
44
|
-
|
|
45
|
-
new_requests = []
|
|
46
|
-
for request_file in self.request_dir.glob("request_*.json"):
|
|
47
|
-
if request_file not in self.processed_requests:
|
|
48
|
-
new_requests.append(request_file)
|
|
49
|
-
|
|
50
|
-
return new_requests
|
|
51
|
-
|
|
52
|
-
async def process_request(self, request_file: Path) -> bool:
|
|
53
|
-
"""
|
|
54
|
-
处理单个请求
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
request_file: 请求文件路径
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
是否成功
|
|
61
|
-
"""
|
|
62
|
-
try:
|
|
63
|
-
# 读取请求文件
|
|
64
|
-
with open(request_file, 'r', encoding='utf-8') as f:
|
|
65
|
-
request_data = json.load(f)
|
|
66
|
-
|
|
67
|
-
request_id = request_data.get('request_id')
|
|
68
|
-
screenshot_path = request_data.get('screenshot_path')
|
|
69
|
-
element_desc = request_data.get('element_desc')
|
|
70
|
-
result_file = self.result_dir / f"result_{request_id}.json"
|
|
71
|
-
|
|
72
|
-
print(f"📝 处理请求: {request_id}")
|
|
73
|
-
print(f" 截图: {screenshot_path}")
|
|
74
|
-
print(f" 元素: {element_desc}")
|
|
75
|
-
|
|
76
|
-
# 🎯 这里需要调用Cursor AI分析截图
|
|
77
|
-
# 由于是在Python脚本中,无法直接调用Cursor AI
|
|
78
|
-
# 所以这里返回提示信息,告诉用户需要手动调用MCP工具
|
|
79
|
-
print(f"💡 请手动调用MCP工具分析截图:")
|
|
80
|
-
print(f" @mobile_analyze_screenshot request_id=\"{request_id}\"")
|
|
81
|
-
|
|
82
|
-
# 标记为已处理
|
|
83
|
-
self.processed_requests.add(request_file)
|
|
84
|
-
|
|
85
|
-
return True
|
|
86
|
-
|
|
87
|
-
except Exception as e:
|
|
88
|
-
print(f"❌ 处理请求失败: {e}")
|
|
89
|
-
return False
|
|
90
|
-
|
|
91
|
-
async def run(self, check_interval: float = 2.0):
|
|
92
|
-
"""
|
|
93
|
-
运行自动分析器(监控模式)
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
check_interval: 检查间隔(秒)
|
|
97
|
-
"""
|
|
98
|
-
print(f"🚀 Cursor AI 自动分析器已启动")
|
|
99
|
-
print(f" 监控目录: {self.request_dir}")
|
|
100
|
-
print(f" 检查间隔: {check_interval}秒")
|
|
101
|
-
|
|
102
|
-
while True:
|
|
103
|
-
try:
|
|
104
|
-
new_requests = self.check_requests()
|
|
105
|
-
for request_file in new_requests:
|
|
106
|
-
await self.process_request(request_file)
|
|
107
|
-
|
|
108
|
-
await asyncio.sleep(check_interval)
|
|
109
|
-
except KeyboardInterrupt:
|
|
110
|
-
print("\n⚠️ 自动分析器已停止")
|
|
111
|
-
break
|
|
112
|
-
except Exception as e:
|
|
113
|
-
print(f"❌ 自动分析器异常: {e}")
|
|
114
|
-
await asyncio.sleep(check_interval)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# 注意:这个自动分析器需要在Cursor AI环境中运行
|
|
118
|
-
# 实际使用时,Cursor AI会通过MCP工具自动处理请求文件
|
|
119
|
-
|