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
@@ -1,520 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 智能测试执行器 - 让Cursor AI自动规划、执行、验证和解决问题
5
-
6
- 功能:
7
- 1. 解析自然语言测试用例
8
- 2. 自动执行每一步操作
9
- 3. 每一步后自动验证是否成功(通过页面元素变化)
10
- 4. 失败时自动分析问题并重试
11
- 5. 找不到元素时自动截图分析
12
- 6. 自动判断操作成功(页面元素出现/变化)
13
- """
14
- import asyncio
15
- import json
16
- import time
17
- from typing import Dict, List, Optional, Any
18
- from pathlib import Path
19
- from datetime import datetime
20
-
21
- from ...core.mobile_client import MobileClient
22
- from ...core.locator.mobile_smart_locator import MobileSmartLocator
23
-
24
-
25
- class SmartTestExecutor:
26
- """
27
- 智能测试执行器
28
-
29
- 让Cursor AI自动规划、执行、验证和解决问题
30
- """
31
-
32
- def __init__(self, client: Optional[MobileClient] = None, locator: Optional[MobileSmartLocator] = None):
33
- """
34
- 初始化智能测试执行器
35
-
36
- Args:
37
- client: MobileClient实例(可选,会自动创建)
38
- locator: MobileSmartLocator实例(可选,会自动创建)
39
- """
40
- self.client = client or MobileClient()
41
- self.locator = locator or MobileSmartLocator(self.client)
42
-
43
- # 执行历史
44
- self.execution_history: List[Dict] = []
45
-
46
- # 页面状态快照(用于对比变化)
47
- self.last_snapshot: Optional[str] = None
48
- self.last_snapshot_time: float = 0
49
-
50
- async def parse_test_case(self, test_description: str) -> List[Dict]:
51
- """
52
- 解析自然语言测试用例
53
-
54
- Args:
55
- test_description: 自然语言描述的测试用例
56
-
57
- Returns:
58
- 步骤列表
59
- """
60
- steps = []
61
-
62
- # 简单的规则解析(可以后续用AI增强)
63
- lines = test_description.strip().split('\n')
64
-
65
- for i, line in enumerate(lines, 1):
66
- line = line.strip()
67
- if not line or line.startswith('#'):
68
- continue
69
-
70
- # 移除序号(如 "1. "、"步骤1:"等)
71
- import re
72
- line = re.sub(r'^\d+[\.、]\s*', '', line)
73
- line = re.sub(r'^步骤\d+[::]\s*', '', line)
74
-
75
- # 解析操作类型
76
- if '打开' in line or '启动' in line:
77
- # 提取包名
78
- package_match = re.search(r'com\.\w+(?:\.\w+)*', line)
79
- if package_match:
80
- steps.append({
81
- 'step_num': i,
82
- 'action': 'launch_app',
83
- 'description': line,
84
- 'package': package_match.group(),
85
- 'wait_time': 3
86
- })
87
- elif '点击' in line:
88
- # 提取元素描述
89
- element = line.replace('点击', '').strip()
90
- steps.append({
91
- 'step_num': i,
92
- 'action': 'click',
93
- 'description': line,
94
- 'element': element,
95
- 'verify': True # 默认验证
96
- })
97
- elif '输入' in line:
98
- # 提取输入框和文本
99
- input_match = re.search(r'输入(.+?)(?:为|:|:)(.+)', line)
100
- if input_match:
101
- element = input_match.group(1).strip()
102
- text = input_match.group(2).strip()
103
- steps.append({
104
- 'step_num': i,
105
- 'action': 'input',
106
- 'description': line,
107
- 'element': element,
108
- 'text': text,
109
- 'verify': True
110
- })
111
- elif '等待' in line:
112
- # 提取等待时间
113
- wait_match = re.search(r'(\d+)', line)
114
- if wait_match:
115
- seconds = int(wait_match.group(1))
116
- steps.append({
117
- 'step_num': i,
118
- 'action': 'wait',
119
- 'description': line,
120
- 'seconds': seconds
121
- })
122
- elif '验证' in line or '检查' in line or '断言' in line:
123
- # 提取验证文本
124
- text_match = re.search(r'["\'](.+?)["\']', line)
125
- if text_match:
126
- text = text_match.group(1)
127
- steps.append({
128
- 'step_num': i,
129
- 'action': 'verify',
130
- 'description': line,
131
- 'expected_text': text
132
- })
133
-
134
- return steps
135
-
136
- async def get_page_snapshot(self) -> str:
137
- """获取当前页面快照"""
138
- snapshot = await self.client.snapshot()
139
- self.last_snapshot = snapshot
140
- self.last_snapshot_time = time.time()
141
- return snapshot
142
-
143
- async def verify_page_change(self, expected_elements: List[str] = None,
144
- unexpected_elements: List[str] = None,
145
- min_wait: float = 0.5) -> Dict:
146
- """
147
- 验证页面是否发生变化
148
-
149
- Args:
150
- expected_elements: 期望出现的元素列表
151
- unexpected_elements: 期望消失的元素列表
152
- min_wait: 最小等待时间(秒)
153
-
154
- Returns:
155
- 验证结果
156
- """
157
- # 等待页面响应
158
- await asyncio.sleep(min_wait)
159
-
160
- # 获取新快照
161
- new_snapshot = await self.get_page_snapshot()
162
-
163
- result = {
164
- 'success': True,
165
- 'page_changed': new_snapshot != self.last_snapshot if self.last_snapshot else True,
166
- 'expected_found': [],
167
- 'expected_missing': [],
168
- 'unexpected_found': [],
169
- 'unexpected_missing': []
170
- }
171
-
172
- # 检查期望出现的元素
173
- if expected_elements:
174
- for elem in expected_elements:
175
- if elem in new_snapshot:
176
- result['expected_found'].append(elem)
177
- else:
178
- result['expected_missing'].append(elem)
179
- result['success'] = False
180
-
181
- # 检查期望消失的元素
182
- if unexpected_elements:
183
- for elem in unexpected_elements:
184
- if elem not in new_snapshot:
185
- result['unexpected_missing'].append(elem)
186
- else:
187
- result['unexpected_found'].append(elem)
188
- result['success'] = False
189
-
190
- return result
191
-
192
- async def execute_click_with_verification(self, element_desc: str,
193
- expected_after: List[str] = None,
194
- unexpected_after: List[str] = None,
195
- max_retries: int = 2) -> Dict:
196
- """
197
- 执行点击操作并自动验证
198
-
199
- Args:
200
- element_desc: 元素描述
201
- expected_after: 点击后期望出现的元素
202
- unexpected_after: 点击后期望消失的元素
203
- max_retries: 最大重试次数
204
-
205
- Returns:
206
- 执行结果
207
- """
208
- result = {
209
- 'success': False,
210
- 'element': element_desc,
211
- 'method': None,
212
- 'retries': 0,
213
- 'error': None
214
- }
215
-
216
- # 获取点击前的快照
217
- snapshot_before = await self.get_page_snapshot()
218
-
219
- for attempt in range(max_retries + 1):
220
- result['retries'] = attempt + 1
221
-
222
- try:
223
- # 定位元素
224
- print(f"\n 🔍 尝试定位: {element_desc} (第{attempt + 1}次)")
225
- locate_result = await self.locator.locate(element_desc)
226
-
227
- if not locate_result:
228
- # 定位失败,截图分析
229
- print(f" ⚠️ 定位失败,截图分析...")
230
- screenshot_path = await self._take_screenshot_for_analysis(element_desc)
231
- result['error'] = f"未找到元素: {element_desc}"
232
- result['screenshot_path'] = screenshot_path
233
-
234
- if attempt < max_retries:
235
- print(f" ⏳ 等待1秒后重试...")
236
- await asyncio.sleep(1)
237
- continue
238
- else:
239
- return result
240
-
241
- # 执行点击
242
- ref = locate_result.get('ref', '')
243
- method = locate_result.get('method', 'unknown')
244
- result['method'] = method
245
-
246
- print(f" ✅ 定位成功: {method}")
247
- print(f" 🖱️ 执行点击...")
248
-
249
- click_result = await self.client.click(element_desc, ref=ref, verify=False)
250
-
251
- if not click_result.get('success'):
252
- result['error'] = click_result.get('reason', '点击失败')
253
- if attempt < max_retries:
254
- await asyncio.sleep(1)
255
- continue
256
- return result
257
-
258
- # 验证点击是否成功(通过页面变化)
259
- print(f" 🔍 验证点击是否成功...")
260
- await asyncio.sleep(0.5) # 等待页面响应
261
-
262
- verification = await self.verify_page_change(
263
- expected_elements=expected_after,
264
- unexpected_elements=unexpected_after
265
- )
266
-
267
- if verification['page_changed']:
268
- print(f" ✅ 页面已变化,点击可能成功")
269
- result['success'] = True
270
-
271
- # 如果有期望元素,检查是否出现
272
- if expected_after:
273
- if verification['expected_found']:
274
- print(f" ✅ 期望元素已出现: {verification['expected_found']}")
275
- if verification['expected_missing']:
276
- print(f" ⚠️ 期望元素未出现: {verification['expected_missing']}")
277
-
278
- return result
279
- else:
280
- print(f" ⚠️ 页面未变化,点击可能失败")
281
- if attempt < max_retries:
282
- print(f" 🔄 重试点击...")
283
- await asyncio.sleep(1)
284
- continue
285
- else:
286
- result['error'] = "点击后页面未变化"
287
- return result
288
-
289
- except Exception as e:
290
- result['error'] = str(e)
291
- print(f" ❌ 执行异常: {e}")
292
- if attempt < max_retries:
293
- await asyncio.sleep(1)
294
- continue
295
- return result
296
-
297
- return result
298
-
299
- async def _take_screenshot_for_analysis(self, element_desc: str) -> str:
300
- """截图用于分析"""
301
- from datetime import datetime
302
- screenshot_dir = Path(__file__).parent.parent.parent.parent / "screenshots"
303
- screenshot_dir.mkdir(exist_ok=True)
304
-
305
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
306
- screenshot_path = screenshot_dir / f"screenshot_{element_desc}_{timestamp}.png"
307
-
308
- self.client.u2.screenshot(str(screenshot_path))
309
- print(f" 📸 截图已保存: {screenshot_path}")
310
-
311
- return str(screenshot_path)
312
-
313
- async def execute_step(self, step: Dict) -> Dict:
314
- """
315
- 执行单个步骤
316
-
317
- Args:
318
- step: 步骤信息
319
-
320
- Returns:
321
- 执行结果
322
- """
323
- step_num = step.get('step_num', 0)
324
- action = step.get('action')
325
- description = step.get('description', '')
326
-
327
- print(f"\n{'='*60}")
328
- print(f"📋 步骤 {step_num}: {description}")
329
- print(f"{'='*60}")
330
-
331
- result = {
332
- 'step_num': step_num,
333
- 'action': action,
334
- 'description': description,
335
- 'success': False,
336
- 'timestamp': datetime.now().isoformat(),
337
- 'details': {}
338
- }
339
-
340
- try:
341
- if action == 'launch_app':
342
- package = step.get('package')
343
- wait_time = step.get('wait_time', 3)
344
-
345
- print(f" 🚀 启动应用: {package}")
346
- await self.client.launch_app(package, wait_time=wait_time)
347
-
348
- # 验证应用是否启动成功
349
- await asyncio.sleep(1)
350
- current_package = self.client.u2.app_current()['package']
351
- if current_package == package:
352
- result['success'] = True
353
- result['details'] = {'package': package}
354
- print(f" ✅ 应用启动成功")
355
- else:
356
- result['details'] = {'expected': package, 'actual': current_package}
357
- print(f" ⚠️ 应用可能未启动成功(当前: {current_package})")
358
-
359
- elif action == 'click':
360
- element = step.get('element')
361
- verify = step.get('verify', True)
362
-
363
- # 根据步骤描述推断期望的变化
364
- expected_after = []
365
- unexpected_after = []
366
-
367
- # 简单的推断逻辑(可以后续用AI增强)
368
- if '云文档' in description and '底部' in description:
369
- expected_after = ['云文档', '我的空间']
370
- elif '我的空间' in description:
371
- expected_after = ['我的空间']
372
- elif '加号' in description or '新建' in description:
373
- expected_after = ['云文档', '在线表格', '思维笔记']
374
- elif '删除' in description:
375
- unexpected_after = ['删除'] # 删除后,删除按钮应该消失
376
-
377
- click_result = await self.execute_click_with_verification(
378
- element,
379
- expected_after=expected_after if verify else None,
380
- unexpected_after=unexpected_after if verify else None
381
- )
382
-
383
- result['success'] = click_result['success']
384
- result['details'] = click_result
385
-
386
- elif action == 'input':
387
- element = step.get('element')
388
- text = step.get('text')
389
-
390
- print(f" ⌨️ 输入: {element} = {text}")
391
-
392
- # 定位输入框
393
- locate_result = await self.locator.locate(element)
394
- if not locate_result:
395
- result['details'] = {'error': f"未找到输入框: {element}"}
396
- return result
397
-
398
- # 执行输入
399
- input_result = await self.client.type_text(element, text, ref=locate_result['ref'])
400
- if input_result.get('success'):
401
- result['success'] = True
402
- result['details'] = {'element': element, 'text': text}
403
- print(f" ✅ 输入成功")
404
- else:
405
- result['details'] = input_result
406
-
407
- elif action == 'wait':
408
- seconds = step.get('seconds', 1)
409
- print(f" ⏳ 等待 {seconds}秒")
410
- await asyncio.sleep(seconds)
411
- result['success'] = True
412
-
413
- elif action == 'verify':
414
- expected_text = step.get('expected_text')
415
- print(f" ✅ 验证: 检查文本 '{expected_text}'")
416
-
417
- snapshot = await self.get_page_snapshot()
418
- if expected_text in snapshot:
419
- result['success'] = True
420
- result['details'] = {'found': True}
421
- print(f" ✅ 验证成功: 找到文本 '{expected_text}'")
422
- else:
423
- result['details'] = {'found': False}
424
- print(f" ❌ 验证失败: 未找到文本 '{expected_text}'")
425
-
426
- except Exception as e:
427
- result['details'] = {'error': str(e)}
428
- print(f" ❌ 执行异常: {e}")
429
- import traceback
430
- traceback.print_exc()
431
-
432
- # 记录执行历史
433
- self.execution_history.append(result)
434
-
435
- return result
436
-
437
- async def execute_test_case(self, test_description: str) -> Dict:
438
- """
439
- 执行完整的测试用例
440
-
441
- Args:
442
- test_description: 自然语言描述的测试用例
443
-
444
- Returns:
445
- 执行结果
446
- """
447
- print("="*60)
448
- print("🚀 智能测试执行器")
449
- print("="*60)
450
- print(f"\n📝 测试用例:\n{test_description}\n")
451
-
452
- # 解析测试用例
453
- steps = await self.parse_test_case(test_description)
454
- print(f"✅ 解析完成,共 {len(steps)} 个步骤\n")
455
-
456
- # 执行步骤
457
- results = []
458
- success_count = 0
459
- fail_count = 0
460
-
461
- for step in steps:
462
- result = await self.execute_step(step)
463
- results.append(result)
464
-
465
- if result['success']:
466
- success_count += 1
467
- else:
468
- fail_count += 1
469
- # 可以选择是否继续执行
470
- # break
471
-
472
- # 生成报告
473
- report = {
474
- 'total_steps': len(steps),
475
- 'success_count': success_count,
476
- 'fail_count': fail_count,
477
- 'results': results,
478
- 'execution_history': self.execution_history
479
- }
480
-
481
- print("\n" + "="*60)
482
- print("📊 执行报告")
483
- print("="*60)
484
- print(f"总步骤数: {report['total_steps']}")
485
- print(f"成功: {success_count}")
486
- print(f"失败: {fail_count}")
487
- print(f"成功率: {success_count/len(steps)*100:.1f}%")
488
-
489
- return report
490
-
491
-
492
- async def main():
493
- """测试主函数"""
494
- executor = SmartTestExecutor()
495
-
496
- test_case = """
497
- 打开 com.im30.mind
498
- 点击底部云文档
499
- 点击我的空间
500
- 点击蓝色加号
501
- 点击云文档(新)
502
- 等待3秒
503
- 点击右上角三个点图标
504
- 弹出的弹窗内点击删除
505
- 之后会再有一个弹窗,点击删除
506
- """
507
-
508
- result = await executor.execute_test_case(test_case)
509
-
510
- # 保存报告
511
- report_path = Path(__file__).parent.parent.parent.parent / "test_report.json"
512
- with open(report_path, 'w', encoding='utf-8') as f:
513
- json.dump(result, f, ensure_ascii=False, indent=2)
514
-
515
- print(f"\n📄 报告已保存: {report_path}")
516
-
517
-
518
- if __name__ == "__main__":
519
- asyncio.run(main())
520
-