pytest-dsl 0.4.0__py3-none-any.whl → 0.6.0__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.
- pytest_dsl/cli.py +28 -33
- pytest_dsl/core/auto_decorator.py +72 -53
- pytest_dsl/core/auto_directory.py +8 -5
- pytest_dsl/core/dsl_executor.py +211 -53
- pytest_dsl/core/http_request.py +272 -221
- pytest_dsl/core/lexer.py +14 -13
- pytest_dsl/core/parser.py +27 -8
- pytest_dsl/core/parsetab.py +71 -66
- pytest_dsl/core/plugin_discovery.py +1 -8
- pytest_dsl/core/yaml_loader.py +96 -19
- pytest_dsl/examples/assert/assertion_example.auto +1 -1
- pytest_dsl/examples/assert/boolean_test.auto +2 -2
- pytest_dsl/examples/assert/expression_test.auto +1 -1
- pytest_dsl/examples/custom/test_advanced_keywords.auto +2 -2
- pytest_dsl/examples/custom/test_custom_keywords.auto +2 -2
- pytest_dsl/examples/custom/test_default_values.auto +2 -2
- pytest_dsl/examples/http/file_reference_test.auto +1 -1
- pytest_dsl/examples/http/http_advanced.auto +1 -1
- pytest_dsl/examples/http/http_example.auto +1 -1
- pytest_dsl/examples/http/http_length_test.auto +1 -1
- pytest_dsl/examples/http/http_retry_assertions.auto +1 -1
- pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +2 -2
- pytest_dsl/examples/http/http_with_yaml.auto +1 -1
- pytest_dsl/examples/quickstart/api_basics.auto +1 -1
- pytest_dsl/examples/quickstart/assertions.auto +1 -1
- pytest_dsl/examples/quickstart/loops.auto +2 -2
- pytest_dsl/keywords/assertion_keywords.py +76 -62
- pytest_dsl/keywords/global_keywords.py +43 -4
- pytest_dsl/keywords/http_keywords.py +141 -139
- {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/METADATA +266 -15
- pytest_dsl-0.6.0.dist-info/RECORD +68 -0
- {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/WHEEL +1 -1
- {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/entry_points.txt +1 -0
- pytest_dsl-0.4.0.dist-info/RECORD +0 -68
- {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ import yaml
|
|
9
9
|
import json
|
10
10
|
import os
|
11
11
|
import time
|
12
|
+
import logging
|
12
13
|
from typing import Dict, Any, Union
|
13
14
|
|
14
15
|
from pytest_dsl.core.keyword_manager import keyword_manager
|
@@ -16,17 +17,20 @@ from pytest_dsl.core.http_request import HTTPRequest
|
|
16
17
|
from pytest_dsl.core.yaml_vars import yaml_vars
|
17
18
|
from pytest_dsl.core.context import TestContext
|
18
19
|
|
20
|
+
# 配置日志
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
19
23
|
def _process_file_reference(reference: Union[str, Dict[str, Any]], allow_vars: bool = True, test_context: TestContext = None) -> Any:
|
20
24
|
"""处理文件引用,加载外部文件内容
|
21
|
-
|
25
|
+
|
22
26
|
支持两种语法:
|
23
27
|
1. 简单语法: "@file:/path/to/file.json" 或 "@file_template:/path/to/file.json"
|
24
28
|
2. 详细语法: 使用file_ref结构提供更多的配置选项
|
25
|
-
|
29
|
+
|
26
30
|
Args:
|
27
31
|
reference: 文件引用字符串或配置字典
|
28
32
|
allow_vars: 是否允许在文件内容中替换变量
|
29
|
-
|
33
|
+
|
30
34
|
Returns:
|
31
35
|
加载并处理后的文件内容
|
32
36
|
"""
|
@@ -35,16 +39,16 @@ def _process_file_reference(reference: Union[str, Dict[str, Any]], allow_vars: b
|
|
35
39
|
# 匹配简单文件引用语法
|
36
40
|
file_ref_pattern = r'^@file(?:_template)?:(.+)$'
|
37
41
|
match = re.match(file_ref_pattern, reference.strip())
|
38
|
-
|
42
|
+
|
39
43
|
if match:
|
40
44
|
file_path = match.group(1).strip()
|
41
45
|
is_template = '_template' in reference[:15] # 检查是否为模板
|
42
46
|
return _load_file_content(file_path, is_template, 'auto', 'utf-8', test_context)
|
43
|
-
|
47
|
+
|
44
48
|
# 处理详细语法
|
45
49
|
elif isinstance(reference, dict) and 'file_ref' in reference:
|
46
50
|
file_ref = reference['file_ref']
|
47
|
-
|
51
|
+
|
48
52
|
if isinstance(file_ref, str):
|
49
53
|
# 如果file_ref是字符串,使用默认配置
|
50
54
|
return _load_file_content(file_ref, allow_vars, 'auto', 'utf-8', test_context)
|
@@ -53,44 +57,44 @@ def _process_file_reference(reference: Union[str, Dict[str, Any]], allow_vars: b
|
|
53
57
|
file_path = file_ref.get('path')
|
54
58
|
if not file_path:
|
55
59
|
raise ValueError("file_ref必须包含path字段")
|
56
|
-
|
60
|
+
|
57
61
|
template = file_ref.get('template', allow_vars)
|
58
62
|
file_type = file_ref.get('type', 'auto')
|
59
63
|
encoding = file_ref.get('encoding', 'utf-8')
|
60
|
-
|
64
|
+
|
61
65
|
return _load_file_content(file_path, template, file_type, encoding, test_context)
|
62
|
-
|
66
|
+
|
63
67
|
# 如果不是文件引用,返回原始值
|
64
68
|
return reference
|
65
69
|
|
66
70
|
|
67
|
-
def _load_file_content(file_path: str, is_template: bool = False,
|
71
|
+
def _load_file_content(file_path: str, is_template: bool = False,
|
68
72
|
file_type: str = 'auto', encoding: str = 'utf-8', test_context: TestContext = None) -> Any:
|
69
73
|
"""加载文件内容
|
70
|
-
|
74
|
+
|
71
75
|
Args:
|
72
76
|
file_path: 文件路径
|
73
77
|
is_template: 是否作为模板处理(替换变量引用)
|
74
78
|
file_type: 文件类型 (auto, json, yaml, text)
|
75
79
|
encoding: 文件编码
|
76
|
-
|
80
|
+
|
77
81
|
Returns:
|
78
82
|
加载并处理后的文件内容
|
79
83
|
"""
|
80
84
|
# 验证文件存在
|
81
85
|
if not os.path.exists(file_path):
|
82
86
|
raise FileNotFoundError(f"找不到引用的文件: {file_path}")
|
83
|
-
|
87
|
+
|
84
88
|
# 读取文件内容
|
85
89
|
with open(file_path, 'r', encoding=encoding) as f:
|
86
90
|
content = f.read()
|
87
|
-
|
91
|
+
|
88
92
|
# 如果是模板,处理变量替换
|
89
93
|
if is_template:
|
90
94
|
from pytest_dsl.core.variable_utils import VariableReplacer
|
91
95
|
replacer = VariableReplacer(test_context=test_context)
|
92
96
|
content = replacer.replace_in_string(content)
|
93
|
-
|
97
|
+
|
94
98
|
# 根据文件类型处理内容
|
95
99
|
if file_type == 'auto':
|
96
100
|
# 根据文件扩展名自动检测类型
|
@@ -101,7 +105,7 @@ def _load_file_content(file_path: str, is_template: bool = False,
|
|
101
105
|
file_type = 'yaml'
|
102
106
|
else:
|
103
107
|
file_type = 'text'
|
104
|
-
|
108
|
+
|
105
109
|
# 处理不同类型的文件
|
106
110
|
if file_type == 'json':
|
107
111
|
try:
|
@@ -120,46 +124,46 @@ def _load_file_content(file_path: str, is_template: bool = False,
|
|
120
124
|
|
121
125
|
def _process_request_config(config: Dict[str, Any], test_context: TestContext = None) -> Dict[str, Any]:
|
122
126
|
"""处理请求配置,检查并处理文件引用
|
123
|
-
|
127
|
+
|
124
128
|
Args:
|
125
129
|
config: 请求配置
|
126
|
-
|
130
|
+
|
127
131
|
Returns:
|
128
132
|
处理后的请求配置
|
129
133
|
"""
|
130
134
|
if not isinstance(config, dict):
|
131
135
|
return config
|
132
|
-
|
136
|
+
|
133
137
|
# 处理request部分
|
134
138
|
if 'request' in config and isinstance(config['request'], dict):
|
135
139
|
request = config['request']
|
136
|
-
|
140
|
+
|
137
141
|
# 处理json字段
|
138
142
|
if 'json' in request:
|
139
143
|
request['json'] = _process_file_reference(request['json'], test_context=test_context)
|
140
|
-
|
144
|
+
|
141
145
|
# 处理data字段
|
142
146
|
if 'data' in request:
|
143
147
|
request['data'] = _process_file_reference(request['data'], test_context=test_context)
|
144
|
-
|
148
|
+
|
145
149
|
# 处理headers字段
|
146
150
|
if 'headers' in request:
|
147
151
|
request['headers'] = _process_file_reference(request['headers'], test_context=test_context)
|
148
|
-
|
152
|
+
|
149
153
|
return config
|
150
154
|
|
151
155
|
|
152
156
|
def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interval=None):
|
153
157
|
"""标准化断言重试配置
|
154
|
-
|
158
|
+
|
155
159
|
将不同来源的重试配置(命令行参数、retry配置、retry_assertions配置)
|
156
160
|
统一转换为标准化的重试配置对象。
|
157
|
-
|
161
|
+
|
158
162
|
Args:
|
159
163
|
config: 原始配置字典
|
160
164
|
assert_retry_count: 命令行级别的重试次数参数
|
161
165
|
assert_retry_interval: 命令行级别的重试间隔参数
|
162
|
-
|
166
|
+
|
163
167
|
Returns:
|
164
168
|
标准化的重试配置字典,格式为:
|
165
169
|
{
|
@@ -180,7 +184,7 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
|
|
180
184
|
'indices': [], # 默认不指定要重试的断言索引
|
181
185
|
'specific': {} # 默认不指定特定断言的重试配置
|
182
186
|
}
|
183
|
-
|
187
|
+
|
184
188
|
# 处理命令行参数
|
185
189
|
if assert_retry_count and int(assert_retry_count) > 0:
|
186
190
|
standard_retry_config['enabled'] = True
|
@@ -188,12 +192,12 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
|
|
188
192
|
standard_retry_config['all'] = True # 命令行参数会重试所有断言
|
189
193
|
if assert_retry_interval:
|
190
194
|
standard_retry_config['interval'] = float(assert_retry_interval)
|
191
|
-
|
195
|
+
|
192
196
|
# 处理专用retry_assertions配置
|
193
197
|
if 'retry_assertions' in config and config['retry_assertions']:
|
194
198
|
retry_assertions = config['retry_assertions']
|
195
199
|
standard_retry_config['enabled'] = True
|
196
|
-
|
200
|
+
|
197
201
|
if 'count' in retry_assertions:
|
198
202
|
standard_retry_config['count'] = retry_assertions['count']
|
199
203
|
if 'interval' in retry_assertions:
|
@@ -204,7 +208,7 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
|
|
204
208
|
standard_retry_config['indices'] = retry_assertions['indices']
|
205
209
|
if 'specific' in retry_assertions:
|
206
210
|
standard_retry_config['specific'] = retry_assertions['specific']
|
207
|
-
|
211
|
+
|
208
212
|
# 处理传统retry配置(如果专用配置不存在)
|
209
213
|
elif 'retry' in config and config['retry']:
|
210
214
|
retry_config = config['retry']
|
@@ -214,7 +218,7 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
|
|
214
218
|
standard_retry_config['all'] = True # 传统配置会重试所有断言
|
215
219
|
if 'interval' in retry_config:
|
216
220
|
standard_retry_config['interval'] = retry_config['interval']
|
217
|
-
|
221
|
+
|
218
222
|
return standard_retry_config
|
219
223
|
|
220
224
|
|
@@ -223,29 +227,27 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
|
|
223
227
|
{'name': '配置', 'mapping': 'config', 'description': '包含请求、捕获和断言的YAML配置'},
|
224
228
|
{'name': '会话', 'mapping': 'session', 'description': '会话名称,用于在多个请求间保持会话状态'},
|
225
229
|
{'name': '保存响应', 'mapping': 'save_response', 'description': '将完整响应保存到指定变量名中'},
|
226
|
-
{'name': '
|
227
|
-
{'name': '重试间隔', 'mapping': 'retry_interval', 'description': '重试间隔时间(秒)'},
|
230
|
+
{'name': '禁用授权', 'mapping': 'disable_auth', 'description': '禁用客户端配置中的授权机制,默认为false'},
|
228
231
|
{'name': '模板', 'mapping': 'template', 'description': '使用YAML变量文件中定义的请求模板'},
|
229
232
|
{'name': '断言重试次数', 'mapping': 'assert_retry_count', 'description': '断言失败时的重试次数'},
|
230
233
|
{'name': '断言重试间隔', 'mapping': 'assert_retry_interval', 'description': '断言重试间隔时间(秒)'}
|
231
234
|
])
|
232
235
|
def http_request(context, **kwargs):
|
233
236
|
"""执行HTTP请求
|
234
|
-
|
237
|
+
|
235
238
|
根据YAML配置发送HTTP请求,支持客户端配置、会话管理、响应捕获和断言。
|
236
|
-
|
239
|
+
|
237
240
|
Args:
|
238
241
|
context: 测试上下文
|
239
242
|
client: 客户端名称
|
240
243
|
config: YAML配置
|
241
244
|
session: 会话名称
|
242
245
|
save_response: 保存响应的变量名
|
243
|
-
|
244
|
-
retry_interval: 重试间隔
|
246
|
+
disable_auth: 禁用客户端配置中的授权机制
|
245
247
|
template: 模板名称
|
246
248
|
assert_retry_count: 断言失败时的重试次数
|
247
249
|
assert_retry_interval: 断言重试间隔时间(秒)
|
248
|
-
|
250
|
+
|
249
251
|
Returns:
|
250
252
|
捕获的变量字典或响应对象
|
251
253
|
"""
|
@@ -253,22 +255,21 @@ def http_request(context, **kwargs):
|
|
253
255
|
config = kwargs.get('config', '{}')
|
254
256
|
session_name = kwargs.get('session')
|
255
257
|
save_response = kwargs.get('save_response')
|
256
|
-
|
257
|
-
retry_interval = kwargs.get('retry_interval')
|
258
|
+
disable_auth = kwargs.get('disable_auth', False)
|
258
259
|
template_name = kwargs.get('template')
|
259
260
|
assert_retry_count = kwargs.get('assert_retry_count')
|
260
261
|
assert_retry_interval = kwargs.get('assert_retry_interval')
|
261
|
-
|
262
|
+
|
262
263
|
with allure.step(f"发送HTTP请求 (客户端: {client_name}{', 会话: ' + session_name if session_name else ''})"):
|
263
264
|
# 处理模板
|
264
265
|
if template_name:
|
265
266
|
# 从YAML变量中获取模板
|
266
267
|
http_templates = yaml_vars.get_variable("http_templates") or {}
|
267
268
|
template = http_templates.get(template_name)
|
268
|
-
|
269
|
+
|
269
270
|
if not template:
|
270
271
|
raise ValueError(f"未找到名为 '{template_name}' 的HTTP请求模板")
|
271
|
-
|
272
|
+
|
272
273
|
# 解析配置并合并模板
|
273
274
|
if isinstance(config, str):
|
274
275
|
# 先进行变量替换,再解析YAML
|
@@ -277,7 +278,7 @@ def http_request(context, **kwargs):
|
|
277
278
|
config = replacer.replace_in_string(config)
|
278
279
|
try:
|
279
280
|
user_config = yaml.safe_load(config) if config else {}
|
280
|
-
|
281
|
+
|
281
282
|
# 深度合并
|
282
283
|
merged_config = _deep_merge(template.copy(), user_config)
|
283
284
|
config = merged_config
|
@@ -289,7 +290,7 @@ def http_request(context, **kwargs):
|
|
289
290
|
from pytest_dsl.core.variable_utils import VariableReplacer
|
290
291
|
replacer = VariableReplacer(test_context=context)
|
291
292
|
config = replacer.replace_in_string(config)
|
292
|
-
|
293
|
+
|
293
294
|
# 解析YAML配置
|
294
295
|
if isinstance(config, str):
|
295
296
|
try:
|
@@ -299,7 +300,7 @@ def http_request(context, **kwargs):
|
|
299
300
|
|
300
301
|
# 统一处理重试配置
|
301
302
|
retry_config = _normalize_retry_config(config, assert_retry_count, assert_retry_interval)
|
302
|
-
|
303
|
+
|
303
304
|
# 为了兼容性,将标准化后的重试配置写回到配置中
|
304
305
|
if retry_config['enabled']:
|
305
306
|
config['retry_assertions'] = {
|
@@ -309,22 +310,22 @@ def http_request(context, **kwargs):
|
|
309
310
|
'indices': retry_config['indices'],
|
310
311
|
'specific': retry_config['specific']
|
311
312
|
}
|
312
|
-
|
313
|
+
|
313
314
|
config = _process_request_config(config, test_context=context)
|
314
|
-
|
315
|
+
|
315
316
|
# 创建HTTP请求对象
|
316
317
|
http_req = HTTPRequest(config, client_name, session_name)
|
317
|
-
|
318
|
+
|
318
319
|
# 执行请求
|
319
|
-
response = http_req.execute()
|
320
|
-
|
320
|
+
response = http_req.execute(disable_auth=disable_auth)
|
321
|
+
|
321
322
|
# 处理捕获
|
322
323
|
captured_values = http_req.captured_values
|
323
|
-
|
324
|
+
|
324
325
|
# 将捕获的变量注册到上下文
|
325
326
|
for var_name, value in captured_values.items():
|
326
327
|
context.set(var_name, value)
|
327
|
-
|
328
|
+
|
328
329
|
# 保存完整响应(如果需要)
|
329
330
|
if save_response:
|
330
331
|
context.set(save_response, response)
|
@@ -337,18 +338,60 @@ def http_request(context, **kwargs):
|
|
337
338
|
else:
|
338
339
|
# 不需要重试,直接断言
|
339
340
|
http_req.process_asserts()
|
340
|
-
|
341
|
-
#
|
342
|
-
|
341
|
+
|
342
|
+
# 获取会话状态(如果使用了会话)
|
343
|
+
session_state = None
|
344
|
+
if session_name:
|
345
|
+
try:
|
346
|
+
from pytest_dsl.core.http_client import http_client_manager
|
347
|
+
session_client = http_client_manager.get_session(session_name, client_name)
|
348
|
+
if session_client and session_client._session:
|
349
|
+
session_state = {
|
350
|
+
"cookies": dict(session_client._session.cookies),
|
351
|
+
"headers": dict(session_client._session.headers)
|
352
|
+
}
|
353
|
+
except Exception as e:
|
354
|
+
# 会话状态获取失败不影响主要功能
|
355
|
+
logger.warning(f"获取会话状态失败: {str(e)}")
|
356
|
+
|
357
|
+
# 准备响应数据(如果需要保存响应)
|
358
|
+
response_data = None
|
359
|
+
if save_response:
|
360
|
+
# 确保响应数据是可序列化的
|
361
|
+
try:
|
362
|
+
import json
|
363
|
+
json.dumps(response.__dict__)
|
364
|
+
response_data = response.__dict__
|
365
|
+
except (TypeError, AttributeError):
|
366
|
+
# 如果无法序列化,转换为基本信息
|
367
|
+
response_data = {
|
368
|
+
"status_code": getattr(response, 'status_code', None),
|
369
|
+
"headers": dict(getattr(response, 'headers', {})),
|
370
|
+
"text": getattr(response, 'text', ''),
|
371
|
+
"url": getattr(response, 'url', '')
|
372
|
+
}
|
373
|
+
|
374
|
+
# 统一返回格式 - 支持远程关键字模式
|
375
|
+
return {
|
376
|
+
"result": captured_values, # 主要返回值保持兼容
|
377
|
+
"captures": captured_values, # 明确的捕获变量
|
378
|
+
"session_state": {session_name: session_state} if session_state else {},
|
379
|
+
"response": response_data, # 完整响应(如果需要)
|
380
|
+
"metadata": {
|
381
|
+
"response_time": getattr(response, 'elapsed', None),
|
382
|
+
"status_code": getattr(response, 'status_code', None),
|
383
|
+
"url": getattr(response, 'url', '')
|
384
|
+
}
|
385
|
+
}
|
343
386
|
|
344
387
|
|
345
388
|
def _deep_merge(dict1, dict2):
|
346
389
|
"""深度合并两个字典
|
347
|
-
|
390
|
+
|
348
391
|
Args:
|
349
392
|
dict1: 基础字典(会被修改)
|
350
393
|
dict2: 要合并的字典(优先级更高)
|
351
|
-
|
394
|
+
|
352
395
|
Returns:
|
353
396
|
合并后的字典
|
354
397
|
"""
|
@@ -362,7 +405,7 @@ def _deep_merge(dict1, dict2):
|
|
362
405
|
|
363
406
|
def _process_assertions_with_unified_retry(http_req, retry_config):
|
364
407
|
"""使用统一的重试配置处理断言
|
365
|
-
|
408
|
+
|
366
409
|
Args:
|
367
410
|
http_req: HTTP请求对象
|
368
411
|
retry_config: 标准化的重试配置
|
@@ -379,21 +422,21 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
379
422
|
name="断言验证失败详情",
|
380
423
|
attachment_type=allure.attachment_type.TEXT
|
381
424
|
)
|
382
|
-
|
425
|
+
|
383
426
|
# 添加一个特殊的标记到配置中,表示我们只想收集失败的断言而不抛出异常
|
384
427
|
original_config = http_req.config.copy() if isinstance(http_req.config, dict) else {}
|
385
|
-
|
428
|
+
|
386
429
|
# 创建一个临时副本
|
387
430
|
temp_config = original_config.copy()
|
388
|
-
|
431
|
+
|
389
432
|
# 添加特殊标记,用于指示http_request.py中的process_asserts在处理fail时不抛出异常
|
390
433
|
# 注意:这需要对应修改HTTPRequest.process_asserts方法
|
391
434
|
temp_config['_collect_failed_assertions_only'] = True
|
392
|
-
|
435
|
+
|
393
436
|
try:
|
394
437
|
# 临时替换配置
|
395
438
|
http_req.config = temp_config
|
396
|
-
|
439
|
+
|
397
440
|
# 重新运行断言,这次只收集失败的断言而不抛出异常
|
398
441
|
_, failed_retryable_assertions = http_req.process_asserts()
|
399
442
|
except Exception as collect_err:
|
@@ -407,23 +450,23 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
407
450
|
finally:
|
408
451
|
# 恢复原始配置
|
409
452
|
http_req.config = original_config
|
410
|
-
|
453
|
+
|
411
454
|
# 有断言失败,判断是否有需要重试的断言
|
412
455
|
if not failed_retryable_assertions:
|
413
456
|
# 没有可重试的断言,重新抛出原始异常
|
414
457
|
raise
|
415
|
-
|
458
|
+
|
416
459
|
# 过滤需要重试的断言
|
417
460
|
retryable_assertions = []
|
418
|
-
|
461
|
+
|
419
462
|
for failed_assertion in failed_retryable_assertions:
|
420
463
|
assertion_idx = failed_assertion['index']
|
421
|
-
|
464
|
+
|
422
465
|
# 判断该断言是否应该重试
|
423
466
|
should_retry = False
|
424
467
|
specific_retry_count = retry_config['count']
|
425
468
|
specific_retry_interval = retry_config['interval']
|
426
|
-
|
469
|
+
|
427
470
|
# 检查特定断言配置
|
428
471
|
if str(assertion_idx) in retry_config['specific']:
|
429
472
|
should_retry = True
|
@@ -439,39 +482,39 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
439
482
|
# 检查是否重试所有
|
440
483
|
elif retry_config['all']:
|
441
484
|
should_retry = True
|
442
|
-
|
485
|
+
|
443
486
|
# 如果应该重试,添加到可重试断言列表
|
444
487
|
if should_retry:
|
445
488
|
# 添加重试配置到断言对象
|
446
489
|
failed_assertion['retry_count'] = specific_retry_count
|
447
490
|
failed_assertion['retry_interval'] = specific_retry_interval
|
448
491
|
retryable_assertions.append(failed_assertion)
|
449
|
-
|
492
|
+
|
450
493
|
# 如果没有可重试的断言,重新抛出异常
|
451
494
|
if not retryable_assertions:
|
452
495
|
raise
|
453
|
-
|
496
|
+
|
454
497
|
# 记录哪些断言会被重试
|
455
498
|
retry_info = "\n".join([
|
456
|
-
f"{i+1}. {a['type']} " +
|
457
|
-
(f"[{a['path']}]" if a['path'] else "") +
|
458
|
-
f": 重试 {a['retry_count']} 次,间隔 {a['retry_interval']} 秒"
|
499
|
+
f"{i+1}. {a['type']} " +
|
500
|
+
(f"[{a['path']}]" if a['path'] else "") +
|
501
|
+
f": 重试 {a['retry_count']} 次,间隔 {a['retry_interval']} 秒"
|
459
502
|
for i, a in enumerate(retryable_assertions)
|
460
503
|
])
|
461
|
-
|
504
|
+
|
462
505
|
allure.attach(
|
463
506
|
f"找到 {len(retryable_assertions)} 个可重试的断言:\n\n{retry_info}",
|
464
507
|
name="重试断言列表",
|
465
508
|
attachment_type=allure.attachment_type.TEXT
|
466
509
|
)
|
467
|
-
|
510
|
+
|
468
511
|
# 开始重试循环
|
469
512
|
max_retry_count = retry_config['count']
|
470
|
-
|
513
|
+
|
471
514
|
# 找出所有断言中最大的重试次数
|
472
515
|
for retryable_assertion in retryable_assertions:
|
473
516
|
max_retry_count = max(max_retry_count, retryable_assertion.get('retry_count', 3))
|
474
|
-
|
517
|
+
|
475
518
|
# 进行断言重试
|
476
519
|
for attempt in range(1, max_retry_count + 1): # 从1开始,因为第0次已经尝试过了
|
477
520
|
# 等待重试间隔
|
@@ -480,41 +523,44 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
480
523
|
retry_interval = retry_config['interval']
|
481
524
|
for assertion in retryable_assertions:
|
482
525
|
retry_interval = max(retry_interval, assertion.get('retry_interval', 1.0))
|
483
|
-
|
526
|
+
|
484
527
|
allure.attach(
|
485
528
|
f"重试 {len(retryable_assertions)} 个断言\n"
|
486
529
|
f"等待间隔: {retry_interval}秒",
|
487
530
|
name="断言重试信息",
|
488
531
|
attachment_type=allure.attachment_type.TEXT
|
489
532
|
)
|
490
|
-
|
533
|
+
|
491
534
|
time.sleep(retry_interval)
|
492
|
-
|
535
|
+
|
493
536
|
# 重新发送请求
|
494
537
|
http_req.execute()
|
495
|
-
|
538
|
+
|
496
539
|
# 过滤出仍在重试范围内的断言
|
497
540
|
still_retryable_assertions = []
|
498
541
|
for assertion in retryable_assertions:
|
499
542
|
assertion_retry_count = assertion.get('retry_count', 3)
|
500
|
-
|
543
|
+
|
501
544
|
# 如果断言的重试次数大于当前尝试次数,继续重试该断言
|
502
545
|
if attempt < assertion_retry_count:
|
503
546
|
still_retryable_assertions.append(assertion)
|
504
|
-
|
547
|
+
|
505
548
|
# 如果没有可以继续重试的断言,跳出循环
|
506
549
|
if not still_retryable_assertions:
|
507
550
|
break
|
508
|
-
|
551
|
+
|
509
552
|
# 只重试那些仍在重试范围内的断言
|
510
553
|
try:
|
511
554
|
# 从原始断言配置中提取出需要重试的断言
|
512
555
|
retry_assertion_indexes = [a['index'] for a in still_retryable_assertions]
|
513
556
|
retry_assertions = [http_req.config.get('asserts', [])[idx] for idx in retry_assertion_indexes]
|
514
|
-
|
515
|
-
#
|
516
|
-
|
517
|
-
|
557
|
+
|
558
|
+
# 创建索引映射:新索引 -> 原始索引
|
559
|
+
index_mapping = {new_idx: orig_idx for new_idx, orig_idx in enumerate(retry_assertion_indexes)}
|
560
|
+
|
561
|
+
# 只处理需要重试的断言,传递索引映射
|
562
|
+
results, new_failed_assertions = http_req.process_asserts(specific_asserts=retry_assertions, index_mapping=index_mapping)
|
563
|
+
|
518
564
|
# 如果所有断言都通过了,检查全部断言
|
519
565
|
if not new_failed_assertions:
|
520
566
|
# 执行一次完整的断言检查,确保所有断言都通过
|
@@ -534,10 +580,10 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
534
580
|
attachment_type=allure.attachment_type.TEXT
|
535
581
|
)
|
536
582
|
continue
|
537
|
-
|
583
|
+
|
538
584
|
# 更新失败的可重试断言列表
|
539
585
|
retryable_assertions = new_failed_assertions
|
540
|
-
|
586
|
+
|
541
587
|
except AssertionError as retry_err:
|
542
588
|
# 重试时断言失败,记录后继续重试
|
543
589
|
allure.attach(
|
@@ -546,7 +592,7 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
546
592
|
attachment_type=allure.attachment_type.TEXT
|
547
593
|
)
|
548
594
|
continue
|
549
|
-
|
595
|
+
|
550
596
|
# 重试次数用完,执行一次完整的断言以获取最终结果和错误
|
551
597
|
# 这会抛出异常,如果仍然有断言失败
|
552
598
|
allure.attach(
|
@@ -554,7 +600,7 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
554
600
|
name="重试完成",
|
555
601
|
attachment_type=allure.attachment_type.TEXT
|
556
602
|
)
|
557
|
-
|
603
|
+
|
558
604
|
try:
|
559
605
|
results, _ = http_req.process_asserts()
|
560
606
|
return results
|
@@ -571,48 +617,4 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
|
|
571
617
|
raise AssertionError(enhanced_error) from final_err
|
572
618
|
|
573
619
|
|
574
|
-
#
|
575
|
-
# 以下函数保留用于向后兼容,但内部逻辑已更改为调用统一重试函数
|
576
|
-
def _process_assertions_with_retry(http_req, retry_count, retry_interval):
|
577
|
-
"""处理断言并支持重试(向后兼容函数)
|
578
|
-
|
579
|
-
Args:
|
580
|
-
http_req: HTTP请求对象
|
581
|
-
retry_count: 重试次数
|
582
|
-
retry_interval: 重试间隔(秒)
|
583
|
-
"""
|
584
|
-
# 创建统一的重试配置
|
585
|
-
retry_config = {
|
586
|
-
'enabled': True,
|
587
|
-
'count': retry_count,
|
588
|
-
'interval': retry_interval,
|
589
|
-
'all': True,
|
590
|
-
'indices': [],
|
591
|
-
'specific': {}
|
592
|
-
}
|
593
|
-
|
594
|
-
# 使用统一的重试处理函数
|
595
|
-
return _process_assertions_with_unified_retry(http_req, retry_config)
|
596
|
-
|
597
|
-
|
598
|
-
def _process_config_based_assertions_with_retry(http_req):
|
599
|
-
"""基于配置处理断言重试(向后兼容函数)
|
600
|
-
|
601
|
-
Args:
|
602
|
-
http_req: HTTP请求对象
|
603
|
-
"""
|
604
|
-
# 从配置中获取重试信息
|
605
|
-
retry_assertions_config = http_req.config.get('retry_assertions', {})
|
606
|
-
|
607
|
-
# 创建统一的重试配置
|
608
|
-
retry_config = {
|
609
|
-
'enabled': True,
|
610
|
-
'count': retry_assertions_config.get('count', 3),
|
611
|
-
'interval': retry_assertions_config.get('interval', 1.0),
|
612
|
-
'all': retry_assertions_config.get('all', False),
|
613
|
-
'indices': retry_assertions_config.get('indices', []),
|
614
|
-
'specific': retry_assertions_config.get('specific', {})
|
615
|
-
}
|
616
|
-
|
617
|
-
# 使用统一的重试处理函数
|
618
|
-
return _process_assertions_with_unified_retry(http_req, retry_config)
|
620
|
+
# 注意:旧的重试函数已被移除,现在使用统一的重试机制
|