pytest-dsl 0.5.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 +56 -11
  5. pytest_dsl/core/http_request.py +272 -221
  6. pytest_dsl/core/lexer.py +3 -13
  7. pytest_dsl/core/parser.py +2 -2
  8. pytest_dsl/core/parsetab.py +69 -69
  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 +58 -56
  30. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/METADATA +138 -9
  31. pytest_dsl-0.6.0.dist-info/RECORD +68 -0
  32. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/WHEEL +1 -1
  33. pytest_dsl-0.5.0.dist-info/RECORD +0 -68
  34. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/entry_points.txt +0 -0
  35. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/licenses/LICENSE +0 -0
  36. {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/top_level.txt +0 -0
@@ -14,24 +14,24 @@ from pytest_dsl.core.keyword_manager import keyword_manager
14
14
 
15
15
  def _extract_jsonpath(json_data: Union[Dict, List], path: str) -> Any:
16
16
  """使用JSONPath从JSON数据中提取值
17
-
17
+
18
18
  Args:
19
19
  json_data: 要提取数据的JSON对象或数组
20
20
  path: JSONPath表达式
21
-
21
+
22
22
  Returns:
23
23
  提取的值或值列表
24
-
24
+
25
25
  Raises:
26
26
  ValueError: 如果JSONPath表达式无效或找不到匹配项
27
27
  """
28
28
  try:
29
29
  if isinstance(json_data, str):
30
30
  json_data = json.loads(json_data)
31
-
31
+
32
32
  jsonpath_expr = jsonpath.parse(path)
33
33
  matches = [match.value for match in jsonpath_expr.find(json_data)]
34
-
34
+
35
35
  if not matches:
36
36
  return None
37
37
  elif len(matches) == 1:
@@ -44,12 +44,12 @@ def _extract_jsonpath(json_data: Union[Dict, List], path: str) -> Any:
44
44
 
45
45
  def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
46
46
  """比较两个值
47
-
47
+
48
48
  Args:
49
49
  actual: 实际值
50
50
  expected: 预期值
51
51
  operator: 比较运算符 (==, !=, >, <, >=, <=, contains, not_contains, matches, and, or, not)
52
-
52
+
53
53
  Returns:
54
54
  比较结果 (True/False)
55
55
  """
@@ -101,32 +101,32 @@ def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
101
101
  ])
102
102
  def assert_condition(**kwargs):
103
103
  """执行表达式断言
104
-
104
+
105
105
  Args:
106
106
  condition: 断言条件表达式
107
107
  message: 断言失败时的错误消息
108
-
108
+
109
109
  Returns:
110
110
  断言结果 (True/False)
111
-
111
+
112
112
  Raises:
113
113
  AssertionError: 如果断言失败
114
114
  """
115
115
  condition = kwargs.get('condition')
116
116
  message = kwargs.get('message', '断言失败')
117
117
  context = kwargs.get('context')
118
-
118
+
119
119
  # 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains, matches, in, and, or, not
120
120
  # 格式: "left_value operator right_value" 或 "boolean_expression"
121
121
  operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains", "matches", "in", "and", "or", "not"]
122
-
122
+
123
123
  # 先检查是否包含这些操作符
124
124
  operator_used = None
125
125
  for op in operators:
126
126
  if f" {op} " in condition:
127
127
  operator_used = op
128
128
  break
129
-
129
+
130
130
  if not operator_used:
131
131
  # 如果没有找到操作符,尝试作为布尔表达式直接求值
132
132
  try:
@@ -142,36 +142,36 @@ def assert_condition(**kwargs):
142
142
  return True
143
143
  except Exception as e:
144
144
  raise AssertionError(f"{message}. 无法解析条件表达式: {condition}. 错误: {str(e)}")
145
-
145
+
146
146
  # 解析左值和右值
147
147
  left_value, right_value = condition.split(f" {operator_used} ", 1)
148
148
  left_value = left_value.strip()
149
149
  right_value = right_value.strip()
150
-
150
+
151
151
  # 移除引号(如果有)
152
152
  if left_value.startswith('"') and left_value.endswith('"'):
153
153
  left_value = left_value[1:-1]
154
154
  elif left_value.startswith("'") and left_value.endswith("'"):
155
155
  left_value = left_value[1:-1]
156
-
156
+
157
157
  if right_value.startswith('"') and right_value.endswith('"'):
158
158
  right_value = right_value[1:-1]
159
159
  elif right_value.startswith("'") and right_value.endswith("'"):
160
160
  right_value = right_value[1:-1]
161
-
161
+
162
162
  # 记录原始值(用于调试)
163
163
  allure.attach(
164
164
  f"原始左值: {left_value}\n原始右值: {right_value}\n操作符: {operator_used}",
165
165
  name="表达式解析",
166
166
  attachment_type=allure.attachment_type.TEXT
167
167
  )
168
-
168
+
169
169
  # 对左值进行变量替换和表达式计算
170
170
  try:
171
171
  # 如果左值包含变量引用,先进行变量替换
172
172
  if '${' in left_value:
173
173
  left_value = context.executor.variable_replacer.replace_in_string(left_value)
174
-
174
+
175
175
  # 检查是否需要计算表达式
176
176
  if any(op in str(left_value) for op in ['+', '-', '*', '/', '%', '(', ')']):
177
177
  try:
@@ -187,7 +187,7 @@ def assert_condition(**kwargs):
187
187
  attachment_type=allure.attachment_type.TEXT
188
188
  )
189
189
  raise ValueError(f"表达式计算错误: {str(e)}")
190
-
190
+
191
191
  # 处理布尔值字符串和数字字符串
192
192
  if isinstance(left_value, str):
193
193
  if left_value.lower() in ('true', 'false'):
@@ -210,13 +210,13 @@ def assert_condition(**kwargs):
210
210
  attachment_type=allure.attachment_type.TEXT
211
211
  )
212
212
  raise
213
-
213
+
214
214
  # 对右值进行变量替换和表达式计算
215
215
  try:
216
216
  # 如果右值包含变量引用,先进行变量替换
217
217
  if '${' in right_value:
218
218
  right_value = context.executor.variable_replacer.replace_in_string(right_value)
219
-
219
+
220
220
  # 检查是否需要计算表达式
221
221
  if any(op in str(right_value) for op in ['+', '-', '*', '/', '%', '(', ')']):
222
222
  try:
@@ -232,7 +232,7 @@ def assert_condition(**kwargs):
232
232
  attachment_type=allure.attachment_type.TEXT
233
233
  )
234
234
  raise ValueError(f"表达式计算错误: {str(e)}")
235
-
235
+
236
236
  # 处理布尔值字符串
237
237
  if isinstance(right_value, str):
238
238
  if right_value.lower() in ('true', 'false'):
@@ -246,7 +246,7 @@ def assert_condition(**kwargs):
246
246
  attachment_type=allure.attachment_type.TEXT
247
247
  )
248
248
  raise
249
-
249
+
250
250
  # 类型转换和特殊处理
251
251
  if operator_used == "contains":
252
252
  # 特殊处理字符串包含操作
@@ -285,7 +285,7 @@ def assert_condition(**kwargs):
285
285
  # 尝试将右值解析为列表或字典
286
286
  if isinstance(right_value, str):
287
287
  right_value = eval(right_value)
288
-
288
+
289
289
  # 如果是字典,检查键
290
290
  if isinstance(right_value, dict):
291
291
  result = left_value in right_value.keys()
@@ -310,17 +310,17 @@ def assert_condition(**kwargs):
310
310
  right_value = float(right_value)
311
311
  except:
312
312
  pass
313
-
313
+
314
314
  # 记录类型转换后的值(用于调试)
315
315
  allure.attach(
316
316
  f"类型转换后左值: {left_value} ({type(left_value).__name__})\n类型转换后右值: {right_value} ({type(right_value).__name__})",
317
317
  name="类型转换",
318
318
  attachment_type=allure.attachment_type.TEXT
319
319
  )
320
-
320
+
321
321
  # 执行比较
322
322
  result = _compare_values(left_value, right_value, operator_used)
323
-
323
+
324
324
  # 记录和处理断言结果
325
325
  if not result:
326
326
  error_details = f"""
@@ -337,7 +337,7 @@ def assert_condition(**kwargs):
337
337
  attachment_type=allure.attachment_type.TEXT
338
338
  )
339
339
  raise AssertionError(error_details)
340
-
340
+
341
341
  # 记录成功的断言
342
342
  allure.attach(
343
343
  f"实际值: {left_value}\n预期值: {right_value}\n操作符: {operator_used}",
@@ -356,17 +356,17 @@ def assert_condition(**kwargs):
356
356
  ])
357
357
  def assert_json(**kwargs):
358
358
  """执行JSON断言
359
-
359
+
360
360
  Args:
361
361
  json_data: JSON数据(字符串或对象)
362
362
  jsonpath: JSONPath表达式
363
363
  expected_value: 预期的值
364
364
  operator: 比较操作符,默认为"=="
365
365
  message: 断言失败时的错误消息
366
-
366
+
367
367
  Returns:
368
368
  断言结果 (True/False)
369
-
369
+
370
370
  Raises:
371
371
  AssertionError: 如果断言失败
372
372
  ValueError: 如果JSONPath无效或找不到匹配项
@@ -376,27 +376,27 @@ def assert_json(**kwargs):
376
376
  expected_value = kwargs.get('expected_value')
377
377
  operator = kwargs.get('operator', '==')
378
378
  message = kwargs.get('message', 'JSON断言失败')
379
-
379
+
380
380
  # 解析JSON(如果需要)
381
381
  if isinstance(json_data, str):
382
382
  try:
383
383
  json_data = json.loads(json_data)
384
384
  except json.JSONDecodeError as e:
385
385
  raise ValueError(f"无效的JSON数据: {str(e)}")
386
-
386
+
387
387
  # 使用JSONPath提取值
388
388
  actual_value = _extract_jsonpath(json_data, path)
389
-
389
+
390
390
  # 记录提取的值
391
391
  allure.attach(
392
392
  f"JSONPath: {path}\n提取值: {actual_value}",
393
393
  name="JSONPath提取结果",
394
394
  attachment_type=allure.attachment_type.TEXT
395
395
  )
396
-
396
+
397
397
  # 比较值
398
398
  result = _compare_values(actual_value, expected_value, operator)
399
-
399
+
400
400
  # 记录和处理断言结果
401
401
  if not result:
402
402
  allure.attach(
@@ -405,7 +405,7 @@ def assert_json(**kwargs):
405
405
  attachment_type=allure.attachment_type.TEXT
406
406
  )
407
407
  raise AssertionError(message)
408
-
408
+
409
409
  # 记录成功的断言
410
410
  allure.attach(
411
411
  f"实际值: {actual_value}\n预期值: {expected_value}\n操作符: {operator}",
@@ -422,21 +422,23 @@ def assert_json(**kwargs):
422
422
  ])
423
423
  def extract_json(**kwargs):
424
424
  """从JSON数据中提取值并保存到变量
425
-
425
+
426
426
  Args:
427
427
  json_data: JSON数据(字符串或对象)
428
428
  jsonpath: JSONPath表达式
429
429
  variable: 存储提取值的变量名
430
-
430
+ context: 测试上下文
431
+
431
432
  Returns:
432
- 提取的值
433
-
433
+ 提取的值或包含提取值的字典(远程模式)
434
+
434
435
  Raises:
435
436
  ValueError: 如果JSONPath无效或找不到匹配项
436
437
  """
437
438
  json_data = kwargs.get('json_data')
438
439
  path = kwargs.get('jsonpath')
439
440
  variable = kwargs.get('variable')
441
+ context = kwargs.get('context')
440
442
 
441
443
  # 解析JSON(如果需要)
442
444
  if isinstance(json_data, str):
@@ -444,19 +446,31 @@ def extract_json(**kwargs):
444
446
  json_data = json.loads(json_data)
445
447
  except json.JSONDecodeError as e:
446
448
  raise ValueError(f"无效的JSON数据: {str(e)}")
447
-
449
+
448
450
  # 使用JSONPath提取值
449
451
  value = _extract_jsonpath(json_data, path)
450
-
452
+
453
+ # 将提取的值设置到上下文中(本地模式)
454
+ if context and variable:
455
+ context.set(variable, value)
456
+
451
457
  # 记录提取的值
452
458
  allure.attach(
453
459
  f"JSONPath: {path}\n提取值: {value}\n保存到变量: {variable}",
454
460
  name="JSON数据提取",
455
461
  attachment_type=allure.attachment_type.TEXT
456
462
  )
457
-
458
- # 返回提取的值用于变量赋值
459
- return value
463
+
464
+ # 统一返回格式 - 支持远程关键字模式
465
+ return {
466
+ "result": value, # 主要返回值保持兼容
467
+ "captures": {variable: value} if variable else {}, # 明确的捕获变量
468
+ "session_state": {},
469
+ "metadata": {
470
+ "jsonpath": path,
471
+ "variable_name": variable
472
+ }
473
+ }
460
474
 
461
475
 
462
476
  @keyword_manager.register('类型断言', [
@@ -466,22 +480,22 @@ def extract_json(**kwargs):
466
480
  ])
467
481
  def assert_type(**kwargs):
468
482
  """断言值的类型
469
-
483
+
470
484
  Args:
471
485
  value: 要检查的值
472
486
  type: 预期的类型 (string, number, boolean, list, object, null)
473
487
  message: 断言失败时的错误消息
474
-
488
+
475
489
  Returns:
476
490
  断言结果 (True/False)
477
-
491
+
478
492
  Raises:
479
493
  AssertionError: 如果断言失败
480
494
  """
481
495
  value = kwargs.get('value')
482
496
  expected_type = kwargs.get('type')
483
497
  message = kwargs.get('message', '类型断言失败')
484
-
498
+
485
499
  # 检查类型
486
500
  if expected_type == 'string':
487
501
  result = isinstance(value, str)
@@ -508,7 +522,7 @@ def assert_type(**kwargs):
508
522
  result = value is None
509
523
  else:
510
524
  raise ValueError(f"不支持的类型: {expected_type}")
511
-
525
+
512
526
  # 记录和处理断言结果
513
527
  if not result:
514
528
  actual_type = type(value).__name__
@@ -518,7 +532,7 @@ def assert_type(**kwargs):
518
532
  attachment_type=allure.attachment_type.TEXT
519
533
  )
520
534
  raise AssertionError(message)
521
-
535
+
522
536
  # 记录成功的断言
523
537
  allure.attach(
524
538
  f"值: {value}\n类型: {expected_type}",
@@ -536,16 +550,16 @@ def assert_type(**kwargs):
536
550
  ])
537
551
  def compare_values(**kwargs):
538
552
  """比较两个值
539
-
553
+
540
554
  Args:
541
555
  actual: 实际值
542
556
  expected: 预期值
543
557
  operator: 比较操作符,默认为"=="
544
558
  message: 断言失败时的错误消息
545
-
559
+
546
560
  Returns:
547
561
  比较结果 (True/False)
548
-
562
+
549
563
  Raises:
550
564
  AssertionError: 如果比较失败
551
565
  """
@@ -553,7 +567,7 @@ def compare_values(**kwargs):
553
567
  expected = kwargs.get('expected')
554
568
  operator = kwargs.get('operator', '==')
555
569
  message = kwargs.get('message', '数据比较失败')
556
-
570
+
557
571
  # 处理布尔值字符串和表达式
558
572
  if isinstance(actual, str):
559
573
  # 检查是否需要计算表达式
@@ -571,7 +585,7 @@ def compare_values(**kwargs):
571
585
  actual = actual.lower() == 'true'
572
586
  elif actual.lower() in ('yes', 'no', '1', '0', 't', 'f', 'y', 'n'):
573
587
  actual = actual.lower() in ('yes', '1', 't', 'y')
574
-
588
+
575
589
  if isinstance(expected, str):
576
590
  # 检查是否需要计算表达式
577
591
  if any(op in expected for op in ['+', '-', '*', '/', '%', '(', ')']):
@@ -588,10 +602,10 @@ def compare_values(**kwargs):
588
602
  expected = expected.lower() == 'true'
589
603
  elif expected.lower() in ('yes', 'no', '1', '0', 't', 'f', 'y', 'n'):
590
604
  expected = expected.lower() in ('yes', '1', 't', 'y')
591
-
605
+
592
606
  # 比较值
593
607
  result = _compare_values(actual, expected, operator)
594
-
608
+
595
609
  # 记录和处理比较结果
596
610
  if not result:
597
611
  allure.attach(
@@ -600,11 +614,11 @@ def compare_values(**kwargs):
600
614
  attachment_type=allure.attachment_type.TEXT
601
615
  )
602
616
  raise AssertionError(message)
603
-
617
+
604
618
  # 记录成功的比较
605
619
  allure.attach(
606
620
  f"实际值: {actual}\n预期值: {expected}\n操作符: {operator}",
607
621
  name="数据比较成功",
608
622
  attachment_type=allure.attachment_type.TEXT
609
623
  )
610
- return result
624
+ return result
@@ -12,7 +12,17 @@ from pytest_dsl.core.global_context import global_context
12
12
  def set_global_variable(name, value, context):
13
13
  """设置全局变量"""
14
14
  global_context.set_variable(name, value)
15
- return value
15
+
16
+ # 统一返回格式 - 支持远程关键字模式
17
+ return {
18
+ "result": value, # 主要返回值保持兼容
19
+ "captures": {},
20
+ "session_state": {},
21
+ "metadata": {
22
+ "variable_name": name,
23
+ "operation": "set_global_variable"
24
+ }
25
+ }
16
26
 
17
27
 
18
28
  @keyword_manager.register(
@@ -26,7 +36,17 @@ def get_global_variable(name, context):
26
36
  value = global_context.get_variable(name)
27
37
  if value is None:
28
38
  raise Exception(f"全局变量未定义: {name}")
29
- return value
39
+
40
+ # 统一返回格式 - 支持远程关键字模式
41
+ return {
42
+ "result": value, # 主要返回值保持兼容
43
+ "captures": {},
44
+ "session_state": {},
45
+ "metadata": {
46
+ "variable_name": name,
47
+ "operation": "get_global_variable"
48
+ }
49
+ }
30
50
 
31
51
 
32
52
  @keyword_manager.register(
@@ -38,7 +58,17 @@ def get_global_variable(name, context):
38
58
  def delete_global_variable(name, context):
39
59
  """删除全局变量"""
40
60
  global_context.delete_variable(name)
41
- return True
61
+
62
+ # 统一返回格式 - 支持远程关键字模式
63
+ return {
64
+ "result": True, # 主要返回值保持兼容
65
+ "captures": {},
66
+ "session_state": {},
67
+ "metadata": {
68
+ "variable_name": name,
69
+ "operation": "delete_global_variable"
70
+ }
71
+ }
42
72
 
43
73
 
44
74
  @keyword_manager.register(
@@ -48,4 +78,13 @@ def delete_global_variable(name, context):
48
78
  def clear_all_global_variables(context):
49
79
  """清除所有全局变量"""
50
80
  global_context.clear_all()
51
- return True
81
+
82
+ # 统一返回格式 - 支持远程关键字模式
83
+ return {
84
+ "result": True, # 主要返回值保持兼容
85
+ "captures": {},
86
+ "session_state": {},
87
+ "metadata": {
88
+ "operation": "clear_all_global_variables"
89
+ }
90
+ }
@@ -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,6 +17,9 @@ 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
 
@@ -223,8 +227,7 @@ 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': '断言重试间隔时间(秒)'}
@@ -240,8 +243,7 @@ def http_request(context, **kwargs):
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: 断言重试间隔时间(秒)
@@ -253,8 +255,7 @@ 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')
@@ -316,7 +317,7 @@ def http_request(context, **kwargs):
316
317
  http_req = HTTPRequest(config, client_name, session_name)
317
318
 
318
319
  # 执行请求
319
- response = http_req.execute()
320
+ response = http_req.execute(disable_auth=disable_auth)
320
321
 
321
322
  # 处理捕获
322
323
  captured_values = http_req.captured_values
@@ -338,8 +339,50 @@ def http_request(context, **kwargs):
338
339
  # 不需要重试,直接断言
339
340
  http_req.process_asserts()
340
341
 
341
- # 返回捕获的变量
342
- return captured_values
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):
@@ -512,8 +555,11 @@ def _process_assertions_with_unified_retry(http_req, retry_config):
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
557
 
515
- # 只处理需要重试的断言
516
- results, new_failed_assertions = http_req.process_asserts(specific_asserts=retry_assertions)
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)
517
563
 
518
564
  # 如果所有断言都通过了,检查全部断言
519
565
  if not new_failed_assertions:
@@ -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
+ # 注意:旧的重试函数已被移除,现在使用统一的重试机制