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.
- 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 +56 -11
- pytest_dsl/core/http_request.py +272 -221
- pytest_dsl/core/lexer.py +3 -13
- pytest_dsl/core/parser.py +2 -2
- pytest_dsl/core/parsetab.py +69 -69
- 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 +58 -56
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/METADATA +138 -9
- pytest_dsl-0.6.0.dist-info/RECORD +68 -0
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/WHEEL +1 -1
- pytest_dsl-0.5.0.dist-info/RECORD +0 -68
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.6.0.dist-info}/top_level.txt +0 -0
pytest_dsl/cli.py
CHANGED
@@ -13,8 +13,9 @@ from pathlib import Path
|
|
13
13
|
from pytest_dsl.core.lexer import get_lexer
|
14
14
|
from pytest_dsl.core.parser import get_parser
|
15
15
|
from pytest_dsl.core.dsl_executor import DSLExecutor
|
16
|
-
from pytest_dsl.core.
|
16
|
+
from pytest_dsl.core.yaml_loader import load_yaml_variables_from_args
|
17
17
|
from pytest_dsl.core.auto_directory import SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
|
18
|
+
from pytest_dsl.core.plugin_discovery import load_all_plugins
|
18
19
|
|
19
20
|
|
20
21
|
def read_file(filename):
|
@@ -27,35 +28,26 @@ def parse_args():
|
|
27
28
|
"""解析命令行参数"""
|
28
29
|
parser = argparse.ArgumentParser(description='执行DSL测试文件')
|
29
30
|
parser.add_argument('path', help='要执行的DSL文件路径或包含DSL文件的目录')
|
30
|
-
parser.add_argument('--yaml-vars', action='append', default=[],
|
31
|
+
parser.add_argument('--yaml-vars', action='append', default=[],
|
31
32
|
help='YAML变量文件路径,可以指定多个文件 (例如: --yaml-vars vars1.yaml --yaml-vars vars2.yaml)')
|
32
33
|
parser.add_argument('--yaml-vars-dir', default=None,
|
33
34
|
help='YAML变量文件目录路径,将加载该目录下所有.yaml文件')
|
34
|
-
|
35
|
+
|
35
36
|
return parser.parse_args()
|
36
37
|
|
37
38
|
|
38
39
|
def load_yaml_variables(args):
|
39
40
|
"""从命令行参数加载YAML变量"""
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
print(f"已加载YAML变量目录: {yaml_vars_dir}")
|
51
|
-
loaded_files = yaml_vars.get_loaded_files()
|
52
|
-
if loaded_files:
|
53
|
-
dir_files = [f for f in loaded_files if Path(f).parent == Path(yaml_vars_dir)]
|
54
|
-
if dir_files:
|
55
|
-
print(f"目录中加载的文件: {', '.join(dir_files)}")
|
56
|
-
except NotADirectoryError:
|
57
|
-
print(f"YAML变量目录不存在: {yaml_vars_dir}")
|
58
|
-
sys.exit(1)
|
41
|
+
# 使用统一的加载函数,包含远程服务器自动连接功能
|
42
|
+
try:
|
43
|
+
load_yaml_variables_from_args(
|
44
|
+
yaml_files=args.yaml_vars,
|
45
|
+
yaml_vars_dir=args.yaml_vars_dir,
|
46
|
+
project_root=os.getcwd() # CLI模式下使用当前工作目录作为项目根目录
|
47
|
+
)
|
48
|
+
except Exception as e:
|
49
|
+
print(f"加载YAML变量失败: {str(e)}")
|
50
|
+
sys.exit(1)
|
59
51
|
|
60
52
|
|
61
53
|
def execute_dsl_file(file_path, lexer, parser, executor):
|
@@ -85,14 +77,17 @@ def main():
|
|
85
77
|
"""命令行入口点"""
|
86
78
|
args = parse_args()
|
87
79
|
path = args.path
|
88
|
-
|
89
|
-
#
|
80
|
+
|
81
|
+
# 加载内置关键字插件
|
82
|
+
load_all_plugins()
|
83
|
+
|
84
|
+
# 加载YAML变量(包括远程服务器自动连接)
|
90
85
|
load_yaml_variables(args)
|
91
|
-
|
86
|
+
|
92
87
|
lexer = get_lexer()
|
93
88
|
parser = get_parser()
|
94
89
|
executor = DSLExecutor()
|
95
|
-
|
90
|
+
|
96
91
|
# 检查路径是文件还是目录
|
97
92
|
if os.path.isfile(path):
|
98
93
|
# 执行单个文件
|
@@ -102,32 +97,32 @@ def main():
|
|
102
97
|
elif os.path.isdir(path):
|
103
98
|
# 执行目录中的所有DSL文件
|
104
99
|
print(f"执行目录: {path}")
|
105
|
-
|
100
|
+
|
106
101
|
# 先执行目录的setup文件(如果存在)
|
107
102
|
setup_file = os.path.join(path, SETUP_FILE_NAME)
|
108
103
|
if os.path.exists(setup_file):
|
109
104
|
execute_hook_file(Path(setup_file), True, path)
|
110
|
-
|
105
|
+
|
111
106
|
# 查找并执行所有DSL文件
|
112
107
|
dsl_files = find_dsl_files(path)
|
113
108
|
if not dsl_files:
|
114
109
|
print(f"目录中没有找到DSL文件: {path}")
|
115
110
|
sys.exit(1)
|
116
|
-
|
111
|
+
|
117
112
|
print(f"找到 {len(dsl_files)} 个DSL文件")
|
118
|
-
|
113
|
+
|
119
114
|
# 执行所有DSL文件
|
120
115
|
failures = 0
|
121
116
|
for file_path in dsl_files:
|
122
117
|
success = execute_dsl_file(file_path, lexer, parser, executor)
|
123
118
|
if not success:
|
124
119
|
failures += 1
|
125
|
-
|
120
|
+
|
126
121
|
# 最后执行目录的teardown文件(如果存在)
|
127
122
|
teardown_file = os.path.join(path, TEARDOWN_FILE_NAME)
|
128
123
|
if os.path.exists(teardown_file):
|
129
124
|
execute_hook_file(Path(teardown_file), False, path)
|
130
|
-
|
125
|
+
|
131
126
|
# 如果有失败的测试,返回非零退出码
|
132
127
|
if failures > 0:
|
133
128
|
print(f"总计 {failures}/{len(dsl_files)} 个测试失败")
|
@@ -140,4 +135,4 @@ def main():
|
|
140
135
|
|
141
136
|
|
142
137
|
if __name__ == '__main__':
|
143
|
-
main()
|
138
|
+
main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""自动测试装饰器模块
|
2
2
|
|
3
|
-
该模块提供装饰器功能,用于将指定目录下的.auto文件动态添加为测试方法到被装饰的类中。
|
3
|
+
该模块提供装饰器功能,用于将指定目录下的.auto和.dsl文件动态添加为测试方法到被装饰的类中。
|
4
4
|
这种方式更贴合pytest的设计理念,可以充分利用pytest的fixture、参数化等功能。
|
5
5
|
"""
|
6
6
|
|
@@ -15,7 +15,11 @@ from pytest_dsl.core.dsl_executor import DSLExecutor
|
|
15
15
|
from pytest_dsl.core.dsl_executor_utils import read_file, execute_dsl_file, extract_metadata_from_ast
|
16
16
|
from pytest_dsl.core.lexer import get_lexer
|
17
17
|
from pytest_dsl.core.parser import get_parser
|
18
|
-
from pytest_dsl.core.auto_directory import
|
18
|
+
from pytest_dsl.core.auto_directory import (
|
19
|
+
SETUP_FILE_NAME, TEARDOWN_FILE_NAME,
|
20
|
+
SETUP_DSL_FILE_NAME, TEARDOWN_DSL_FILE_NAME,
|
21
|
+
execute_hook_file
|
22
|
+
)
|
19
23
|
|
20
24
|
# 获取词法分析器和解析器实例
|
21
25
|
lexer = get_lexer()
|
@@ -24,12 +28,12 @@ parser = get_parser()
|
|
24
28
|
|
25
29
|
def auto_dsl(directory: Union[str, Path], is_file: bool = False):
|
26
30
|
"""
|
27
|
-
装饰器函数,用于将指定目录下的.auto文件动态添加为测试方法到被装饰的类中。
|
28
|
-
|
31
|
+
装饰器函数,用于将指定目录下的.auto和.dsl文件动态添加为测试方法到被装饰的类中。
|
32
|
+
|
29
33
|
Args:
|
30
|
-
directory: 包含.auto文件的目录路径,可以是相对路径或绝对路径
|
34
|
+
directory: 包含.auto或.dsl文件的目录路径,可以是相对路径或绝对路径
|
31
35
|
is_file: 是否是文件路径而不是目录路径
|
32
|
-
|
36
|
+
|
33
37
|
Returns:
|
34
38
|
装饰器函数
|
35
39
|
"""
|
@@ -40,7 +44,7 @@ def auto_dsl(directory: Union[str, Path], is_file: bool = False):
|
|
40
44
|
caller_file = caller_frame.f_globals['__file__']
|
41
45
|
caller_dir = Path(caller_file).parent
|
42
46
|
path = (caller_dir / path).resolve()
|
43
|
-
|
47
|
+
|
44
48
|
if is_file:
|
45
49
|
# 路径是文件
|
46
50
|
if not path.exists() or not path.is_file():
|
@@ -51,109 +55,124 @@ def auto_dsl(directory: Union[str, Path], is_file: bool = False):
|
|
51
55
|
if not path.exists() or not path.is_dir():
|
52
56
|
raise ValueError(f"目录不存在或不是有效目录: {path}")
|
53
57
|
directory_path = path
|
54
|
-
|
58
|
+
|
55
59
|
def decorator(cls):
|
56
60
|
if is_file:
|
57
61
|
# 如果是文件路径,只添加这个文件的测试方法
|
58
62
|
_add_test_method(cls, file_path)
|
59
63
|
else:
|
60
|
-
# 检查setup
|
61
|
-
setup_file =
|
62
|
-
teardown_file =
|
63
|
-
|
64
|
+
# 检查setup和teardown文件(支持.auto和.dsl扩展名)
|
65
|
+
setup_file = None
|
66
|
+
teardown_file = None
|
67
|
+
|
68
|
+
# 优先查找.auto文件,然后查找.dsl文件
|
69
|
+
for setup_name in [SETUP_FILE_NAME, SETUP_DSL_FILE_NAME]:
|
70
|
+
potential_setup = directory_path / setup_name
|
71
|
+
if potential_setup.exists():
|
72
|
+
setup_file = potential_setup
|
73
|
+
break
|
74
|
+
|
75
|
+
for teardown_name in [TEARDOWN_FILE_NAME, TEARDOWN_DSL_FILE_NAME]:
|
76
|
+
potential_teardown = directory_path / teardown_name
|
77
|
+
if potential_teardown.exists():
|
78
|
+
teardown_file = potential_teardown
|
79
|
+
break
|
80
|
+
|
64
81
|
# 添加setup和teardown方法
|
65
|
-
if setup_file
|
82
|
+
if setup_file:
|
66
83
|
@classmethod
|
67
84
|
@pytest.fixture(scope="class", autouse=True)
|
68
85
|
def setup_class(cls, request):
|
69
86
|
execute_hook_file(setup_file, True, str(directory_path))
|
70
|
-
|
87
|
+
|
71
88
|
setattr(cls, "setup_class", setup_class)
|
72
|
-
|
73
|
-
if teardown_file
|
89
|
+
|
90
|
+
if teardown_file:
|
74
91
|
@classmethod
|
75
92
|
@pytest.fixture(scope="class", autouse=True)
|
76
93
|
def teardown_class(cls, request):
|
77
94
|
request.addfinalizer(lambda: execute_hook_file(teardown_file, False, str(directory_path)))
|
78
|
-
|
95
|
+
|
79
96
|
setattr(cls, "teardown_class", teardown_class)
|
80
|
-
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
97
|
+
|
98
|
+
# 处理目录中的测试文件,支持.auto和.dsl扩展名
|
99
|
+
excluded_files = [SETUP_FILE_NAME, TEARDOWN_FILE_NAME, SETUP_DSL_FILE_NAME, TEARDOWN_DSL_FILE_NAME]
|
100
|
+
for pattern in ["*.auto", "*.dsl"]:
|
101
|
+
for test_file in directory_path.glob(pattern):
|
102
|
+
if test_file.name not in excluded_files:
|
103
|
+
_add_test_method(cls, test_file)
|
104
|
+
|
86
105
|
return cls
|
87
|
-
|
106
|
+
|
88
107
|
return decorator
|
89
108
|
|
90
109
|
|
91
|
-
def _add_test_method(cls: Type,
|
110
|
+
def _add_test_method(cls: Type, test_file: Path) -> None:
|
92
111
|
"""
|
93
|
-
|
94
|
-
|
112
|
+
为DSL测试文件创建测试方法并添加到类中
|
113
|
+
|
95
114
|
Args:
|
96
115
|
cls: 要添加测试方法的类
|
97
|
-
|
116
|
+
test_file: DSL测试文件路径(.auto或.dsl)
|
98
117
|
"""
|
99
|
-
test_name = f"test_{
|
100
|
-
|
118
|
+
test_name = f"test_{test_file.stem}"
|
119
|
+
|
101
120
|
# 读取DSL文件内容并解析
|
102
|
-
dsl_code = read_file(str(
|
121
|
+
dsl_code = read_file(str(test_file))
|
103
122
|
ast = parser.parse(dsl_code, lexer=lexer)
|
104
|
-
|
123
|
+
|
105
124
|
# 检查是否有数据驱动标记和测试名称
|
106
125
|
data_source, test_title = extract_metadata_from_ast(ast)
|
107
|
-
|
126
|
+
|
108
127
|
if data_source:
|
109
|
-
test_method = _create_data_driven_test(
|
128
|
+
test_method = _create_data_driven_test(test_file, data_source, test_title)
|
110
129
|
else:
|
111
|
-
test_method = _create_simple_test(
|
112
|
-
|
130
|
+
test_method = _create_simple_test(test_file)
|
131
|
+
|
113
132
|
# 将测试方法添加到类
|
114
133
|
setattr(cls, test_name, test_method)
|
115
134
|
|
116
135
|
|
117
|
-
def _create_simple_test(
|
136
|
+
def _create_simple_test(test_file: Path) -> Callable:
|
118
137
|
"""
|
119
138
|
创建普通的测试方法
|
120
|
-
|
139
|
+
|
121
140
|
Args:
|
122
|
-
|
123
|
-
|
141
|
+
test_file: DSL测试文件路径(.auto或.dsl)
|
142
|
+
|
124
143
|
Returns:
|
125
144
|
function: 测试方法
|
126
145
|
"""
|
127
146
|
def test_method(self):
|
128
|
-
execute_dsl_file(str(
|
129
|
-
|
147
|
+
execute_dsl_file(str(test_file))
|
148
|
+
|
130
149
|
return test_method
|
131
150
|
|
132
151
|
|
133
|
-
def _create_data_driven_test(
|
152
|
+
def _create_data_driven_test(test_file: Path, data_source: Dict, test_title: Optional[str]) -> Callable:
|
134
153
|
"""
|
135
154
|
创建数据驱动的测试方法
|
136
|
-
|
155
|
+
|
137
156
|
Args:
|
138
|
-
|
157
|
+
test_file: DSL测试文件路径(.auto或.dsl)
|
139
158
|
data_source: 数据源
|
140
159
|
test_title: 测试标题
|
141
|
-
|
160
|
+
|
142
161
|
Returns:
|
143
162
|
function: 装饰后的测试方法
|
144
163
|
"""
|
145
164
|
def test_method(self, test_data):
|
146
165
|
executor = DSLExecutor()
|
147
166
|
executor.set_current_data(test_data)
|
148
|
-
execute_dsl_file(str(
|
149
|
-
|
167
|
+
execute_dsl_file(str(test_file), executor)
|
168
|
+
|
150
169
|
# 加载测试数据
|
151
170
|
executor = DSLExecutor()
|
152
171
|
test_data_list = executor._load_test_data(data_source)
|
153
|
-
|
172
|
+
|
154
173
|
# 为每个数据集创建一个唯一的ID
|
155
|
-
test_ids = _generate_test_ids(test_data_list, test_title or
|
156
|
-
|
174
|
+
test_ids = _generate_test_ids(test_data_list, test_title or test_file.stem)
|
175
|
+
|
157
176
|
# 使用pytest.mark.parametrize装饰测试方法
|
158
177
|
return pytest.mark.parametrize(
|
159
178
|
'test_data',
|
@@ -165,11 +184,11 @@ def _create_data_driven_test(auto_file: Path, data_source: Dict, test_title: Opt
|
|
165
184
|
def _generate_test_ids(test_data_list: List[Dict[str, Any]], base_name: str) -> List[str]:
|
166
185
|
"""
|
167
186
|
为数据驱动测试生成ID
|
168
|
-
|
187
|
+
|
169
188
|
Args:
|
170
189
|
test_data_list: 测试数据列表
|
171
190
|
base_name: 基础名称
|
172
|
-
|
191
|
+
|
173
192
|
Returns:
|
174
193
|
List[str]: 测试ID列表
|
175
194
|
"""
|
@@ -32,6 +32,9 @@ _teardown_executed = set()
|
|
32
32
|
# 常量定义
|
33
33
|
SETUP_FILE_NAME = "setup.auto"
|
34
34
|
TEARDOWN_FILE_NAME = "teardown.auto"
|
35
|
+
# 支持.dsl扩展名的setup和teardown文件
|
36
|
+
SETUP_DSL_FILE_NAME = "setup.dsl"
|
37
|
+
TEARDOWN_DSL_FILE_NAME = "teardown.dsl"
|
35
38
|
TMP_DIR = "/tmp"
|
36
39
|
LOCK_FILE_SUFFIX = ".lock"
|
37
40
|
EXECUTED_FILE_SUFFIX = ".lock.executed"
|
@@ -39,11 +42,11 @@ EXECUTED_FILE_SUFFIX = ".lock.executed"
|
|
39
42
|
|
40
43
|
def get_lock_file_path(dir_path: str, is_setup: bool) -> str:
|
41
44
|
"""获取锁文件路径
|
42
|
-
|
45
|
+
|
43
46
|
Args:
|
44
47
|
dir_path: 目录路径
|
45
48
|
is_setup: 是否为setup锁文件
|
46
|
-
|
49
|
+
|
47
50
|
Returns:
|
48
51
|
str: 锁文件路径
|
49
52
|
"""
|
@@ -53,7 +56,7 @@ def get_lock_file_path(dir_path: str, is_setup: bool) -> str:
|
|
53
56
|
|
54
57
|
def execute_hook_file(file_path: Path, is_setup: bool, dir_path_str: str) -> None:
|
55
58
|
"""执行setup或teardown钩子文件
|
56
|
-
|
59
|
+
|
57
60
|
Args:
|
58
61
|
file_path: 钩子文件路径
|
59
62
|
is_setup: 是否为setup钩子
|
@@ -62,12 +65,12 @@ def execute_hook_file(file_path: Path, is_setup: bool, dir_path_str: str) -> Non
|
|
62
65
|
hook_type = "Setup" if is_setup else "Teardown"
|
63
66
|
executed_set = _setup_executed if is_setup else _teardown_executed
|
64
67
|
lock_file = get_lock_file_path(dir_path_str, is_setup)
|
65
|
-
|
68
|
+
|
66
69
|
# 检查是否已执行过
|
67
70
|
if dir_path_str in executed_set:
|
68
71
|
logger.info(f"{hook_type} for directory already executed: {dir_path_str}")
|
69
72
|
return
|
70
|
-
|
73
|
+
|
71
74
|
# 使用filelock获取锁并执行
|
72
75
|
with FileLock(lock_file):
|
73
76
|
if dir_path_str not in executed_set: # 再次检查,防止在获取锁期间被其他进程执行
|
pytest_dsl/core/dsl_executor.py
CHANGED
@@ -11,7 +11,6 @@ from pytest_dsl.core.context import TestContext
|
|
11
11
|
import pytest_dsl.keywords
|
12
12
|
from pytest_dsl.core.yaml_vars import yaml_vars
|
13
13
|
from pytest_dsl.core.variable_utils import VariableReplacer
|
14
|
-
from pytest_dsl.remote.keyword_client import remote_keyword_manager
|
15
14
|
|
16
15
|
|
17
16
|
class DSLExecutor:
|
@@ -218,6 +217,8 @@ class DSLExecutor:
|
|
218
217
|
Args:
|
219
218
|
node: RemoteImport节点
|
220
219
|
"""
|
220
|
+
from pytest_dsl.remote.keyword_client import remote_keyword_manager
|
221
|
+
|
221
222
|
remote_info = node.value
|
222
223
|
url = self._replace_variables_in_string(remote_info['url'])
|
223
224
|
alias = remote_info['alias']
|
@@ -395,20 +396,40 @@ class DSLExecutor:
|
|
395
396
|
result = self.execute(keyword_call_node)
|
396
397
|
|
397
398
|
if result is not None:
|
399
|
+
# 处理新的统一返回格式(支持远程关键字模式)
|
400
|
+
if isinstance(result, dict) and 'result' in result:
|
401
|
+
# 提取主要返回值
|
402
|
+
main_result = result['result']
|
403
|
+
|
404
|
+
# 处理captures字段中的变量
|
405
|
+
captures = result.get('captures', {})
|
406
|
+
for capture_var, capture_value in captures.items():
|
407
|
+
if capture_var.startswith('g_'):
|
408
|
+
global_context.set_variable(capture_var, capture_value)
|
409
|
+
else:
|
410
|
+
self.variable_replacer.local_variables[capture_var] = capture_value
|
411
|
+
self.test_context.set(capture_var, capture_value)
|
412
|
+
|
413
|
+
# 将主要结果赋值给指定变量
|
414
|
+
actual_result = main_result
|
415
|
+
else:
|
416
|
+
# 传统格式,直接使用结果
|
417
|
+
actual_result = result
|
418
|
+
|
398
419
|
# 检查变量名是否以g_开头,如果是则设置为全局变量
|
399
420
|
if var_name.startswith('g_'):
|
400
|
-
global_context.set_variable(var_name,
|
421
|
+
global_context.set_variable(var_name, actual_result)
|
401
422
|
allure.attach(
|
402
|
-
f"全局变量: {var_name}\n值: {
|
423
|
+
f"全局变量: {var_name}\n值: {actual_result}",
|
403
424
|
name="全局变量赋值",
|
404
425
|
attachment_type=allure.attachment_type.TEXT
|
405
426
|
)
|
406
427
|
else:
|
407
428
|
# 存储在本地变量字典和测试上下文中
|
408
|
-
self.variable_replacer.local_variables[var_name] =
|
409
|
-
self.test_context.set(var_name,
|
429
|
+
self.variable_replacer.local_variables[var_name] = actual_result
|
430
|
+
self.test_context.set(var_name, actual_result) # 同时添加到测试上下文
|
410
431
|
allure.attach(
|
411
|
-
f"变量: {var_name}\n值: {
|
432
|
+
f"变量: {var_name}\n值: {actual_result}",
|
412
433
|
name="赋值详情",
|
413
434
|
attachment_type=allure.attachment_type.TEXT
|
414
435
|
)
|
@@ -511,6 +532,8 @@ class DSLExecutor:
|
|
511
532
|
Returns:
|
512
533
|
执行结果
|
513
534
|
"""
|
535
|
+
from pytest_dsl.remote.keyword_client import remote_keyword_manager
|
536
|
+
|
514
537
|
call_info = node.value
|
515
538
|
alias = call_info['alias']
|
516
539
|
keyword_name = call_info['keyword']
|
@@ -560,20 +583,42 @@ class DSLExecutor:
|
|
560
583
|
result = self.execute(remote_keyword_call_node)
|
561
584
|
|
562
585
|
if result is not None:
|
586
|
+
# 注意:远程关键字客户端已经处理了新格式的返回值,
|
587
|
+
# 这里接收到的result应该已经是主要返回值,而不是完整的字典格式
|
588
|
+
# 但为了保险起见,我们仍然检查是否为新格式
|
589
|
+
if isinstance(result, dict) and 'result' in result:
|
590
|
+
# 如果仍然是新格式(可能是嵌套的远程调用),提取主要返回值
|
591
|
+
main_result = result['result']
|
592
|
+
|
593
|
+
# 处理captures字段中的变量
|
594
|
+
captures = result.get('captures', {})
|
595
|
+
for capture_var, capture_value in captures.items():
|
596
|
+
if capture_var.startswith('g_'):
|
597
|
+
global_context.set_variable(capture_var, capture_value)
|
598
|
+
else:
|
599
|
+
self.variable_replacer.local_variables[capture_var] = capture_value
|
600
|
+
self.test_context.set(capture_var, capture_value)
|
601
|
+
|
602
|
+
# 将主要结果赋值给指定变量
|
603
|
+
actual_result = main_result
|
604
|
+
else:
|
605
|
+
# 传统格式或已经处理过的格式,直接使用结果
|
606
|
+
actual_result = result
|
607
|
+
|
563
608
|
# 检查变量名是否以g_开头,如果是则设置为全局变量
|
564
609
|
if var_name.startswith('g_'):
|
565
|
-
global_context.set_variable(var_name,
|
610
|
+
global_context.set_variable(var_name, actual_result)
|
566
611
|
allure.attach(
|
567
|
-
f"全局变量: {var_name}\n值: {
|
612
|
+
f"全局变量: {var_name}\n值: {actual_result}",
|
568
613
|
name="全局变量赋值",
|
569
614
|
attachment_type=allure.attachment_type.TEXT
|
570
615
|
)
|
571
616
|
else:
|
572
617
|
# 存储在本地变量字典和测试上下文中
|
573
|
-
self.variable_replacer.local_variables[var_name] =
|
574
|
-
self.test_context.set(var_name,
|
618
|
+
self.variable_replacer.local_variables[var_name] = actual_result
|
619
|
+
self.test_context.set(var_name, actual_result) # 同时添加到测试上下文
|
575
620
|
allure.attach(
|
576
|
-
f"变量: {var_name}\n值: {
|
621
|
+
f"变量: {var_name}\n值: {actual_result}",
|
577
622
|
name="赋值详情",
|
578
623
|
attachment_type=allure.attachment_type.TEXT
|
579
624
|
)
|