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.
Files changed (36) hide show
  1. pytest_dsl/cli.py +28 -33
  2. pytest_dsl/core/auto_decorator.py +72 -53
  3. pytest_dsl/core/auto_directory.py +8 -5
  4. pytest_dsl/core/dsl_executor.py +211 -53
  5. pytest_dsl/core/http_request.py +272 -221
  6. pytest_dsl/core/lexer.py +14 -13
  7. pytest_dsl/core/parser.py +27 -8
  8. pytest_dsl/core/parsetab.py +71 -66
  9. pytest_dsl/core/plugin_discovery.py +1 -8
  10. pytest_dsl/core/yaml_loader.py +96 -19
  11. pytest_dsl/examples/assert/assertion_example.auto +1 -1
  12. pytest_dsl/examples/assert/boolean_test.auto +2 -2
  13. pytest_dsl/examples/assert/expression_test.auto +1 -1
  14. pytest_dsl/examples/custom/test_advanced_keywords.auto +2 -2
  15. pytest_dsl/examples/custom/test_custom_keywords.auto +2 -2
  16. pytest_dsl/examples/custom/test_default_values.auto +2 -2
  17. pytest_dsl/examples/http/file_reference_test.auto +1 -1
  18. pytest_dsl/examples/http/http_advanced.auto +1 -1
  19. pytest_dsl/examples/http/http_example.auto +1 -1
  20. pytest_dsl/examples/http/http_length_test.auto +1 -1
  21. pytest_dsl/examples/http/http_retry_assertions.auto +1 -1
  22. pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +2 -2
  23. pytest_dsl/examples/http/http_with_yaml.auto +1 -1
  24. pytest_dsl/examples/quickstart/api_basics.auto +1 -1
  25. pytest_dsl/examples/quickstart/assertions.auto +1 -1
  26. pytest_dsl/examples/quickstart/loops.auto +2 -2
  27. pytest_dsl/keywords/assertion_keywords.py +76 -62
  28. pytest_dsl/keywords/global_keywords.py +43 -4
  29. pytest_dsl/keywords/http_keywords.py +141 -139
  30. {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/METADATA +266 -15
  31. pytest_dsl-0.6.0.dist-info/RECORD +68 -0
  32. {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/WHEEL +1 -1
  33. {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/entry_points.txt +1 -0
  34. pytest_dsl-0.4.0.dist-info/RECORD +0 -68
  35. {pytest_dsl-0.4.0.dist-info → pytest_dsl-0.6.0.dist-info}/licenses/LICENSE +0 -0
  36. {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': '重试次数', 'mapping': 'retry_count', 'description': '请求失败时的重试次数'},
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
- retry_count: 重试次数
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
- retry_count = kwargs.get('retry_count')
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
- return captured_values
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
- results, new_failed_assertions = http_req.process_asserts(specific_asserts=retry_assertions)
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
+ # 注意:旧的重试函数已被移除,现在使用统一的重试机制