pytest-dsl 0.15.2__py3-none-any.whl → 0.15.4__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/__init__.py +52 -3
- pytest_dsl/cli.py +8 -1
- pytest_dsl/core/context.py +59 -5
- pytest_dsl/core/custom_keyword_manager.py +14 -8
- pytest_dsl/core/dsl_executor.py +28 -8
- pytest_dsl/core/global_context.py +38 -8
- pytest_dsl/core/http_client.py +29 -5
- pytest_dsl/core/keyword_utils.py +7 -0
- pytest_dsl/core/remote_server_registry.py +333 -0
- pytest_dsl/core/utils.py +34 -27
- pytest_dsl/core/validator.py +71 -1
- pytest_dsl/core/variable_providers.py +202 -0
- pytest_dsl/core/variable_utils.py +45 -38
- pytest_dsl/core/yaml_loader.py +176 -36
- pytest_dsl/keywords/http_keywords.py +9 -5
- pytest_dsl/remote/__init__.py +63 -1
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/METADATA +1 -1
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/RECORD +22 -20
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/top_level.txt +0 -0
pytest_dsl/__init__.py
CHANGED
@@ -12,7 +12,7 @@ pytest-dsl - 基于pytest的DSL测试框架
|
|
12
12
|
- 自定义关键字支持
|
13
13
|
"""
|
14
14
|
|
15
|
-
__version__ = "0.
|
15
|
+
__version__ = "0.15.4"
|
16
16
|
|
17
17
|
# 核心执行器
|
18
18
|
from pytest_dsl.core.dsl_executor import DSLExecutor
|
@@ -47,6 +47,20 @@ from pytest_dsl.core.variable_utils import VariableReplacer
|
|
47
47
|
# 自定义关键字管理器
|
48
48
|
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
49
49
|
|
50
|
+
# 远程服务器注册器
|
51
|
+
from pytest_dsl.core.remote_server_registry import (
|
52
|
+
remote_server_registry, RemoteServerRegistry,
|
53
|
+
register_remote_server_with_variables,
|
54
|
+
create_database_variable_provider,
|
55
|
+
create_config_file_variable_provider
|
56
|
+
)
|
57
|
+
|
58
|
+
# 远程服务器配置加载器
|
59
|
+
from pytest_dsl.core.yaml_loader import (
|
60
|
+
load_remote_servers_from_yaml,
|
61
|
+
register_remote_servers_from_config
|
62
|
+
)
|
63
|
+
|
50
64
|
# 关键字加载器
|
51
65
|
from pytest_dsl.core.keyword_loader import (
|
52
66
|
keyword_loader, KeywordLoader,
|
@@ -61,6 +75,22 @@ from pytest_dsl.core.keyword_utils import (
|
|
61
75
|
generate_html_report
|
62
76
|
)
|
63
77
|
|
78
|
+
# 远程关键字功能
|
79
|
+
try:
|
80
|
+
from pytest_dsl.remote import (
|
81
|
+
remote_keyword_manager, RemoteKeywordManager, RemoteKeywordClient,
|
82
|
+
register_remote_server, register_multiple_servers
|
83
|
+
)
|
84
|
+
_REMOTE_AVAILABLE = True
|
85
|
+
except ImportError:
|
86
|
+
# 如果远程功能依赖不可用,设置为None
|
87
|
+
remote_keyword_manager = None
|
88
|
+
RemoteKeywordManager = None
|
89
|
+
RemoteKeywordClient = None
|
90
|
+
register_remote_server = None
|
91
|
+
register_multiple_servers = None
|
92
|
+
_REMOTE_AVAILABLE = False
|
93
|
+
|
64
94
|
# 便捷导入的别名
|
65
95
|
Executor = DSLExecutor
|
66
96
|
Validator = DSLValidator
|
@@ -106,6 +136,17 @@ __all__ = [
|
|
106
136
|
'Node', 'get_parser', 'get_lexer',
|
107
137
|
'TestContext', 'global_context',
|
108
138
|
'VariableReplacer',
|
139
|
+
|
140
|
+
# 远程关键字功能(如果可用)
|
141
|
+
'remote_keyword_manager', 'RemoteKeywordManager', 'RemoteKeywordClient',
|
142
|
+
'register_remote_server', 'register_multiple_servers',
|
143
|
+
|
144
|
+
# 远程服务器注册器
|
145
|
+
'remote_server_registry', 'RemoteServerRegistry',
|
146
|
+
'register_remote_server_with_variables',
|
147
|
+
'create_database_variable_provider',
|
148
|
+
'create_config_file_variable_provider',
|
149
|
+
'load_remote_servers_from_yaml', 'register_remote_servers_from_config',
|
109
150
|
]
|
110
151
|
|
111
152
|
# 快捷函数
|
@@ -137,7 +178,8 @@ def parse_dsl(content: str) -> Node:
|
|
137
178
|
return parser.parse(content, lexer=lexer)
|
138
179
|
|
139
180
|
|
140
|
-
def execute_dsl(content: str, context: dict = None,
|
181
|
+
def execute_dsl(content: str, context: dict = None,
|
182
|
+
enable_hooks: bool = True) -> any:
|
141
183
|
"""执行DSL内容的便捷函数
|
142
184
|
|
143
185
|
Args:
|
@@ -158,7 +200,8 @@ def execute_dsl(content: str, context: dict = None, enable_hooks: bool = True) -
|
|
158
200
|
return executor.execute(ast)
|
159
201
|
|
160
202
|
|
161
|
-
def register_keyword(name: str, parameters: list = None,
|
203
|
+
def register_keyword(name: str, parameters: list = None,
|
204
|
+
source_type: str = "external",
|
162
205
|
source_name: str = "user_defined"):
|
163
206
|
"""注册关键字的装饰器
|
164
207
|
|
@@ -198,6 +241,12 @@ def check_version_compatibility():
|
|
198
241
|
pass
|
199
242
|
|
200
243
|
|
244
|
+
# 远程功能检查
|
245
|
+
def is_remote_available() -> bool:
|
246
|
+
"""检查远程功能是否可用"""
|
247
|
+
return _REMOTE_AVAILABLE
|
248
|
+
|
249
|
+
|
201
250
|
# 初始化时进行版本检查
|
202
251
|
check_version_compatibility()
|
203
252
|
|
pytest_dsl/cli.py
CHANGED
@@ -165,11 +165,18 @@ def load_yaml_variables(args):
|
|
165
165
|
environment = (os.environ.get('PYTEST_DSL_ENVIRONMENT') or
|
166
166
|
os.environ.get('ENVIRONMENT'))
|
167
167
|
|
168
|
+
# 智能判断是否应该加载默认配置
|
169
|
+
# 如果用户指定了YAML文件或目录,则不自动加载默认配置
|
170
|
+
user_specified_files = bool(args.yaml_vars)
|
171
|
+
user_specified_dir = bool(args.yaml_vars_dir)
|
172
|
+
auto_load_default = not (user_specified_files or user_specified_dir)
|
173
|
+
|
168
174
|
load_yaml_variables_from_args(
|
169
175
|
yaml_files=args.yaml_vars,
|
170
176
|
yaml_vars_dir=args.yaml_vars_dir,
|
171
177
|
project_root=os.getcwd(), # CLI模式下使用当前工作目录作为项目根目录
|
172
|
-
environment=environment
|
178
|
+
environment=environment,
|
179
|
+
auto_load_default=auto_load_default # 使用智能判断的结果
|
173
180
|
)
|
174
181
|
except Exception as e:
|
175
182
|
print(f"加载YAML变量失败: {str(e)}")
|
pytest_dsl/core/context.py
CHANGED
@@ -1,18 +1,42 @@
|
|
1
1
|
class TestContext:
|
2
2
|
def __init__(self):
|
3
3
|
self._data = {}
|
4
|
+
self._external_providers = [] # 外部变量提供者列表
|
4
5
|
|
5
6
|
def set(self, key: str, value: any) -> None:
|
6
7
|
"""设置上下文变量"""
|
7
8
|
self._data[key] = value
|
8
9
|
|
9
10
|
def get(self, key: str, default=None) -> any:
|
10
|
-
"""
|
11
|
-
|
11
|
+
"""获取上下文变量,遵循变量优先级:本地变量 > 外部提供者变量"""
|
12
|
+
# 1. 首先检查本地变量
|
13
|
+
if key in self._data:
|
14
|
+
return self._data[key]
|
15
|
+
|
16
|
+
# 2. 检查外部提供者(按注册顺序)
|
17
|
+
for provider in self._external_providers:
|
18
|
+
if hasattr(provider, 'get_variable'):
|
19
|
+
value = provider.get_variable(key)
|
20
|
+
if value is not None:
|
21
|
+
return value
|
22
|
+
|
23
|
+
# 3. 返回默认值
|
24
|
+
return default
|
12
25
|
|
13
26
|
def has(self, key: str) -> bool:
|
14
|
-
"""
|
15
|
-
|
27
|
+
"""检查上下文变量是否存在(包括外部提供者)"""
|
28
|
+
# 检查本地变量
|
29
|
+
if key in self._data:
|
30
|
+
return True
|
31
|
+
|
32
|
+
# 检查外部提供者
|
33
|
+
for provider in self._external_providers:
|
34
|
+
if hasattr(provider, 'get_variable'):
|
35
|
+
value = provider.get_variable(key)
|
36
|
+
if value is not None:
|
37
|
+
return True
|
38
|
+
|
39
|
+
return False
|
16
40
|
|
17
41
|
def clear(self) -> None:
|
18
42
|
"""清空上下文"""
|
@@ -20,4 +44,34 @@ class TestContext:
|
|
20
44
|
|
21
45
|
def get_local_variables(self) -> dict:
|
22
46
|
"""获取所有本地变量"""
|
23
|
-
return self._data
|
47
|
+
return self._data
|
48
|
+
|
49
|
+
def register_external_variable_provider(self, provider) -> None:
|
50
|
+
"""注册外部变量提供者
|
51
|
+
|
52
|
+
Args:
|
53
|
+
provider: 变量提供者,需要实现get_variable(key)方法
|
54
|
+
"""
|
55
|
+
if provider not in self._external_providers:
|
56
|
+
self._external_providers.append(provider)
|
57
|
+
|
58
|
+
def sync_variables_from_external_sources(self) -> None:
|
59
|
+
"""将外部变量提供者中的常用变量同步到本地缓存中,提高访问性能
|
60
|
+
|
61
|
+
这个方法会调用所有外部提供者的get_all_variables方法,
|
62
|
+
将常用变量缓存到本地_data字典中,以提高后续访问的性能。
|
63
|
+
注意:本地变量的优先级仍然高于外部变量。
|
64
|
+
"""
|
65
|
+
for provider in self._external_providers:
|
66
|
+
if hasattr(provider, 'get_all_variables'):
|
67
|
+
try:
|
68
|
+
# 获取提供者的所有变量
|
69
|
+
external_vars = provider.get_all_variables()
|
70
|
+
if isinstance(external_vars, dict):
|
71
|
+
# 只同步那些本地还没有的变量,保持本地变量的优先级
|
72
|
+
for key, value in external_vars.items():
|
73
|
+
if key not in self._data:
|
74
|
+
self._data[key] = value
|
75
|
+
except Exception as e:
|
76
|
+
# 如果某个提供者同步失败,记录警告但继续处理其他提供者
|
77
|
+
print(f"警告:同步外部变量提供者变量时发生错误: {e}")
|
@@ -212,7 +212,7 @@ class CustomKeywordManager:
|
|
212
212
|
print(f"资源文件 {file_path} 加载失败: {str(e)}")
|
213
213
|
raise
|
214
214
|
|
215
|
-
def _process_resource_file_content(self, content: str,
|
215
|
+
def _process_resource_file_content(self, content: str,
|
216
216
|
file_path: str) -> None:
|
217
217
|
"""处理资源文件内容
|
218
218
|
|
@@ -258,7 +258,7 @@ class CustomKeywordManager:
|
|
258
258
|
# 递归加载导入的资源文件
|
259
259
|
self.load_resource_file(imported_file)
|
260
260
|
|
261
|
-
def _register_keywords_from_ast(self, ast: Node,
|
261
|
+
def _register_keywords_from_ast(self, ast: Node,
|
262
262
|
source_name: str) -> None:
|
263
263
|
"""从AST中注册关键字(重构后的版本)
|
264
264
|
|
@@ -341,6 +341,12 @@ class CustomKeywordManager:
|
|
341
341
|
executor.test_context.set(
|
342
342
|
param_name, kwargs[param_mapping_name])
|
343
343
|
|
344
|
+
# 重要:创建变量替换器,使变量解析正常工作
|
345
|
+
from pytest_dsl.core.variable_utils import VariableReplacer
|
346
|
+
executor.variable_replacer = VariableReplacer(
|
347
|
+
executor.variables, executor.test_context
|
348
|
+
)
|
349
|
+
|
344
350
|
# 执行关键字体中的语句
|
345
351
|
result = None
|
346
352
|
try:
|
@@ -357,7 +363,7 @@ class CustomKeywordManager:
|
|
357
363
|
|
358
364
|
print(f"已注册自定义关键字: {keyword_name} 来自文件: {file_path}")
|
359
365
|
|
360
|
-
def register_keyword_from_dsl_content(self, dsl_content: str,
|
366
|
+
def register_keyword_from_dsl_content(self, dsl_content: str,
|
361
367
|
source_name: str = "DSL内容") -> list:
|
362
368
|
"""从DSL内容注册关键字(公共方法)
|
363
369
|
|
@@ -379,8 +385,8 @@ class CustomKeywordManager:
|
|
379
385
|
|
380
386
|
# 收集注册前的关键字列表
|
381
387
|
existing_keywords = (
|
382
|
-
set(keyword_manager._keywords.keys())
|
383
|
-
if hasattr(keyword_manager, '_keywords')
|
388
|
+
set(keyword_manager._keywords.keys())
|
389
|
+
if hasattr(keyword_manager, '_keywords')
|
384
390
|
else set()
|
385
391
|
)
|
386
392
|
|
@@ -389,8 +395,8 @@ class CustomKeywordManager:
|
|
389
395
|
|
390
396
|
# 计算新注册的关键字
|
391
397
|
new_keywords = (
|
392
|
-
set(keyword_manager._keywords.keys())
|
393
|
-
if hasattr(keyword_manager, '_keywords')
|
398
|
+
set(keyword_manager._keywords.keys())
|
399
|
+
if hasattr(keyword_manager, '_keywords')
|
394
400
|
else set()
|
395
401
|
)
|
396
402
|
registered_keywords = list(new_keywords - existing_keywords)
|
@@ -405,7 +411,7 @@ class CustomKeywordManager:
|
|
405
411
|
raise
|
406
412
|
|
407
413
|
def register_specific_keyword_from_dsl_content(
|
408
|
-
self, keyword_name: str, dsl_content: str,
|
414
|
+
self, keyword_name: str, dsl_content: str,
|
409
415
|
source_name: str = "DSL内容") -> bool:
|
410
416
|
"""从DSL内容注册指定的关键字(公共方法)
|
411
417
|
|
pytest_dsl/core/dsl_executor.py
CHANGED
@@ -72,6 +72,10 @@ class DSLExecutor:
|
|
72
72
|
self.variables = {}
|
73
73
|
self.test_context = TestContext()
|
74
74
|
self.test_context.executor = self # 让 test_context 能够访问到 executor
|
75
|
+
|
76
|
+
# 设置变量提供者,实现YAML变量等外部变量源的注入
|
77
|
+
self._setup_variable_providers()
|
78
|
+
|
75
79
|
self.variable_replacer = VariableReplacer(
|
76
80
|
self.variables, self.test_context)
|
77
81
|
self.imported_files = set() # 跟踪已导入的文件,避免循环导入
|
@@ -348,7 +352,7 @@ class DSLExecutor:
|
|
348
352
|
# 对于不包含 ${} 的普通字符串,检查是否为单纯的变量名
|
349
353
|
# 只有当字符串是有效的变量名格式且确实存在该变量时,才当作变量处理
|
350
354
|
if (re.match(r'^[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*$', value) and
|
351
|
-
|
355
|
+
value in self.variable_replacer.local_variables):
|
352
356
|
return self.variable_replacer.local_variables[value]
|
353
357
|
else:
|
354
358
|
# 否则当作字符串字面量处理
|
@@ -366,6 +370,7 @@ class DSLExecutor:
|
|
366
370
|
:param expr_node: 比较表达式节点
|
367
371
|
:return: 比较结果(布尔值)
|
368
372
|
"""
|
373
|
+
operator = "未知" # 设置默认值,避免UnboundLocalError
|
369
374
|
try:
|
370
375
|
left_value = self.eval_expression(expr_node.children[0])
|
371
376
|
right_value = self.eval_expression(expr_node.children[1])
|
@@ -403,6 +408,7 @@ class DSLExecutor:
|
|
403
408
|
:param expr_node: 算术表达式节点
|
404
409
|
:return: 计算结果
|
405
410
|
"""
|
411
|
+
operator = "未知" # 设置默认值,避免UnboundLocalError
|
406
412
|
try:
|
407
413
|
left_value = self.eval_expression(expr_node.children[0])
|
408
414
|
right_value = self.eval_expression(expr_node.children[1])
|
@@ -903,7 +909,7 @@ class DSLExecutor:
|
|
903
909
|
try:
|
904
910
|
# 准备参数(这里可能抛出参数解析异常)
|
905
911
|
kwargs = self._prepare_keyword_params(node, keyword_info)
|
906
|
-
|
912
|
+
|
907
913
|
# 传递自定义步骤名称给KeywordManager,避免重复的allure步骤嵌套
|
908
914
|
kwargs['step_name'] = keyword_name # 内层步骤只显示关键字名称
|
909
915
|
# 避免KeywordManager重复记录,由DSL执行器统一记录
|
@@ -927,7 +933,8 @@ class DSLExecutor:
|
|
927
933
|
if "参数解析异常" in core_error:
|
928
934
|
# 提取参数名和具体错误
|
929
935
|
import re
|
930
|
-
match = re.search(
|
936
|
+
match = re.search(
|
937
|
+
r'参数解析异常 \(([^)]+)\): (.+)', core_error)
|
931
938
|
if match:
|
932
939
|
param_name, detailed_error = match.groups()
|
933
940
|
error_details = f"参数解析失败 ({param_name}): {detailed_error}{line_info}\n上下文: 执行KeywordCall节点"
|
@@ -938,7 +945,7 @@ class DSLExecutor:
|
|
938
945
|
else:
|
939
946
|
# 其他异常
|
940
947
|
error_details = f"执行KeywordCall节点: {str(e)}{line_info}\n上下文: 执行KeywordCall节点"
|
941
|
-
|
948
|
+
|
942
949
|
allure.attach(
|
943
950
|
error_details,
|
944
951
|
name="DSL执行异常",
|
@@ -958,14 +965,14 @@ class DSLExecutor:
|
|
958
965
|
for param in node.children[0]:
|
959
966
|
param_name = param.value
|
960
967
|
english_param_name = mapping.get(param_name, param_name)
|
961
|
-
|
968
|
+
|
962
969
|
# 在子步骤中处理参数值解析,但不记录异常详情
|
963
970
|
with allure.step(f"解析参数: {param_name}"):
|
964
971
|
try:
|
965
972
|
# 对参数值进行变量替换
|
966
973
|
param_value = self.eval_expression(param.children[0])
|
967
974
|
kwargs[english_param_name] = param_value
|
968
|
-
|
975
|
+
|
969
976
|
# 只记录参数解析成功的简要信息
|
970
977
|
allure.attach(
|
971
978
|
f"参数名: {param_name}\n"
|
@@ -1144,7 +1151,8 @@ class DSLExecutor:
|
|
1144
1151
|
else:
|
1145
1152
|
self.variable_replacer.local_variables[
|
1146
1153
|
capture_var] = capture_value
|
1147
|
-
self.test_context.set(
|
1154
|
+
self.test_context.set(
|
1155
|
+
capture_var, capture_value)
|
1148
1156
|
|
1149
1157
|
# 将主要结果赋值给指定变量
|
1150
1158
|
actual_result = main_result
|
@@ -1265,7 +1273,7 @@ class DSLExecutor:
|
|
1265
1273
|
# 其他异常使用统一处理机制
|
1266
1274
|
# 对于这些节点类型,异常已经在步骤中记录过了,跳过重复记录
|
1267
1275
|
step_handled_nodes = {
|
1268
|
-
'KeywordCall', 'Assignment', 'AssignmentKeywordCall',
|
1276
|
+
'KeywordCall', 'Assignment', 'AssignmentKeywordCall',
|
1269
1277
|
'ForLoop', 'RemoteKeywordCall', 'AssignmentRemoteKeywordCall'
|
1270
1278
|
}
|
1271
1279
|
skip_logging = node.type in step_handled_nodes
|
@@ -1312,6 +1320,18 @@ class DSLExecutor:
|
|
1312
1320
|
f"hooks_enabled={self.enable_hooks}, "
|
1313
1321
|
f"tracking_enabled={self.enable_tracking})")
|
1314
1322
|
|
1323
|
+
def _setup_variable_providers(self):
|
1324
|
+
"""设置变量提供者,将外部变量源注入到TestContext中"""
|
1325
|
+
try:
|
1326
|
+
from .variable_providers import setup_context_with_default_providers
|
1327
|
+
setup_context_with_default_providers(self.test_context)
|
1328
|
+
|
1329
|
+
# 同步常用变量到context中,提高访问性能
|
1330
|
+
self.test_context.sync_variables_from_external_sources()
|
1331
|
+
except ImportError as e:
|
1332
|
+
# 如果导入失败,记录警告但不影响正常功能
|
1333
|
+
print(f"警告:无法设置变量提供者: {e}")
|
1334
|
+
|
1315
1335
|
def _init_hooks(self):
|
1316
1336
|
"""初始化hook机制"""
|
1317
1337
|
try:
|
@@ -4,7 +4,6 @@ import tempfile
|
|
4
4
|
import allure
|
5
5
|
from typing import Dict, Any, Optional
|
6
6
|
from filelock import FileLock
|
7
|
-
from .yaml_vars import yaml_vars
|
8
7
|
|
9
8
|
|
10
9
|
class GlobalContext:
|
@@ -19,6 +18,20 @@ class GlobalContext:
|
|
19
18
|
self._storage_dir, "global_vars.json")
|
20
19
|
self._lock_file = os.path.join(self._storage_dir, "global_vars.lock")
|
21
20
|
|
21
|
+
# 初始化变量提供者(延迟加载,避免循环导入)
|
22
|
+
self._yaml_provider = None
|
23
|
+
|
24
|
+
def _get_yaml_provider(self):
|
25
|
+
"""延迟获取YAML变量提供者,避免循环导入"""
|
26
|
+
if self._yaml_provider is None:
|
27
|
+
try:
|
28
|
+
from .variable_providers import YAMLVariableProvider
|
29
|
+
self._yaml_provider = YAMLVariableProvider()
|
30
|
+
except ImportError:
|
31
|
+
# 如果变量提供者不可用,创建一个空的提供者
|
32
|
+
self._yaml_provider = _EmptyProvider()
|
33
|
+
return self._yaml_provider
|
34
|
+
|
22
35
|
def set_variable(self, name: str, value: Any) -> None:
|
23
36
|
"""设置全局变量"""
|
24
37
|
with FileLock(self._lock_file):
|
@@ -34,8 +47,9 @@ class GlobalContext:
|
|
34
47
|
|
35
48
|
def get_variable(self, name: str) -> Any:
|
36
49
|
"""获取全局变量,优先从YAML变量中获取"""
|
37
|
-
# 首先尝试从YAML
|
38
|
-
|
50
|
+
# 首先尝试从YAML变量中获取(通过变量提供者)
|
51
|
+
yaml_provider = self._get_yaml_provider()
|
52
|
+
yaml_value = yaml_provider.get_variable(name)
|
39
53
|
if yaml_value is not None:
|
40
54
|
return yaml_value
|
41
55
|
|
@@ -46,8 +60,9 @@ class GlobalContext:
|
|
46
60
|
|
47
61
|
def has_variable(self, name: str) -> bool:
|
48
62
|
"""检查全局变量是否存在(包括YAML变量)"""
|
49
|
-
# 首先检查YAML
|
50
|
-
|
63
|
+
# 首先检查YAML变量(通过变量提供者)
|
64
|
+
yaml_provider = self._get_yaml_provider()
|
65
|
+
if yaml_provider.has_variable(name):
|
51
66
|
return True
|
52
67
|
|
53
68
|
# 然后检查全局变量存储
|
@@ -73,9 +88,11 @@ class GlobalContext:
|
|
73
88
|
"""清除所有全局变量(包括YAML变量)"""
|
74
89
|
with FileLock(self._lock_file):
|
75
90
|
self._save_variables({})
|
76
|
-
|
77
|
-
# 清除YAML
|
78
|
-
|
91
|
+
|
92
|
+
# 清除YAML变量(通过变量提供者)
|
93
|
+
yaml_provider = self._get_yaml_provider()
|
94
|
+
if hasattr(yaml_provider, 'clear'):
|
95
|
+
yaml_provider.clear()
|
79
96
|
|
80
97
|
allure.attach(
|
81
98
|
"清除所有全局变量",
|
@@ -99,5 +116,18 @@ class GlobalContext:
|
|
99
116
|
json.dump(variables, f, ensure_ascii=False, indent=2)
|
100
117
|
|
101
118
|
|
119
|
+
class _EmptyProvider:
|
120
|
+
"""空的变量提供者,用作后备方案"""
|
121
|
+
|
122
|
+
def get_variable(self, key: str) -> Optional[Any]:
|
123
|
+
return None
|
124
|
+
|
125
|
+
def has_variable(self, key: str) -> bool:
|
126
|
+
return False
|
127
|
+
|
128
|
+
def clear(self):
|
129
|
+
pass
|
130
|
+
|
131
|
+
|
102
132
|
# 创建全局上下文管理器实例
|
103
133
|
global_context = GlobalContext()
|
pytest_dsl/core/http_client.py
CHANGED
@@ -3,7 +3,6 @@ import logging
|
|
3
3
|
from typing import Dict, Any
|
4
4
|
import requests
|
5
5
|
from urllib.parse import urljoin
|
6
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
7
6
|
from pytest_dsl.core.auth_provider import create_auth_provider
|
8
7
|
|
9
8
|
logger = logging.getLogger(__name__)
|
@@ -289,6 +288,31 @@ class HTTPClientManager:
|
|
289
288
|
"""初始化客户端管理器"""
|
290
289
|
self._clients: Dict[str, HTTPClient] = {}
|
291
290
|
self._sessions: Dict[str, HTTPClient] = {}
|
291
|
+
self._context = None # 添加context引用
|
292
|
+
|
293
|
+
def set_context(self, context):
|
294
|
+
"""设置测试上下文,用于获取HTTP客户端配置
|
295
|
+
|
296
|
+
Args:
|
297
|
+
context: TestContext实例
|
298
|
+
"""
|
299
|
+
self._context = context
|
300
|
+
|
301
|
+
def _get_http_clients_config(self) -> Dict[str, Any]:
|
302
|
+
"""从context获取HTTP客户端配置
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
HTTP客户端配置字典
|
306
|
+
"""
|
307
|
+
if self._context:
|
308
|
+
return self._context.get("http_clients") or {}
|
309
|
+
|
310
|
+
# 如果没有context,尝试从yaml_vars获取(兼容性)
|
311
|
+
try:
|
312
|
+
from pytest_dsl.core.yaml_vars import yaml_vars
|
313
|
+
return yaml_vars.get_variable("http_clients") or {}
|
314
|
+
except ImportError:
|
315
|
+
return {}
|
292
316
|
|
293
317
|
def create_client(self, config: Dict[str, Any]) -> HTTPClient:
|
294
318
|
"""从配置创建客户端
|
@@ -326,8 +350,8 @@ class HTTPClientManager:
|
|
326
350
|
if name in self._clients:
|
327
351
|
return self._clients[name]
|
328
352
|
|
329
|
-
# 从
|
330
|
-
http_clients =
|
353
|
+
# 从context获取HTTP客户端配置(统一的变量获取方式)
|
354
|
+
http_clients = self._get_http_clients_config()
|
331
355
|
client_config = http_clients.get(name)
|
332
356
|
|
333
357
|
if not client_config:
|
@@ -377,7 +401,7 @@ class HTTPClientManager:
|
|
377
401
|
return session
|
378
402
|
|
379
403
|
def _get_client_config(self, name: str) -> Dict[str, Any]:
|
380
|
-
"""从
|
404
|
+
"""从context获取客户端配置
|
381
405
|
|
382
406
|
Args:
|
383
407
|
name: 客户端名称
|
@@ -385,7 +409,7 @@ class HTTPClientManager:
|
|
385
409
|
Returns:
|
386
410
|
客户端配置
|
387
411
|
"""
|
388
|
-
http_clients =
|
412
|
+
http_clients = self._get_http_clients_config()
|
389
413
|
client_config = http_clients.get(name)
|
390
414
|
|
391
415
|
if not client_config and name == "default":
|
pytest_dsl/core/keyword_utils.py
CHANGED
@@ -210,6 +210,13 @@ class KeywordFormatter:
|
|
210
210
|
'parameters': keyword_info.parameters
|
211
211
|
}
|
212
212
|
|
213
|
+
# 添加来源字段,优先显示项目自定义关键字的文件位置
|
214
|
+
if keyword_info.file_location:
|
215
|
+
keyword_data['source'] = keyword_info.file_location
|
216
|
+
else:
|
217
|
+
keyword_data['source'] = keyword_info.source_info.get(
|
218
|
+
'display_name', keyword_info.source_info.get('name', '未知'))
|
219
|
+
|
213
220
|
# 远程关键字特殊信息
|
214
221
|
if keyword_info.remote_info:
|
215
222
|
keyword_data['remote'] = keyword_info.remote_info
|