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.
Files changed (65) hide show
  1. mobile_mcp/__init__.py +34 -0
  2. mobile_mcp/config.py +142 -0
  3. mobile_mcp/core/basic_tools_lite.py +3266 -0
  4. {core → mobile_mcp/core}/device_manager.py +2 -2
  5. mobile_mcp/core/dynamic_config.py +272 -0
  6. mobile_mcp/core/ios_client_wda.py +569 -0
  7. mobile_mcp/core/ios_device_manager_wda.py +306 -0
  8. {core → mobile_mcp/core}/mobile_client.py +279 -39
  9. mobile_mcp/core/template_matcher.py +429 -0
  10. mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  11. mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  12. mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  13. mobile_mcp/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  14. mobile_mcp/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  15. mobile_mcp/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  16. {core → mobile_mcp/core}/utils/smart_wait.py +3 -3
  17. mobile_mcp/mcp_tools/__init__.py +10 -0
  18. mobile_mcp/mcp_tools/mcp_server.py +1071 -0
  19. mobile_mcp_ai-2.5.8.dist-info/METADATA +469 -0
  20. mobile_mcp_ai-2.5.8.dist-info/RECORD +32 -0
  21. mobile_mcp_ai-2.5.8.dist-info/entry_points.txt +2 -0
  22. mobile_mcp_ai-2.5.8.dist-info/licenses/LICENSE +201 -0
  23. mobile_mcp_ai-2.5.8.dist-info/top_level.txt +1 -0
  24. core/ai/__init__.py +0 -11
  25. core/ai/ai_analyzer.py +0 -197
  26. core/ai/ai_config.py +0 -116
  27. core/ai/ai_platform_adapter.py +0 -399
  28. core/ai/smart_test_executor.py +0 -520
  29. core/ai/test_generator.py +0 -365
  30. core/ai/test_generator_from_history.py +0 -391
  31. core/ai/test_generator_standalone.py +0 -293
  32. core/assertion/__init__.py +0 -9
  33. core/assertion/smart_assertion.py +0 -341
  34. core/basic_tools.py +0 -377
  35. core/h5/__init__.py +0 -10
  36. core/h5/h5_handler.py +0 -548
  37. core/ios_client.py +0 -219
  38. core/ios_device_manager.py +0 -252
  39. core/locator/__init__.py +0 -10
  40. core/locator/cursor_ai_auto_analyzer.py +0 -119
  41. core/locator/cursor_vision_helper.py +0 -414
  42. core/locator/mobile_smart_locator.py +0 -1640
  43. core/locator/position_analyzer.py +0 -813
  44. core/locator/script_updater.py +0 -157
  45. core/nl_test_runner.py +0 -585
  46. core/smart_app_launcher.py +0 -334
  47. core/smart_tools.py +0 -311
  48. mcp/__init__.py +0 -8
  49. mcp/mcp_server.py +0 -1919
  50. mcp/mcp_server_simple.py +0 -476
  51. mobile_mcp_ai-2.1.2.dist-info/METADATA +0 -567
  52. mobile_mcp_ai-2.1.2.dist-info/RECORD +0 -45
  53. mobile_mcp_ai-2.1.2.dist-info/entry_points.txt +0 -2
  54. mobile_mcp_ai-2.1.2.dist-info/top_level.txt +0 -4
  55. vision/__init__.py +0 -10
  56. vision/vision_locator.py +0 -404
  57. {core → mobile_mcp/core}/__init__.py +0 -0
  58. {core → mobile_mcp/core}/utils/__init__.py +0 -0
  59. {core → mobile_mcp/core}/utils/logger.py +0 -0
  60. {core → mobile_mcp/core}/utils/operation_history_manager.py +0 -0
  61. {utils → mobile_mcp/utils}/__init__.py +0 -0
  62. {utils → mobile_mcp/utils}/logger.py +0 -0
  63. {utils → mobile_mcp/utils}/xml_formatter.py +0 -0
  64. {utils → mobile_mcp/utils}/xml_parser.py +0 -0
  65. {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
-
@@ -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,10 +0,0 @@
1
- """
2
- 移动端定位器模块
3
- """
4
-
5
- from .mobile_smart_locator import MobileSmartLocator
6
-
7
- __all__ = [
8
- 'MobileSmartLocator',
9
- ]
10
-
@@ -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
-