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/test_generator.py
DELETED
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
移动端测试用例生成器
|
|
5
|
-
自然语言 → 移动端测试脚本
|
|
6
|
-
|
|
7
|
-
用法:
|
|
8
|
-
generator = MobileTestGenerator()
|
|
9
|
-
script = generator.generate("打开App\n点击登录按钮\n输入邮箱 test@example.com")
|
|
10
|
-
generator.save("test_login.py", script)
|
|
11
|
-
"""
|
|
12
|
-
import re
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import List, Dict
|
|
15
|
-
from datetime import datetime
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class MobileTestStep:
|
|
19
|
-
"""移动端测试步骤"""
|
|
20
|
-
def __init__(self, action: str, **kwargs):
|
|
21
|
-
self.action = action
|
|
22
|
-
self.params = kwargs
|
|
23
|
-
|
|
24
|
-
def __repr__(self):
|
|
25
|
-
return f"MobileTestStep(action={self.action}, params={self.params})"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class MobileTestGenerator:
|
|
29
|
-
"""
|
|
30
|
-
移动端测试用例生成器
|
|
31
|
-
|
|
32
|
-
功能:
|
|
33
|
-
1. 解析自然语言测试用例
|
|
34
|
-
2. 生成移动端测试脚本
|
|
35
|
-
3. 支持中文自然语言输入
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, output_dir: str = "tests"):
|
|
39
|
-
"""
|
|
40
|
-
初始化生成器
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
output_dir: 生成的测试文件输出目录
|
|
44
|
-
"""
|
|
45
|
-
self.output_dir = Path(output_dir)
|
|
46
|
-
self.output_dir.mkdir(exist_ok=True)
|
|
47
|
-
|
|
48
|
-
def parse_natural_language(self, test_case: str) -> List[MobileTestStep]:
|
|
49
|
-
"""
|
|
50
|
-
解析自然语言测试用例
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
test_case: 自然语言描述的测试用例(中文)
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
测试步骤列表
|
|
57
|
-
"""
|
|
58
|
-
steps = []
|
|
59
|
-
lines = [line.strip() for line in test_case.strip().split('\n') if line.strip()]
|
|
60
|
-
|
|
61
|
-
for line in lines:
|
|
62
|
-
# 跳过注释
|
|
63
|
-
if line.startswith('#'):
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
# 解析打开App
|
|
67
|
-
if "打开App" in line or "启动App" in line:
|
|
68
|
-
# 提取包名(如果有)
|
|
69
|
-
package_match = re.search(r'(?:打开|启动)(?:App|应用)[::]\s*([^\s]+)', line)
|
|
70
|
-
package = package_match.group(1) if package_match else None
|
|
71
|
-
steps.append(MobileTestStep('launch_app', package=package, raw=line))
|
|
72
|
-
|
|
73
|
-
# 解析点击
|
|
74
|
-
elif "点击" in line:
|
|
75
|
-
description = line.replace("点击", "").strip()
|
|
76
|
-
# 自动识别常见模式
|
|
77
|
-
# "点击底部导航栏第3个图标" → description="底部导航栏第3个图标"
|
|
78
|
-
# "点击右下角加号" → description="右下角加号"
|
|
79
|
-
# "点击登录" → description="登录"
|
|
80
|
-
steps.append(MobileTestStep(
|
|
81
|
-
'click',
|
|
82
|
-
description=description,
|
|
83
|
-
raw=line
|
|
84
|
-
))
|
|
85
|
-
|
|
86
|
-
# 解析输入
|
|
87
|
-
elif "输入" in line:
|
|
88
|
-
# 支持多种格式:
|
|
89
|
-
# "在邮箱输入框输入 test@example.com"
|
|
90
|
-
# "邮箱输入框输入 test@example.com"
|
|
91
|
-
# "输入邮箱 test@example.com"
|
|
92
|
-
# "输入密码 password123"
|
|
93
|
-
# "内容输入框输入 自动化测试"
|
|
94
|
-
|
|
95
|
-
input_match = re.search(r'(.+?)输入\s+(.+)', line)
|
|
96
|
-
if input_match:
|
|
97
|
-
field_desc = input_match.group(1).strip()
|
|
98
|
-
text_content = input_match.group(2).strip()
|
|
99
|
-
|
|
100
|
-
# 清理描述
|
|
101
|
-
clean_desc = field_desc.replace("在", "").replace("的", "").strip()
|
|
102
|
-
|
|
103
|
-
# 智能补全"输入框"后缀
|
|
104
|
-
if "输入框" not in clean_desc:
|
|
105
|
-
# 如果只是关键词(如"邮箱"、"密码"、"内容"),自动补全
|
|
106
|
-
if clean_desc and not any(kw in clean_desc for kw in ["按钮", "图标", "标签"]):
|
|
107
|
-
clean_desc = f"{clean_desc}输入框"
|
|
108
|
-
elif not clean_desc:
|
|
109
|
-
clean_desc = "输入框"
|
|
110
|
-
|
|
111
|
-
steps.append(MobileTestStep(
|
|
112
|
-
'type',
|
|
113
|
-
description=clean_desc,
|
|
114
|
-
text=text_content,
|
|
115
|
-
raw=line
|
|
116
|
-
))
|
|
117
|
-
|
|
118
|
-
# 解析滑动
|
|
119
|
-
elif "滑动" in line or "滑动" in line:
|
|
120
|
-
direction_match = re.search(r'(?:向上|向下|向左|向右|上|下|左|右)', line)
|
|
121
|
-
if direction_match:
|
|
122
|
-
direction_text = direction_match.group(0)
|
|
123
|
-
direction_map = {
|
|
124
|
-
'向上': 'up', '上': 'up',
|
|
125
|
-
'向下': 'down', '下': 'down',
|
|
126
|
-
'向左': 'left', '左': 'left',
|
|
127
|
-
'向右': 'right', '右': 'right'
|
|
128
|
-
}
|
|
129
|
-
direction = direction_map.get(direction_text, 'up')
|
|
130
|
-
steps.append(MobileTestStep('swipe', direction=direction, raw=line))
|
|
131
|
-
|
|
132
|
-
# 解析等待
|
|
133
|
-
elif "等待" in line:
|
|
134
|
-
time_match = re.search(r'(\d+)', line)
|
|
135
|
-
if time_match:
|
|
136
|
-
steps.append(MobileTestStep('wait', seconds=int(time_match.group(1)), raw=line))
|
|
137
|
-
|
|
138
|
-
# 解析断言
|
|
139
|
-
elif "断言" in line or "检查" in line:
|
|
140
|
-
description = line.replace("断言", "").replace("检查", "").strip()
|
|
141
|
-
steps.append(MobileTestStep('assert', description=description, raw=line))
|
|
142
|
-
|
|
143
|
-
return steps
|
|
144
|
-
|
|
145
|
-
def generate_test_script(self, test_name: str, test_case: str, package_name: str = "com.im30.way") -> str:
|
|
146
|
-
"""
|
|
147
|
-
生成移动端测试脚本
|
|
148
|
-
|
|
149
|
-
Args:
|
|
150
|
-
test_name: 测试用例名称(中文,会自动转换为文件名)
|
|
151
|
-
test_case: 自然语言测试用例
|
|
152
|
-
package_name: App包名
|
|
153
|
-
|
|
154
|
-
Returns:
|
|
155
|
-
生成的测试脚本内容
|
|
156
|
-
"""
|
|
157
|
-
steps = self.parse_natural_language(test_case)
|
|
158
|
-
|
|
159
|
-
# 生成文件名(中文转拼音或直接使用)
|
|
160
|
-
safe_name = re.sub(r'[^\w\s-]', '', test_name).strip().replace(' ', '_')
|
|
161
|
-
|
|
162
|
-
# 生成脚本内容
|
|
163
|
-
script_lines = [
|
|
164
|
-
"#!/usr/bin/env python3",
|
|
165
|
-
"# -*- coding: utf-8 -*-",
|
|
166
|
-
f'"""',
|
|
167
|
-
f"移动端测试用例: {test_name}",
|
|
168
|
-
f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
|
169
|
-
f"",
|
|
170
|
-
f"原始测试用例:",
|
|
171
|
-
*[f"{step.params.get('raw', '')}" for step in steps if step.params.get('raw')],
|
|
172
|
-
f'"""',
|
|
173
|
-
"import asyncio",
|
|
174
|
-
"import sys",
|
|
175
|
-
"from pathlib import Path",
|
|
176
|
-
"",
|
|
177
|
-
"# 添加backend目录到路径",
|
|
178
|
-
"sys.path.insert(0, str(Path(__file__).parent.parent.parent))",
|
|
179
|
-
"",
|
|
180
|
-
"from mobile_mcp.core.mobile_client import MobileClient",
|
|
181
|
-
"from mobile_mcp.core.locator.mobile_smart_locator import MobileSmartLocator",
|
|
182
|
-
"",
|
|
183
|
-
"",
|
|
184
|
-
f"class Test{safe_name}:",
|
|
185
|
-
f' """测试类: {test_name}"""',
|
|
186
|
-
f" ",
|
|
187
|
-
f" PACKAGE_NAME = \"{package_name}\"",
|
|
188
|
-
f" ",
|
|
189
|
-
f" def __init__(self):",
|
|
190
|
-
f" self.client = None",
|
|
191
|
-
f" self.locator = None",
|
|
192
|
-
f" ",
|
|
193
|
-
f" async def setup(self):",
|
|
194
|
-
f" \"\"\"测试前置准备\"\"\"",
|
|
195
|
-
f" print(\"=\" * 60)",
|
|
196
|
-
f" print(f\"🚀 {test_name}\")",
|
|
197
|
-
f" print(\"=\" * 60)",
|
|
198
|
-
f" ",
|
|
199
|
-
f" # 连接设备",
|
|
200
|
-
f" print(\"\\n📱 连接设备...\")",
|
|
201
|
-
f" self.client = MobileClient(device_id=None)",
|
|
202
|
-
f" self.locator = MobileSmartLocator(self.client)",
|
|
203
|
-
f" ",
|
|
204
|
-
f" # 启动App",
|
|
205
|
-
f" print(f\"\\n📱 启动App: {{self.PACKAGE_NAME}}\")",
|
|
206
|
-
f" result = await self.client.launch_app(self.PACKAGE_NAME, wait_time=5)",
|
|
207
|
-
f" if not result.get('success'):",
|
|
208
|
-
f" raise Exception(f\"启动App失败: {{result.get('reason')}}\")",
|
|
209
|
-
f" ",
|
|
210
|
-
f" await asyncio.sleep(2) # 等待页面加载",
|
|
211
|
-
f" ",
|
|
212
|
-
f" async def teardown(self):",
|
|
213
|
-
f" \"\"\"测试后清理\"\"\"",
|
|
214
|
-
f" if self.client:",
|
|
215
|
-
f" self.client.device_manager.disconnect()",
|
|
216
|
-
f" ",
|
|
217
|
-
f" async def test_case(self):",
|
|
218
|
-
f" \"\"\"测试用例主体\"\"\"",
|
|
219
|
-
f" try:",
|
|
220
|
-
]
|
|
221
|
-
|
|
222
|
-
# 生成测试步骤代码
|
|
223
|
-
step_index = 1
|
|
224
|
-
for step in steps:
|
|
225
|
-
action = step.action
|
|
226
|
-
params = step.params
|
|
227
|
-
|
|
228
|
-
if action == 'launch_app':
|
|
229
|
-
if params.get('package'):
|
|
230
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', '启动App')}")
|
|
231
|
-
script_lines.append(f" result = await self.client.launch_app(\"{params['package']}\", wait_time=5)")
|
|
232
|
-
script_lines.append(f" await asyncio.sleep(2)")
|
|
233
|
-
else:
|
|
234
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', '启动App')}")
|
|
235
|
-
script_lines.append(f" # App已在setup中启动")
|
|
236
|
-
|
|
237
|
-
elif action == 'click':
|
|
238
|
-
description = params.get('description', '')
|
|
239
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', f'点击{description}')}")
|
|
240
|
-
script_lines.append(f" print(f\"\\n步骤{step_index}: 点击 {description}\")")
|
|
241
|
-
script_lines.append(f" result = await self.locator.locate(\"{description}\")")
|
|
242
|
-
script_lines.append(f" if result:")
|
|
243
|
-
script_lines.append(f" click_result = await self.client.click(\"{description}\", ref=result['ref'])")
|
|
244
|
-
script_lines.append(f" if click_result.get('success'):")
|
|
245
|
-
script_lines.append(f" print(f\"✅ 点击成功\")")
|
|
246
|
-
script_lines.append(f" await asyncio.sleep(1)")
|
|
247
|
-
script_lines.append(f" else:")
|
|
248
|
-
script_lines.append(f" print(f\"⚠️ 点击失败: {{click_result.get('reason')}}\")")
|
|
249
|
-
script_lines.append(f" # 尝试使用坐标点击(如果ref包含坐标信息)")
|
|
250
|
-
script_lines.append(f" ref = result.get('ref', '')")
|
|
251
|
-
script_lines.append(f" if ref.startswith('vision_coord_') or (ref.startswith('[') and '][' in ref):")
|
|
252
|
-
script_lines.append(f" print(f\" 尝试使用坐标点击: {{ref}}\")")
|
|
253
|
-
script_lines.append(f" await self.client.click(\"{description}\", ref=ref, verify=False)")
|
|
254
|
-
script_lines.append(f" await asyncio.sleep(1)")
|
|
255
|
-
script_lines.append(f" else:")
|
|
256
|
-
script_lines.append(f" print(f\"⚠️ 未找到: {description},尝试视觉识别...\")")
|
|
257
|
-
script_lines.append(f" # 🎯 定位失败时,尝试视觉识别获取坐标")
|
|
258
|
-
script_lines.append(f" try:")
|
|
259
|
-
script_lines.append(f" from mobile_mcp.vision.vision_locator import MobileVisionLocator")
|
|
260
|
-
script_lines.append(f" vision_locator = MobileVisionLocator(self.client)")
|
|
261
|
-
script_lines.append(f" vision_result = await vision_locator.locate_element_by_vision(\"{description}\")")
|
|
262
|
-
script_lines.append(f" if vision_result and vision_result.get('found'):")
|
|
263
|
-
script_lines.append(f" x = vision_result.get('x', 0)")
|
|
264
|
-
script_lines.append(f" y = vision_result.get('y', 0)")
|
|
265
|
-
script_lines.append(f" print(f\" ✅ 视觉识别成功,坐标: ({{x}}, {{y}})\")")
|
|
266
|
-
script_lines.append(f" self.client.u2.click(x, y)")
|
|
267
|
-
script_lines.append(f" await asyncio.sleep(1)")
|
|
268
|
-
script_lines.append(f" else:")
|
|
269
|
-
script_lines.append(f" print(f\" ❌ 视觉识别也失败: {{vision_result.get('reason', 'unknown') if vision_result else 'unknown'}}\")")
|
|
270
|
-
script_lines.append(f" raise Exception(f\"无法定位元素: {description}\")")
|
|
271
|
-
script_lines.append(f" except Exception as e:")
|
|
272
|
-
script_lines.append(f" print(f\" ❌ 视觉识别异常: {{e}}\")")
|
|
273
|
-
script_lines.append(f" raise Exception(f\"无法定位元素: {description}\")")
|
|
274
|
-
|
|
275
|
-
elif action == 'type':
|
|
276
|
-
description = params.get('description', '输入框')
|
|
277
|
-
text = params.get('text', '')
|
|
278
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', f'输入{text}')}")
|
|
279
|
-
script_lines.append(f" print(f\"\\n步骤{step_index}: 在{description}输入 {text}\")")
|
|
280
|
-
script_lines.append(f" result = await self.locator.locate(\"{description}\")")
|
|
281
|
-
script_lines.append(f" if result:")
|
|
282
|
-
script_lines.append(f" await self.client.type_text(\"{description}\", \"{text}\", ref=result['ref'])")
|
|
283
|
-
script_lines.append(f" await asyncio.sleep(0.5)")
|
|
284
|
-
script_lines.append(f" else:")
|
|
285
|
-
script_lines.append(f" print(f\"⚠️ 未找到: {description}\")")
|
|
286
|
-
|
|
287
|
-
elif action == 'swipe':
|
|
288
|
-
direction = params.get('direction', 'up')
|
|
289
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', f'滑动{direction}')}")
|
|
290
|
-
script_lines.append(f" print(f\"\\n步骤{step_index}: 滑动 {direction}\")")
|
|
291
|
-
script_lines.append(f" await self.client.swipe(\"{direction}\")")
|
|
292
|
-
script_lines.append(f" await asyncio.sleep(1)")
|
|
293
|
-
|
|
294
|
-
elif action == 'wait':
|
|
295
|
-
seconds = params.get('seconds', 1)
|
|
296
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', f'等待{seconds}秒')}")
|
|
297
|
-
script_lines.append(f" await asyncio.sleep({seconds})")
|
|
298
|
-
|
|
299
|
-
elif action == 'assert':
|
|
300
|
-
description = params.get('description', '')
|
|
301
|
-
script_lines.append(f" # 步骤{step_index}: {params.get('raw', f'断言{description}')}")
|
|
302
|
-
script_lines.append(f" print(f\"\\n步骤{step_index}: 验证 {description}\")")
|
|
303
|
-
script_lines.append(f" snapshot = await self.client.snapshot()")
|
|
304
|
-
script_lines.append(f" if \"{description}\" in snapshot:")
|
|
305
|
-
script_lines.append(f" print(f\"✅ 验证通过: {description}\")")
|
|
306
|
-
script_lines.append(f" else:")
|
|
307
|
-
script_lines.append(f" print(f\"⚠️ 验证失败: {description}\")")
|
|
308
|
-
|
|
309
|
-
step_index += 1
|
|
310
|
-
|
|
311
|
-
# 添加结尾
|
|
312
|
-
script_lines.extend([
|
|
313
|
-
f" ",
|
|
314
|
-
f" # 打印统计信息",
|
|
315
|
-
f" print(\"\\n\" + \"=\" * 60)",
|
|
316
|
-
f" print(\"📊 定位统计:\")",
|
|
317
|
-
f" print(\"=\" * 60)",
|
|
318
|
-
f" print(f\" 总定位次数: {{self.locator.stats['total']}}\")",
|
|
319
|
-
f" print(f\" 规则匹配: {{self.locator.stats['rule_hits']}}\")",
|
|
320
|
-
f" print(f\" 缓存命中: {{self.locator.stats['cache_hits']}}\")",
|
|
321
|
-
f" print(f\" XML分析: {{self.locator.stats['xml_analysis']}}\")",
|
|
322
|
-
f" print(f\" 视觉识别: {{self.locator.stats['vision_calls']}}\")",
|
|
323
|
-
f" print(f\" AI分析: {{self.locator.stats['ai_calls']}}\")",
|
|
324
|
-
f" ",
|
|
325
|
-
f" print(\"\\n✅ 测试完成!\")",
|
|
326
|
-
f" ",
|
|
327
|
-
f" except Exception as e:",
|
|
328
|
-
f" print(f\"\\n❌ 测试失败: {{e}}\")",
|
|
329
|
-
f" import traceback",
|
|
330
|
-
f" traceback.print_exc()",
|
|
331
|
-
f" raise",
|
|
332
|
-
f"",
|
|
333
|
-
f"",
|
|
334
|
-
f"async def run_test():",
|
|
335
|
-
f" \"\"\"运行测试\"\"\"",
|
|
336
|
-
f" test = Test{safe_name}()",
|
|
337
|
-
f" try:",
|
|
338
|
-
f" await test.setup()",
|
|
339
|
-
f" await test.test_case()",
|
|
340
|
-
f" finally:",
|
|
341
|
-
f" await test.teardown()",
|
|
342
|
-
f"",
|
|
343
|
-
f"",
|
|
344
|
-
f"if __name__ == \"__main__\":",
|
|
345
|
-
f" asyncio.run(run_test())",
|
|
346
|
-
])
|
|
347
|
-
|
|
348
|
-
return '\n'.join(script_lines)
|
|
349
|
-
|
|
350
|
-
def save(self, filename: str, script: str):
|
|
351
|
-
"""
|
|
352
|
-
保存生成的测试脚本
|
|
353
|
-
|
|
354
|
-
Args:
|
|
355
|
-
filename: 文件名(会自动添加.py后缀)
|
|
356
|
-
script: 脚本内容
|
|
357
|
-
"""
|
|
358
|
-
if not filename.endswith('.py'):
|
|
359
|
-
filename += '.py'
|
|
360
|
-
|
|
361
|
-
file_path = self.output_dir / filename
|
|
362
|
-
file_path.write_text(script, encoding='utf-8')
|
|
363
|
-
print(f"✅ 测试用例已保存: {file_path}")
|
|
364
|
-
return file_path
|
|
365
|
-
|