pytest-dsl 0.5.0__py3-none-any.whl → 0.7.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 +91 -23
- pytest_dsl/core/http_request.py +272 -221
- pytest_dsl/core/lexer.py +17 -17
- pytest_dsl/core/parser.py +52 -4
- pytest_dsl/core/parsetab.py +81 -70
- pytest_dsl/core/plugin_discovery.py +1 -8
- pytest_dsl/core/utils.py +43 -23
- pytest_dsl/core/variable_utils.py +215 -70
- 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.7.0.dist-info/METADATA +1040 -0
- pytest_dsl-0.7.0.dist-info/RECORD +67 -0
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/WHEEL +1 -1
- pytest_dsl/parsetab.py +0 -69
- pytest_dsl-0.5.0.dist-info/METADATA +0 -596
- pytest_dsl-0.5.0.dist-info/RECORD +0 -68
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.5.0.dist-info → pytest_dsl-0.7.0.dist-info}/top_level.txt +0 -0
@@ -8,44 +8,44 @@ from pytest_dsl.core.yaml_vars import yaml_vars
|
|
8
8
|
|
9
9
|
class VariableReplacer:
|
10
10
|
"""统一的变量替换工具类
|
11
|
-
|
11
|
+
|
12
12
|
提供统一的变量替换功能,支持字符串、字典和列表中的变量替换。
|
13
13
|
变量查找优先级:本地变量 > 测试上下文 > YAML变量 > 全局上下文
|
14
14
|
"""
|
15
|
-
|
15
|
+
|
16
16
|
def __init__(self, local_variables: Dict[str, Any] = None, test_context: TestContext = None):
|
17
17
|
"""初始化变量替换器
|
18
|
-
|
18
|
+
|
19
19
|
Args:
|
20
20
|
local_variables: 本地变量字典
|
21
21
|
test_context: 测试上下文对象
|
22
22
|
"""
|
23
23
|
self.local_variables = local_variables or {}
|
24
24
|
self._test_context = test_context or TestContext()
|
25
|
-
|
25
|
+
|
26
26
|
@property
|
27
27
|
def test_context(self) -> TestContext:
|
28
28
|
"""获取测试上下文,确保始终使用最新的上下文对象
|
29
|
-
|
29
|
+
|
30
30
|
如果上下文对象中包含executor属性,则使用executor的上下文
|
31
31
|
(这确保即使上下文被替换也能获取正确的引用)
|
32
|
-
|
32
|
+
|
33
33
|
Returns:
|
34
34
|
测试上下文对象
|
35
35
|
"""
|
36
36
|
if hasattr(self._test_context, 'executor') and self._test_context.executor is not None:
|
37
37
|
return self._test_context.executor.test_context
|
38
38
|
return self._test_context
|
39
|
-
|
39
|
+
|
40
40
|
def get_variable(self, var_name: str) -> Any:
|
41
41
|
"""获取变量值,按照优先级查找
|
42
|
-
|
42
|
+
|
43
43
|
Args:
|
44
44
|
var_name: 变量名
|
45
|
-
|
45
|
+
|
46
46
|
Returns:
|
47
47
|
变量值,如果变量不存在则抛出 KeyError
|
48
|
-
|
48
|
+
|
49
49
|
Raises:
|
50
50
|
KeyError: 当变量不存在时
|
51
51
|
"""
|
@@ -53,31 +53,31 @@ class VariableReplacer:
|
|
53
53
|
if var_name in self.local_variables:
|
54
54
|
value = self.local_variables[var_name]
|
55
55
|
return self._convert_value(value)
|
56
|
-
|
56
|
+
|
57
57
|
# 从测试上下文中获取
|
58
58
|
if self.test_context.has(var_name):
|
59
59
|
value = self.test_context.get(var_name)
|
60
60
|
return self._convert_value(value)
|
61
|
-
|
61
|
+
|
62
62
|
# 从YAML变量中获取
|
63
63
|
yaml_value = yaml_vars.get_variable(var_name)
|
64
64
|
if yaml_value is not None:
|
65
65
|
return self._convert_value(yaml_value)
|
66
|
-
|
66
|
+
|
67
67
|
# 从全局上下文获取
|
68
68
|
if global_context.has_variable(var_name):
|
69
69
|
value = global_context.get_variable(var_name)
|
70
70
|
return self._convert_value(value)
|
71
|
-
|
71
|
+
|
72
72
|
# 如果变量不存在,抛出异常
|
73
73
|
raise KeyError(f"变量 '{var_name}' 不存在")
|
74
|
-
|
74
|
+
|
75
75
|
def _convert_value(self, value: Any) -> Any:
|
76
76
|
"""转换值为正确的类型
|
77
|
-
|
77
|
+
|
78
78
|
Args:
|
79
79
|
value: 要转换的值
|
80
|
-
|
80
|
+
|
81
81
|
Returns:
|
82
82
|
转换后的值
|
83
83
|
"""
|
@@ -93,68 +93,213 @@ class VariableReplacer:
|
|
93
93
|
except (ValueError, TypeError):
|
94
94
|
pass
|
95
95
|
return value
|
96
|
-
|
96
|
+
|
97
97
|
def replace_in_string(self, value: str) -> str:
|
98
98
|
"""替换字符串中的变量引用
|
99
|
-
|
99
|
+
|
100
|
+
支持多种访问语法:
|
101
|
+
- 基本变量: ${variable}
|
102
|
+
- 点号访问: ${object.property}
|
103
|
+
- 数组索引: ${array[0]}, ${array[-1]}
|
104
|
+
- 字典键访问: ${dict["key"]}, ${dict['key']}
|
105
|
+
- 混合访问: ${users[0].name}, ${data["users"][0]["name"]}
|
106
|
+
|
100
107
|
Args:
|
101
108
|
value: 包含变量引用的字符串
|
102
|
-
|
109
|
+
|
103
110
|
Returns:
|
104
111
|
替换后的字符串
|
105
|
-
|
112
|
+
|
106
113
|
Raises:
|
107
114
|
KeyError: 当变量不存在时
|
108
115
|
"""
|
109
116
|
if not isinstance(value, str) or '${' not in value:
|
110
117
|
return value
|
111
|
-
|
112
|
-
#
|
113
|
-
|
114
|
-
|
118
|
+
|
119
|
+
# 扩展的变量引用模式,支持数组索引和字典键访问
|
120
|
+
# 匹配: ${variable}, ${obj.prop}, ${arr[0]}, ${dict["key"]}, ${obj[0].prop} 等
|
121
|
+
pattern = r'\$\{([a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*(?:(?:\.[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*)|(?:\[[^\]]+\]))*)\}'
|
122
|
+
|
115
123
|
result = value
|
116
124
|
matches = list(re.finditer(pattern, result))
|
117
|
-
|
125
|
+
|
118
126
|
# 从后向前替换,避免位置偏移
|
119
127
|
for match in reversed(matches):
|
120
|
-
var_ref = match.group(1) # 例如: "
|
121
|
-
|
122
|
-
|
123
|
-
# 获取根变量
|
124
|
-
root_var_name = parts[0]
|
128
|
+
var_ref = match.group(1) # 例如: "users[0].name" 或 "data['key']"
|
129
|
+
|
125
130
|
try:
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
for part in parts[1:]:
|
133
|
-
if isinstance(var_value, dict) and part in var_value:
|
134
|
-
var_value = var_value[part]
|
135
|
-
else:
|
136
|
-
raise KeyError(f"无法访问属性 '{part}',变量 '{root_var_name}' 的类型是 {type(var_value).__name__}")
|
137
|
-
|
138
|
-
# 替换变量引用
|
139
|
-
result = result[:match.start()] + str(var_value) + result[match.end():]
|
140
|
-
|
131
|
+
var_value = self._parse_variable_path(var_ref)
|
132
|
+
# 替换变量引用
|
133
|
+
result = result[:match.start()] + str(var_value) + result[match.end():]
|
134
|
+
except (KeyError, IndexError, TypeError) as e:
|
135
|
+
raise KeyError(f"无法解析变量引用 '${{{var_ref}}}': {str(e)}")
|
136
|
+
|
141
137
|
return result
|
142
|
-
|
138
|
+
|
139
|
+
def _parse_variable_path(self, var_ref: str):
|
140
|
+
"""解析复杂的变量访问路径
|
141
|
+
|
142
|
+
支持的语法:
|
143
|
+
- variable
|
144
|
+
- object.property
|
145
|
+
- array[0]
|
146
|
+
- dict["key"]
|
147
|
+
- users[0].name
|
148
|
+
- data["users"][0]["name"]
|
149
|
+
|
150
|
+
Args:
|
151
|
+
var_ref: 变量引用路径,如 "users[0].name"
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
解析后的变量值
|
155
|
+
|
156
|
+
Raises:
|
157
|
+
KeyError: 当变量不存在时
|
158
|
+
IndexError: 当数组索引超出范围时
|
159
|
+
TypeError: 当类型不匹配时
|
160
|
+
"""
|
161
|
+
# 解析访问路径
|
162
|
+
path_parts = self._tokenize_path(var_ref)
|
163
|
+
|
164
|
+
# 获取根变量
|
165
|
+
root_var_name = path_parts[0]
|
166
|
+
try:
|
167
|
+
current_value = self.get_variable(root_var_name)
|
168
|
+
except KeyError:
|
169
|
+
raise KeyError(f"变量 '{root_var_name}' 不存在")
|
170
|
+
|
171
|
+
# 逐步访问路径
|
172
|
+
for part in path_parts[1:]:
|
173
|
+
current_value = self._access_value(current_value, part, root_var_name)
|
174
|
+
|
175
|
+
return current_value
|
176
|
+
|
177
|
+
def _tokenize_path(self, var_ref: str) -> list:
|
178
|
+
"""将变量路径分解为访问令牌
|
179
|
+
|
180
|
+
例如:
|
181
|
+
- "users[0].name" -> ["users", "[0]", "name"]
|
182
|
+
- "data['key'].items[1]" -> ["data", "['key']", "items", "[1]"]
|
183
|
+
|
184
|
+
Args:
|
185
|
+
var_ref: 变量引用路径
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
访问令牌列表
|
189
|
+
"""
|
190
|
+
tokens = []
|
191
|
+
current_token = ""
|
192
|
+
i = 0
|
193
|
+
|
194
|
+
while i < len(var_ref):
|
195
|
+
char = var_ref[i]
|
196
|
+
|
197
|
+
if char == '.':
|
198
|
+
if current_token:
|
199
|
+
tokens.append(current_token)
|
200
|
+
current_token = ""
|
201
|
+
elif char == '[':
|
202
|
+
if current_token:
|
203
|
+
tokens.append(current_token)
|
204
|
+
current_token = ""
|
205
|
+
|
206
|
+
# 找到匹配的右括号
|
207
|
+
bracket_count = 1
|
208
|
+
bracket_content = "["
|
209
|
+
i += 1
|
210
|
+
|
211
|
+
while i < len(var_ref) and bracket_count > 0:
|
212
|
+
char = var_ref[i]
|
213
|
+
bracket_content += char
|
214
|
+
if char == '[':
|
215
|
+
bracket_count += 1
|
216
|
+
elif char == ']':
|
217
|
+
bracket_count -= 1
|
218
|
+
i += 1
|
219
|
+
|
220
|
+
tokens.append(bracket_content)
|
221
|
+
i -= 1 # 回退一位,因为外层循环会自增
|
222
|
+
else:
|
223
|
+
current_token += char
|
224
|
+
|
225
|
+
i += 1
|
226
|
+
|
227
|
+
if current_token:
|
228
|
+
tokens.append(current_token)
|
229
|
+
|
230
|
+
return tokens
|
231
|
+
|
232
|
+
def _access_value(self, current_value, access_token: str, root_var_name: str):
|
233
|
+
"""根据访问令牌获取值
|
234
|
+
|
235
|
+
Args:
|
236
|
+
current_value: 当前值
|
237
|
+
access_token: 访问令牌,如 "name", "[0]", "['key']"
|
238
|
+
root_var_name: 根变量名(用于错误信息)
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
访问后的值
|
242
|
+
|
243
|
+
Raises:
|
244
|
+
KeyError: 当键不存在时
|
245
|
+
IndexError: 当索引超出范围时
|
246
|
+
TypeError: 当类型不匹配时
|
247
|
+
"""
|
248
|
+
if access_token.startswith('[') and access_token.endswith(']'):
|
249
|
+
# 数组索引或字典键访问
|
250
|
+
key_content = access_token[1:-1].strip()
|
251
|
+
|
252
|
+
# 处理字符串键(带引号)
|
253
|
+
if (key_content.startswith('"') and key_content.endswith('"')) or \
|
254
|
+
(key_content.startswith("'") and key_content.endswith("'")):
|
255
|
+
key = key_content[1:-1] # 去掉引号
|
256
|
+
if isinstance(current_value, dict):
|
257
|
+
if key not in current_value:
|
258
|
+
raise KeyError(f"字典中不存在键 '{key}'")
|
259
|
+
return current_value[key]
|
260
|
+
else:
|
261
|
+
raise TypeError(f"无法在 {type(current_value).__name__} 类型上使用字符串键访问")
|
262
|
+
|
263
|
+
# 处理数字索引
|
264
|
+
try:
|
265
|
+
index = int(key_content)
|
266
|
+
if isinstance(current_value, (list, tuple)):
|
267
|
+
if index >= len(current_value) or index < -len(current_value):
|
268
|
+
raise IndexError(f"数组索引 {index} 超出范围,数组长度为 {len(current_value)}")
|
269
|
+
return current_value[index]
|
270
|
+
elif isinstance(current_value, dict):
|
271
|
+
# 字典也可以用数字键
|
272
|
+
str_key = str(index)
|
273
|
+
if str_key not in current_value and index not in current_value:
|
274
|
+
raise KeyError(f"字典中不存在键 '{index}' 或 '{str_key}'")
|
275
|
+
return current_value.get(index, current_value.get(str_key))
|
276
|
+
else:
|
277
|
+
raise TypeError(f"无法在 {type(current_value).__name__} 类型上使用索引访问")
|
278
|
+
except ValueError:
|
279
|
+
raise ValueError(f"无效的索引格式: '{key_content}'")
|
280
|
+
|
281
|
+
else:
|
282
|
+
# 属性访问(点号语法)
|
283
|
+
if isinstance(current_value, dict) and access_token in current_value:
|
284
|
+
return current_value[access_token]
|
285
|
+
else:
|
286
|
+
raise KeyError(f"无法访问属性 '{access_token}',当前值类型是 {type(current_value).__name__}")
|
287
|
+
|
143
288
|
def replace_in_dict(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
144
289
|
"""递归替换字典中的变量引用
|
145
|
-
|
290
|
+
|
146
291
|
Args:
|
147
292
|
data: 包含变量引用的字典
|
148
|
-
|
293
|
+
|
149
294
|
Returns:
|
150
295
|
替换后的字典
|
151
|
-
|
296
|
+
|
152
297
|
Raises:
|
153
298
|
KeyError: 当变量不存在时
|
154
299
|
"""
|
155
300
|
if not isinstance(data, dict):
|
156
301
|
return data
|
157
|
-
|
302
|
+
|
158
303
|
result = {}
|
159
304
|
for key, value in data.items():
|
160
305
|
# 替换键中的变量
|
@@ -162,35 +307,35 @@ class VariableReplacer:
|
|
162
307
|
# 替换值中的变量
|
163
308
|
new_value = self.replace_in_value(value)
|
164
309
|
result[new_key] = new_value
|
165
|
-
|
310
|
+
|
166
311
|
return result
|
167
|
-
|
312
|
+
|
168
313
|
def replace_in_list(self, data: List[Any]) -> List[Any]:
|
169
314
|
"""递归替换列表中的变量引用
|
170
|
-
|
315
|
+
|
171
316
|
Args:
|
172
317
|
data: 包含变量引用的列表
|
173
|
-
|
318
|
+
|
174
319
|
Returns:
|
175
320
|
替换后的列表
|
176
|
-
|
321
|
+
|
177
322
|
Raises:
|
178
323
|
KeyError: 当变量不存在时
|
179
324
|
"""
|
180
325
|
if not isinstance(data, list):
|
181
326
|
return data
|
182
|
-
|
327
|
+
|
183
328
|
return [self.replace_in_value(item) for item in data]
|
184
|
-
|
329
|
+
|
185
330
|
def replace_in_value(self, value: Any) -> Any:
|
186
331
|
"""递归替换任意值中的变量引用
|
187
|
-
|
332
|
+
|
188
333
|
Args:
|
189
334
|
value: 任意值,可能是字符串、字典、列表等
|
190
|
-
|
335
|
+
|
191
336
|
Returns:
|
192
337
|
替换后的值
|
193
|
-
|
338
|
+
|
194
339
|
Raises:
|
195
340
|
KeyError: 当变量不存在时
|
196
341
|
"""
|
@@ -217,16 +362,16 @@ class VariableReplacer:
|
|
217
362
|
return value
|
218
363
|
except:
|
219
364
|
return value
|
220
|
-
|
365
|
+
|
221
366
|
def replace_in_json(self, json_str: str) -> str:
|
222
367
|
"""替换JSON字符串中的变量引用
|
223
|
-
|
368
|
+
|
224
369
|
Args:
|
225
370
|
json_str: 包含变量引用的JSON字符串
|
226
|
-
|
371
|
+
|
227
372
|
Returns:
|
228
373
|
替换后的JSON字符串
|
229
|
-
|
374
|
+
|
230
375
|
Raises:
|
231
376
|
KeyError: 当变量不存在时
|
232
377
|
json.JSONDecodeError: 当JSON解析失败时
|
@@ -241,16 +386,16 @@ class VariableReplacer:
|
|
241
386
|
except json.JSONDecodeError:
|
242
387
|
# 如果JSON解析失败,直接作为字符串处理
|
243
388
|
return self.replace_in_string(json_str)
|
244
|
-
|
389
|
+
|
245
390
|
def replace_in_yaml(self, yaml_str: str) -> str:
|
246
391
|
"""替换YAML字符串中的变量引用
|
247
|
-
|
392
|
+
|
248
393
|
Args:
|
249
394
|
yaml_str: 包含变量引用的YAML字符串
|
250
|
-
|
395
|
+
|
251
396
|
Returns:
|
252
397
|
替换后的YAML字符串
|
253
|
-
|
398
|
+
|
254
399
|
Raises:
|
255
400
|
KeyError: 当变量不存在时
|
256
401
|
"""
|
@@ -264,4 +409,4 @@ class VariableReplacer:
|
|
264
409
|
return yaml.dump(replaced_data, allow_unicode=True)
|
265
410
|
except:
|
266
411
|
# 如果YAML解析失败,直接作为字符串处理
|
267
|
-
return self.replace_in_string(yaml_str)
|
412
|
+
return self.replace_in_string(yaml_str)
|
pytest_dsl/core/yaml_loader.py
CHANGED
@@ -10,7 +10,7 @@ from pytest_dsl.core.yaml_vars import yaml_vars
|
|
10
10
|
|
11
11
|
def add_yaml_options(parser):
|
12
12
|
"""添加YAML变量相关的命令行参数选项
|
13
|
-
|
13
|
+
|
14
14
|
Args:
|
15
15
|
parser: pytest命令行参数解析器
|
16
16
|
"""
|
@@ -29,34 +29,111 @@ def add_yaml_options(parser):
|
|
29
29
|
)
|
30
30
|
|
31
31
|
|
32
|
-
def
|
33
|
-
"""
|
34
|
-
|
35
|
-
从命令行参数指定的文件和目录加载YAML变量。
|
36
|
-
|
32
|
+
def load_yaml_variables_from_args(yaml_files=None, yaml_vars_dir=None, project_root=None):
|
33
|
+
"""从参数加载YAML变量文件(通用函数)
|
34
|
+
|
37
35
|
Args:
|
38
|
-
|
36
|
+
yaml_files: YAML文件列表
|
37
|
+
yaml_vars_dir: YAML变量目录路径
|
38
|
+
project_root: 项目根目录(用于默认config目录)
|
39
39
|
"""
|
40
40
|
# 加载单个YAML文件
|
41
|
-
yaml_files = config.getoption('--yaml-vars')
|
42
41
|
if yaml_files:
|
43
42
|
yaml_vars.load_yaml_files(yaml_files)
|
44
43
|
print(f"已加载YAML变量文件: {', '.join(yaml_files)}")
|
45
44
|
|
46
45
|
# 加载目录中的YAML文件
|
47
|
-
yaml_vars_dir
|
48
|
-
|
49
|
-
|
50
|
-
# 通过pytest的rootdir获取使用者的项目根目录
|
51
|
-
project_root = config.rootdir
|
52
|
-
yaml_vars_dir = str(project_root / 'config')
|
46
|
+
if yaml_vars_dir is None and project_root:
|
47
|
+
# 默认使用项目根目录下的config目录
|
48
|
+
yaml_vars_dir = str(Path(project_root) / 'config')
|
53
49
|
print(f"使用默认YAML变量目录: {yaml_vars_dir}")
|
54
|
-
|
55
|
-
if Path(yaml_vars_dir).exists():
|
50
|
+
|
51
|
+
if yaml_vars_dir and Path(yaml_vars_dir).exists():
|
56
52
|
yaml_vars.load_from_directory(yaml_vars_dir)
|
57
53
|
print(f"已加载YAML变量目录: {yaml_vars_dir}")
|
58
54
|
loaded_files = yaml_vars.get_loaded_files()
|
59
55
|
if loaded_files:
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
# 过滤出当前目录的文件
|
57
|
+
if yaml_vars_dir:
|
58
|
+
dir_files = [f for f in loaded_files if Path(f).parent == Path(yaml_vars_dir)]
|
59
|
+
if dir_files:
|
60
|
+
print(f"目录中加载的文件: {', '.join(dir_files)}")
|
61
|
+
else:
|
62
|
+
print(f"加载的文件: {', '.join(loaded_files)}")
|
63
|
+
elif yaml_vars_dir:
|
64
|
+
print(f"YAML变量目录不存在: {yaml_vars_dir}")
|
65
|
+
|
66
|
+
# 加载完YAML变量后,自动连接远程服务器
|
67
|
+
load_remote_servers_from_yaml()
|
68
|
+
|
69
|
+
|
70
|
+
def load_yaml_variables(config):
|
71
|
+
"""加载YAML变量文件(pytest插件接口)
|
72
|
+
|
73
|
+
从pytest配置对象中获取命令行参数并加载YAML变量。
|
74
|
+
|
75
|
+
Args:
|
76
|
+
config: pytest配置对象
|
77
|
+
"""
|
78
|
+
# 获取命令行参数
|
79
|
+
yaml_files = config.getoption('--yaml-vars')
|
80
|
+
yaml_vars_dir = config.getoption('--yaml-vars-dir')
|
81
|
+
project_root = config.rootdir
|
82
|
+
|
83
|
+
# 调用通用加载函数
|
84
|
+
load_yaml_variables_from_args(
|
85
|
+
yaml_files=yaml_files,
|
86
|
+
yaml_vars_dir=yaml_vars_dir,
|
87
|
+
project_root=project_root
|
88
|
+
)
|
89
|
+
|
90
|
+
|
91
|
+
def load_remote_servers_from_yaml():
|
92
|
+
"""从YAML配置中自动加载远程服务器
|
93
|
+
|
94
|
+
检查YAML变量中是否包含remote_servers配置,如果有则自动连接这些服务器。
|
95
|
+
"""
|
96
|
+
try:
|
97
|
+
# 获取远程服务器配置
|
98
|
+
remote_servers_config = yaml_vars.get_variable('remote_servers')
|
99
|
+
if not remote_servers_config:
|
100
|
+
|
101
|
+
return
|
102
|
+
|
103
|
+
print(f"发现 {len(remote_servers_config)} 个远程服务器配置")
|
104
|
+
|
105
|
+
# 导入远程关键字管理器
|
106
|
+
from pytest_dsl.remote.keyword_client import remote_keyword_manager
|
107
|
+
|
108
|
+
# 遍历配置并连接服务器
|
109
|
+
for server_name, server_config in remote_servers_config.items():
|
110
|
+
try:
|
111
|
+
url = server_config.get('url')
|
112
|
+
alias = server_config.get('alias', server_name)
|
113
|
+
api_key = server_config.get('api_key')
|
114
|
+
sync_config = server_config.get('sync_config')
|
115
|
+
|
116
|
+
if not url:
|
117
|
+
print(f"跳过服务器 {server_name}: 缺少URL配置")
|
118
|
+
continue
|
119
|
+
|
120
|
+
print(f"正在连接远程服务器: {server_name} ({url}) 别名: {alias}")
|
121
|
+
|
122
|
+
# 注册远程服务器
|
123
|
+
success = remote_keyword_manager.register_remote_server(
|
124
|
+
url=url,
|
125
|
+
alias=alias,
|
126
|
+
api_key=api_key,
|
127
|
+
sync_config=sync_config
|
128
|
+
)
|
129
|
+
|
130
|
+
if success:
|
131
|
+
print(f"成功连接到远程服务器: {server_name} ({url})")
|
132
|
+
else:
|
133
|
+
print(f"连接远程服务器失败: {server_name} ({url})")
|
134
|
+
|
135
|
+
except Exception as e:
|
136
|
+
print(f"连接远程服务器 {server_name} 时发生错误: {str(e)}")
|
137
|
+
|
138
|
+
except Exception as e:
|
139
|
+
print(f"加载远程服务器配置时发生错误: {str(e)}")
|