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/ai/smart_test_executor.py
DELETED
|
@@ -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
|
-
|