pytest-dsl 0.9.0__py3-none-any.whl → 0.10.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 +88 -12
- pytest_dsl/core/keyword_manager.py +21 -5
- pytest_dsl/keywords/assertion_keywords.py +18 -12
- pytest_dsl/keywords/http_keywords.py +4 -4
- pytest_dsl/keywords/system_keywords.py +73 -48
- pytest_dsl/remote/__init__.py +7 -0
- pytest_dsl/remote/hook_manager.py +155 -0
- pytest_dsl/remote/keyword_client.py +399 -0
- pytest_dsl/remote/keyword_server.py +618 -0
- pytest_dsl/remote/variable_bridge.py +164 -0
- {pytest_dsl-0.9.0.dist-info → pytest_dsl-0.10.0.dist-info}/METADATA +88 -23
- {pytest_dsl-0.9.0.dist-info → pytest_dsl-0.10.0.dist-info}/RECORD +16 -11
- {pytest_dsl-0.9.0.dist-info → pytest_dsl-0.10.0.dist-info}/entry_points.txt +1 -1
- {pytest_dsl-0.9.0.dist-info → pytest_dsl-0.10.0.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.9.0.dist-info → pytest_dsl-0.10.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.9.0.dist-info → pytest_dsl-0.10.0.dist-info}/top_level.txt +0 -0
pytest_dsl/cli.py
CHANGED
@@ -64,8 +64,12 @@ def parse_args():
|
|
64
64
|
)
|
65
65
|
list_parser.add_argument(
|
66
66
|
'--format', choices=['text', 'json'],
|
67
|
-
default='
|
68
|
-
help='输出格式:
|
67
|
+
default='json',
|
68
|
+
help='输出格式:json(默认) 或 text'
|
69
|
+
)
|
70
|
+
list_parser.add_argument(
|
71
|
+
'--output', '-o', type=str, default=None,
|
72
|
+
help='输出文件路径(仅对 json 格式有效,默认为 keywords.json)'
|
69
73
|
)
|
70
74
|
list_parser.add_argument(
|
71
75
|
'--filter', type=str, default=None,
|
@@ -88,7 +92,10 @@ def parse_args():
|
|
88
92
|
if '--list-keywords' in argv:
|
89
93
|
parser.add_argument('--list-keywords', action='store_true')
|
90
94
|
parser.add_argument(
|
91
|
-
'--format', choices=['text', 'json'], default='
|
95
|
+
'--format', choices=['text', 'json'], default='json'
|
96
|
+
)
|
97
|
+
parser.add_argument(
|
98
|
+
'--output', '-o', type=str, default=None
|
92
99
|
)
|
93
100
|
parser.add_argument('--filter', type=str, default=None)
|
94
101
|
parser.add_argument(
|
@@ -178,13 +185,22 @@ def format_keyword_info_text(keyword_name, keyword_info, show_category=True):
|
|
178
185
|
param_name = getattr(param, 'name', str(param))
|
179
186
|
param_mapping = getattr(param, 'mapping', '')
|
180
187
|
param_desc = getattr(param, 'description', '')
|
188
|
+
param_default = getattr(param, 'default', None)
|
181
189
|
|
190
|
+
# 构建参数描述
|
191
|
+
param_info = []
|
182
192
|
if param_mapping and param_mapping != param_name:
|
183
|
-
|
184
|
-
f" {param_name} ({param_mapping}): {param_desc}"
|
185
|
-
)
|
193
|
+
param_info.append(f"{param_name} ({param_mapping})")
|
186
194
|
else:
|
187
|
-
|
195
|
+
param_info.append(param_name)
|
196
|
+
|
197
|
+
param_info.append(f": {param_desc}")
|
198
|
+
|
199
|
+
# 添加默认值信息
|
200
|
+
if param_default is not None:
|
201
|
+
param_info.append(f" (默认值: {param_default})")
|
202
|
+
|
203
|
+
lines.append(f" {''.join(param_info)}")
|
188
204
|
else:
|
189
205
|
lines.append(" 参数: 无")
|
190
206
|
|
@@ -221,6 +237,12 @@ def format_keyword_info_json(keyword_name, keyword_info):
|
|
221
237
|
'mapping': getattr(param, 'mapping', ''),
|
222
238
|
'description': getattr(param, 'description', '')
|
223
239
|
}
|
240
|
+
|
241
|
+
# 添加默认值信息
|
242
|
+
param_default = getattr(param, 'default', None)
|
243
|
+
if param_default is not None:
|
244
|
+
param_data['default'] = param_default
|
245
|
+
|
224
246
|
keyword_data['parameters'].append(param_data)
|
225
247
|
|
226
248
|
# 函数文档
|
@@ -231,8 +253,8 @@ def format_keyword_info_json(keyword_name, keyword_info):
|
|
231
253
|
return keyword_data
|
232
254
|
|
233
255
|
|
234
|
-
def list_keywords(output_format='
|
235
|
-
category_filter='all'):
|
256
|
+
def list_keywords(output_format='json', name_filter=None,
|
257
|
+
category_filter='all', output_file=None):
|
236
258
|
"""罗列所有关键字信息"""
|
237
259
|
import json
|
238
260
|
|
@@ -319,7 +341,25 @@ def list_keywords(output_format='text', name_filter=None,
|
|
319
341
|
keyword_data = format_keyword_info_json(name, info)
|
320
342
|
keywords_data['keywords'].append(keyword_data)
|
321
343
|
|
322
|
-
|
344
|
+
json_output = json.dumps(keywords_data, ensure_ascii=False, indent=2)
|
345
|
+
|
346
|
+
# 确定输出文件名
|
347
|
+
if output_file is None:
|
348
|
+
output_file = 'keywords.json'
|
349
|
+
|
350
|
+
# 写入到文件
|
351
|
+
try:
|
352
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
353
|
+
f.write(json_output)
|
354
|
+
print(f"关键字信息已保存到文件: {output_file}")
|
355
|
+
print(f"共 {total_count} 个关键字")
|
356
|
+
for cat, count in category_counts.items():
|
357
|
+
cat_names = {'builtin': '内置', 'custom': '自定义', 'remote': '远程'}
|
358
|
+
print(f" {cat_names.get(cat, cat)}: {count} 个")
|
359
|
+
except Exception as e:
|
360
|
+
print(f"保存文件失败: {e}")
|
361
|
+
# 如果写入文件失败,则回退到打印
|
362
|
+
print(json_output)
|
323
363
|
|
324
364
|
|
325
365
|
def load_yaml_variables(args):
|
@@ -433,16 +473,19 @@ def main():
|
|
433
473
|
list_keywords(
|
434
474
|
output_format=args.format,
|
435
475
|
name_filter=args.filter,
|
436
|
-
category_filter=args.category
|
476
|
+
category_filter=args.category,
|
477
|
+
output_file=args.output
|
437
478
|
)
|
438
479
|
elif args.command == 'run':
|
439
480
|
run_dsl_tests(args)
|
440
481
|
elif args.command == 'list-keywords-compat':
|
441
482
|
# 向后兼容:旧的--list-keywords格式
|
483
|
+
output_file = getattr(args, 'output', None)
|
442
484
|
list_keywords(
|
443
485
|
output_format=args.format,
|
444
486
|
name_filter=args.filter,
|
445
|
-
category_filter=args.category
|
487
|
+
category_filter=args.category,
|
488
|
+
output_file=output_file
|
446
489
|
)
|
447
490
|
elif args.command == 'run-compat':
|
448
491
|
# 向后兼容:默认执行DSL测试
|
@@ -453,5 +496,38 @@ def main():
|
|
453
496
|
sys.exit(1)
|
454
497
|
|
455
498
|
|
499
|
+
def main_list_keywords():
|
500
|
+
"""关键字列表命令的专用入口点"""
|
501
|
+
parser = argparse.ArgumentParser(description='查看pytest-dsl可用关键字列表')
|
502
|
+
parser.add_argument(
|
503
|
+
'--format', choices=['text', 'json'],
|
504
|
+
default='json',
|
505
|
+
help='输出格式:json(默认) 或 text'
|
506
|
+
)
|
507
|
+
parser.add_argument(
|
508
|
+
'--output', '-o', type=str, default=None,
|
509
|
+
help='输出文件路径(仅对 json 格式有效,默认为 keywords.json)'
|
510
|
+
)
|
511
|
+
parser.add_argument(
|
512
|
+
'--filter', type=str, default=None,
|
513
|
+
help='过滤关键字名称(支持部分匹配)'
|
514
|
+
)
|
515
|
+
parser.add_argument(
|
516
|
+
'--category',
|
517
|
+
choices=['builtin', 'custom', 'remote', 'all'],
|
518
|
+
default='all',
|
519
|
+
help='关键字类别:builtin(内置)、custom(自定义)、remote(远程)、all(全部,默认)'
|
520
|
+
)
|
521
|
+
|
522
|
+
args = parser.parse_args()
|
523
|
+
|
524
|
+
list_keywords(
|
525
|
+
output_format=args.format,
|
526
|
+
name_filter=args.filter,
|
527
|
+
category_filter=args.category,
|
528
|
+
output_file=args.output
|
529
|
+
)
|
530
|
+
|
531
|
+
|
456
532
|
if __name__ == '__main__':
|
457
533
|
main()
|
@@ -1,13 +1,14 @@
|
|
1
|
-
from typing import Dict, Any, Callable, List
|
1
|
+
from typing import Dict, Any, Callable, List, Optional
|
2
2
|
import functools
|
3
3
|
import allure
|
4
4
|
|
5
5
|
|
6
6
|
class Parameter:
|
7
|
-
def __init__(self, name: str, mapping: str, description: str):
|
7
|
+
def __init__(self, name: str, mapping: str, description: str, default: Any = None):
|
8
8
|
self.name = name
|
9
9
|
self.mapping = mapping
|
10
10
|
self.description = description
|
11
|
+
self.default = default
|
11
12
|
|
12
13
|
|
13
14
|
class KeywordManager:
|
@@ -34,6 +35,7 @@ class KeywordManager:
|
|
34
35
|
|
35
36
|
param_list = [Parameter(**p) for p in parameters]
|
36
37
|
mapping = {p.name: p.mapping for p in param_list}
|
38
|
+
defaults = {p.mapping: p.default for p in param_list if p.default is not None}
|
37
39
|
|
38
40
|
# 自动添加 step_name 到 mapping 中
|
39
41
|
mapping["步骤名称"] = "step_name"
|
@@ -41,7 +43,8 @@ class KeywordManager:
|
|
41
43
|
self._keywords[name] = {
|
42
44
|
'func': wrapper,
|
43
45
|
'mapping': mapping,
|
44
|
-
'parameters': param_list
|
46
|
+
'parameters': param_list,
|
47
|
+
'defaults': defaults # 存储默认值
|
45
48
|
}
|
46
49
|
return wrapper
|
47
50
|
return decorator
|
@@ -51,7 +54,19 @@ class KeywordManager:
|
|
51
54
|
keyword_info = self._keywords.get(keyword_name)
|
52
55
|
if not keyword_info:
|
53
56
|
raise KeyError(f"未注册的关键字: {keyword_name}")
|
54
|
-
|
57
|
+
|
58
|
+
# 应用默认值
|
59
|
+
final_params = {}
|
60
|
+
defaults = keyword_info.get('defaults', {})
|
61
|
+
|
62
|
+
# 首先设置所有默认值
|
63
|
+
for param_key, default_value in defaults.items():
|
64
|
+
final_params[param_key] = default_value
|
65
|
+
|
66
|
+
# 然后用传入的参数覆盖默认值
|
67
|
+
final_params.update(params)
|
68
|
+
|
69
|
+
return keyword_info['func'](**final_params)
|
55
70
|
|
56
71
|
def get_keyword_info(self, keyword_name: str) -> Dict:
|
57
72
|
"""获取关键字信息"""
|
@@ -99,8 +114,9 @@ class KeywordManager:
|
|
99
114
|
description="自定义的步骤名称,用于在报告中显示"
|
100
115
|
))
|
101
116
|
for param in info['parameters']:
|
117
|
+
default_info = f" (默认值: {param.default})" if param.default is not None else ""
|
102
118
|
docs.append(
|
103
|
-
f" {param.name} ({param.mapping}): {param.description}")
|
119
|
+
f" {param.name} ({param.mapping}): {param.description}{default_info}")
|
104
120
|
docs.append("")
|
105
121
|
return "\n".join(docs)
|
106
122
|
|
@@ -48,7 +48,8 @@ def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
|
|
48
48
|
Args:
|
49
49
|
actual: 实际值
|
50
50
|
expected: 预期值
|
51
|
-
operator: 比较运算符 (==, !=, >, <, >=, <=, contains, not_contains,
|
51
|
+
operator: 比较运算符 (==, !=, >, <, >=, <=, contains, not_contains,
|
52
|
+
matches, and, or, not)
|
52
53
|
|
53
54
|
Returns:
|
54
55
|
比较结果 (True/False)
|
@@ -96,8 +97,9 @@ def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
|
|
96
97
|
|
97
98
|
|
98
99
|
@keyword_manager.register('断言', [
|
99
|
-
{'name': '条件', 'mapping': 'condition',
|
100
|
-
|
100
|
+
{'name': '条件', 'mapping': 'condition',
|
101
|
+
'description': '断言条件表达式,例如: "${value} == 100" 或 "1 + 1 == 2"'},
|
102
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': '断言失败'},
|
101
103
|
])
|
102
104
|
def assert_condition(**kwargs):
|
103
105
|
"""执行表达式断言
|
@@ -116,9 +118,11 @@ def assert_condition(**kwargs):
|
|
116
118
|
message = kwargs.get('message', '断言失败')
|
117
119
|
context = kwargs.get('context')
|
118
120
|
|
119
|
-
# 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains,
|
121
|
+
# 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains,
|
122
|
+
# matches, in, and, or, not
|
120
123
|
# 格式: "left_value operator right_value" 或 "boolean_expression"
|
121
|
-
operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains",
|
124
|
+
operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains",
|
125
|
+
"matches", "in", "and", "or", "not"]
|
122
126
|
|
123
127
|
# 先检查是否包含这些操作符
|
124
128
|
operator_used = None
|
@@ -132,7 +136,8 @@ def assert_condition(**kwargs):
|
|
132
136
|
try:
|
133
137
|
# 对条件进行变量替换
|
134
138
|
if '${' in condition:
|
135
|
-
condition = context.executor.variable_replacer.replace_in_string(
|
139
|
+
condition = context.executor.variable_replacer.replace_in_string(
|
140
|
+
condition)
|
136
141
|
# 尝试直接求值
|
137
142
|
result = eval(condition)
|
138
143
|
if not isinstance(result, bool):
|
@@ -141,7 +146,8 @@ def assert_condition(**kwargs):
|
|
141
146
|
raise AssertionError(f"{message}. 布尔表达式求值为假: {condition}")
|
142
147
|
return True
|
143
148
|
except Exception as e:
|
144
|
-
raise AssertionError(
|
149
|
+
raise AssertionError(
|
150
|
+
f"{message}. 无法解析条件表达式: {condition}. 错误: {str(e)}")
|
145
151
|
|
146
152
|
# 解析左值和右值
|
147
153
|
left_value, right_value = condition.split(f" {operator_used} ", 1)
|
@@ -351,8 +357,8 @@ def assert_condition(**kwargs):
|
|
351
357
|
{'name': 'JSON数据', 'mapping': 'json_data', 'description': 'JSON数据(字符串或对象)'},
|
352
358
|
{'name': 'JSONPath', 'mapping': 'jsonpath', 'description': 'JSONPath表达式'},
|
353
359
|
{'name': '预期值', 'mapping': 'expected_value', 'description': '预期的值'},
|
354
|
-
{'name': '操作符', 'mapping': 'operator', 'description': '
|
355
|
-
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
|
360
|
+
{'name': '操作符', 'mapping': 'operator', 'description': '比较操作符', 'default': '=='},
|
361
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': 'JSON断言失败'},
|
356
362
|
])
|
357
363
|
def assert_json(**kwargs):
|
358
364
|
"""执行JSON断言
|
@@ -476,7 +482,7 @@ def extract_json(**kwargs):
|
|
476
482
|
@keyword_manager.register('类型断言', [
|
477
483
|
{'name': '值', 'mapping': 'value', 'description': '要检查的值'},
|
478
484
|
{'name': '类型', 'mapping': 'type', 'description': '预期的类型 (string, number, boolean, list, object, null)'},
|
479
|
-
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
|
485
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': '类型断言失败'},
|
480
486
|
])
|
481
487
|
def assert_type(**kwargs):
|
482
488
|
"""断言值的类型
|
@@ -545,8 +551,8 @@ def assert_type(**kwargs):
|
|
545
551
|
@keyword_manager.register('数据比较', [
|
546
552
|
{'name': '实际值', 'mapping': 'actual', 'description': '实际值'},
|
547
553
|
{'name': '预期值', 'mapping': 'expected', 'description': '预期值'},
|
548
|
-
{'name': '操作符', 'mapping': 'operator', 'description': '
|
549
|
-
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
|
554
|
+
{'name': '操作符', 'mapping': 'operator', 'description': '比较操作符', 'default': '=='},
|
555
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': '数据比较失败'},
|
550
556
|
])
|
551
557
|
def compare_values(**kwargs):
|
552
558
|
"""比较两个值
|
@@ -223,14 +223,14 @@ def _normalize_retry_config(config, assert_retry_count=None, assert_retry_interv
|
|
223
223
|
|
224
224
|
|
225
225
|
@keyword_manager.register('HTTP请求', [
|
226
|
-
{'name': '客户端', 'mapping': 'client', 'description': '客户端名称,对应YAML变量文件中的客户端配置'},
|
226
|
+
{'name': '客户端', 'mapping': 'client', 'description': '客户端名称,对应YAML变量文件中的客户端配置', 'default': 'default'},
|
227
227
|
{'name': '配置', 'mapping': 'config', 'description': '包含请求、捕获和断言的YAML配置'},
|
228
228
|
{'name': '会话', 'mapping': 'session', 'description': '会话名称,用于在多个请求间保持会话状态'},
|
229
229
|
{'name': '保存响应', 'mapping': 'save_response', 'description': '将完整响应保存到指定变量名中'},
|
230
|
-
{'name': '禁用授权', 'mapping': 'disable_auth', 'description': '
|
230
|
+
{'name': '禁用授权', 'mapping': 'disable_auth', 'description': '禁用客户端配置中的授权机制', 'default': False},
|
231
231
|
{'name': '模板', 'mapping': 'template', 'description': '使用YAML变量文件中定义的请求模板'},
|
232
|
-
{'name': '断言重试次数', 'mapping': 'assert_retry_count', 'description': '断言失败时的重试次数'},
|
233
|
-
{'name': '断言重试间隔', 'mapping': 'assert_retry_interval', 'description': '断言重试间隔时间(秒)'}
|
232
|
+
{'name': '断言重试次数', 'mapping': 'assert_retry_count', 'description': '断言失败时的重试次数', 'default': 0},
|
233
|
+
{'name': '断言重试间隔', 'mapping': 'assert_retry_interval', 'description': '断言重试间隔时间(秒)', 'default': 1}
|
234
234
|
])
|
235
235
|
def http_request(context, **kwargs):
|
236
236
|
"""执行HTTP请求
|
@@ -24,46 +24,54 @@ def return_result(**kwargs):
|
|
24
24
|
|
25
25
|
|
26
26
|
@keyword_manager.register('等待', [
|
27
|
-
{'name': '秒数', 'mapping': 'seconds',
|
27
|
+
{'name': '秒数', 'mapping': 'seconds',
|
28
|
+
'description': '等待的秒数,可以是小数', 'default': 1}
|
28
29
|
])
|
29
30
|
def wait_seconds(**kwargs):
|
30
|
-
"""
|
31
|
+
"""等待指定的时间
|
31
32
|
|
32
33
|
Args:
|
33
|
-
seconds:
|
34
|
+
seconds: 等待的秒数,默认为1秒
|
34
35
|
"""
|
35
|
-
seconds = float(kwargs.get('seconds',
|
36
|
+
seconds = float(kwargs.get('seconds', 1))
|
37
|
+
|
36
38
|
with allure.step(f"等待 {seconds} 秒"):
|
37
39
|
time.sleep(seconds)
|
38
|
-
|
40
|
+
allure.attach(
|
41
|
+
f"等待时间: {seconds} 秒",
|
42
|
+
name="等待完成",
|
43
|
+
attachment_type=allure.attachment_type.TEXT
|
44
|
+
)
|
39
45
|
|
40
46
|
|
41
47
|
@keyword_manager.register('获取当前时间', [
|
42
|
-
{'name': '格式', 'mapping': 'format',
|
43
|
-
|
48
|
+
{'name': '格式', 'mapping': 'format',
|
49
|
+
'description': '时间格式,例如 "%Y-%m-%d %H:%M:%S"', 'default': 'timestamp'},
|
50
|
+
{'name': '时区', 'mapping': 'timezone',
|
51
|
+
'description': '时区,例如 "Asia/Shanghai"', 'default': 'Asia/Shanghai'}
|
44
52
|
])
|
45
53
|
def get_current_time(**kwargs):
|
46
54
|
"""获取当前时间
|
47
55
|
|
48
56
|
Args:
|
49
|
-
format:
|
50
|
-
timezone:
|
57
|
+
format: 时间格式,如果设置为'timestamp'则返回时间戳
|
58
|
+
timezone: 时区,默认为Asia/Shanghai
|
51
59
|
|
52
60
|
Returns:
|
53
|
-
str: 格式化的时间字符串或时间戳
|
61
|
+
str/float: 格式化的时间字符串或时间戳
|
54
62
|
"""
|
55
|
-
|
56
|
-
|
63
|
+
format_str = kwargs.get('format', 'timestamp')
|
64
|
+
timezone_str = kwargs.get('timezone', 'Asia/Shanghai')
|
57
65
|
|
58
66
|
# 获取当前时间
|
59
|
-
if
|
67
|
+
if timezone_str and timezone_str != 'local':
|
60
68
|
import pytz
|
61
69
|
try:
|
62
|
-
tz = pytz.timezone(
|
70
|
+
tz = pytz.timezone(timezone_str)
|
63
71
|
current_time = datetime.datetime.now(tz)
|
64
72
|
except Exception as e:
|
65
73
|
allure.attach(
|
66
|
-
f"时区设置异常: {str(e)}",
|
74
|
+
f"时区设置异常: {str(e)},使用本地时区",
|
67
75
|
name="时区设置异常",
|
68
76
|
attachment_type=allure.attachment_type.TEXT
|
69
77
|
)
|
@@ -72,34 +80,36 @@ def get_current_time(**kwargs):
|
|
72
80
|
current_time = datetime.datetime.now()
|
73
81
|
|
74
82
|
# 格式化时间
|
75
|
-
if
|
83
|
+
if format_str and format_str != 'timestamp':
|
76
84
|
try:
|
77
|
-
result = current_time.strftime(
|
85
|
+
result = current_time.strftime(format_str)
|
78
86
|
except Exception as e:
|
79
87
|
allure.attach(
|
80
|
-
f"时间格式化异常: {str(e)}",
|
88
|
+
f"时间格式化异常: {str(e)},返回默认格式",
|
81
89
|
name="时间格式化异常",
|
82
90
|
attachment_type=allure.attachment_type.TEXT
|
83
91
|
)
|
84
|
-
result =
|
92
|
+
result = current_time.strftime('%Y-%m-%d %H:%M:%S')
|
85
93
|
else:
|
86
94
|
# 返回时间戳
|
87
|
-
result =
|
95
|
+
result = int(current_time.timestamp())
|
88
96
|
|
89
97
|
return result
|
90
98
|
|
91
99
|
|
92
100
|
@keyword_manager.register('生成随机字符串', [
|
93
|
-
{'name': '长度', 'mapping': 'length',
|
101
|
+
{'name': '长度', 'mapping': 'length',
|
102
|
+
'description': '随机字符串的长度', 'default': 8},
|
94
103
|
{'name': '类型', 'mapping': 'type',
|
95
|
-
|
104
|
+
'description': '字符类型:字母(letters)、数字(digits)、字母数字(alphanumeric)、全部(all)',
|
105
|
+
'default': 'alphanumeric'}
|
96
106
|
])
|
97
107
|
def generate_random_string(**kwargs):
|
98
|
-
"""
|
108
|
+
"""生成指定长度和类型的随机字符串
|
99
109
|
|
100
110
|
Args:
|
101
|
-
length:
|
102
|
-
type:
|
111
|
+
length: 字符串长度
|
112
|
+
type: 字符类型
|
103
113
|
|
104
114
|
Returns:
|
105
115
|
str: 生成的随机字符串
|
@@ -134,12 +144,15 @@ def generate_random_string(**kwargs):
|
|
134
144
|
|
135
145
|
|
136
146
|
@keyword_manager.register('生成随机数', [
|
137
|
-
{'name': '最小值', 'mapping': 'min',
|
138
|
-
|
139
|
-
{'name': '
|
147
|
+
{'name': '最小值', 'mapping': 'min',
|
148
|
+
'description': '随机数的最小值', 'default': 0},
|
149
|
+
{'name': '最大值', 'mapping': 'max',
|
150
|
+
'description': '随机数的最大值', 'default': 100},
|
151
|
+
{'name': '小数位数', 'mapping': 'decimals',
|
152
|
+
'description': '小数位数,0表示整数', 'default': 0}
|
140
153
|
])
|
141
154
|
def generate_random_number(**kwargs):
|
142
|
-
"""
|
155
|
+
"""生成指定范围内的随机数
|
143
156
|
|
144
157
|
Args:
|
145
158
|
min: 随机数的最小值
|
@@ -172,24 +185,27 @@ def generate_random_number(**kwargs):
|
|
172
185
|
|
173
186
|
@keyword_manager.register('字符串操作', [
|
174
187
|
{'name': '操作', 'mapping': 'operation',
|
175
|
-
|
188
|
+
'description': '操作类型:拼接(concat)、替换(replace)、分割(split)、大写(upper)、小写(lower)、去空格(strip)',
|
189
|
+
'default': 'strip'},
|
176
190
|
{'name': '字符串', 'mapping': 'string', 'description': '要操作的字符串'},
|
177
|
-
{'name': '参数1', 'mapping': 'param1',
|
178
|
-
|
191
|
+
{'name': '参数1', 'mapping': 'param1',
|
192
|
+
'description': '操作参数1,根据操作类型不同而不同', 'default': ''},
|
193
|
+
{'name': '参数2', 'mapping': 'param2',
|
194
|
+
'description': '操作参数2,根据操作类型不同而不同', 'default': ''}
|
179
195
|
])
|
180
196
|
def string_operation(**kwargs):
|
181
197
|
"""字符串操作
|
182
198
|
|
183
199
|
Args:
|
184
|
-
operation:
|
200
|
+
operation: 操作类型,默认为strip(去空格)
|
185
201
|
string: 要操作的字符串
|
186
|
-
param1: 操作参数1
|
187
|
-
param2: 操作参数2
|
202
|
+
param1: 操作参数1,默认为空字符串
|
203
|
+
param2: 操作参数2,默认为空字符串
|
188
204
|
|
189
205
|
Returns:
|
190
206
|
str: 操作结果
|
191
207
|
"""
|
192
|
-
operation = kwargs.get('operation', '').lower()
|
208
|
+
operation = kwargs.get('operation', 'strip').lower()
|
193
209
|
string = str(kwargs.get('string', ''))
|
194
210
|
param1 = kwargs.get('param1', '')
|
195
211
|
param2 = kwargs.get('param2', '')
|
@@ -204,12 +220,16 @@ def string_operation(**kwargs):
|
|
204
220
|
result = string.replace(str(param1), str(param2))
|
205
221
|
elif operation == 'split':
|
206
222
|
# 分割字符串
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
223
|
+
if param1: # 如果提供了分隔符
|
224
|
+
result = string.split(str(param1))
|
225
|
+
if param2 and param2.isdigit():
|
226
|
+
# 如果提供了索引,返回指定位置的元素
|
227
|
+
index = int(param2)
|
228
|
+
if 0 <= index < len(result):
|
229
|
+
result = result[index]
|
230
|
+
else:
|
231
|
+
# 默认按空格分割
|
232
|
+
result = string.split()
|
213
233
|
elif operation == 'upper':
|
214
234
|
# 转大写
|
215
235
|
result = string.upper()
|
@@ -217,19 +237,21 @@ def string_operation(**kwargs):
|
|
217
237
|
# 转小写
|
218
238
|
result = string.lower()
|
219
239
|
elif operation == 'strip':
|
220
|
-
#
|
240
|
+
# 去空格(默认操作)
|
221
241
|
result = string.strip()
|
222
242
|
else:
|
223
243
|
# 未知操作,返回原字符串
|
224
244
|
allure.attach(
|
225
|
-
f"未知的字符串操作: {operation}",
|
245
|
+
f"未知的字符串操作: {operation},使用默认操作strip",
|
226
246
|
name="字符串操作错误",
|
227
247
|
attachment_type=allure.attachment_type.TEXT
|
228
248
|
)
|
249
|
+
result = string.strip()
|
229
250
|
|
230
251
|
with allure.step(f"字符串操作: {operation}"):
|
231
252
|
allure.attach(
|
232
|
-
f"原字符串: {string}\n操作: {operation}\n参数1: {param1}\n
|
253
|
+
f"原字符串: {string}\n操作: {operation}\n参数1: {param1}\n"
|
254
|
+
f"参数2: {param2}\n结果: {result}",
|
233
255
|
name="字符串操作结果",
|
234
256
|
attachment_type=allure.attachment_type.TEXT
|
235
257
|
)
|
@@ -239,7 +261,8 @@ def string_operation(**kwargs):
|
|
239
261
|
|
240
262
|
@keyword_manager.register('日志', [
|
241
263
|
{'name': '级别', 'mapping': 'level',
|
242
|
-
|
264
|
+
'description': '日志级别:DEBUG, INFO, WARNING, ERROR, CRITICAL',
|
265
|
+
'default': 'INFO'},
|
243
266
|
{'name': '消息', 'mapping': 'message', 'description': '日志消息内容'}
|
244
267
|
])
|
245
268
|
def log_message(**kwargs):
|
@@ -270,8 +293,10 @@ def log_message(**kwargs):
|
|
270
293
|
|
271
294
|
@keyword_manager.register('执行命令', [
|
272
295
|
{'name': '命令', 'mapping': 'command', 'description': '要执行的系统命令'},
|
273
|
-
{'name': '超时', 'mapping': 'timeout',
|
274
|
-
|
296
|
+
{'name': '超时', 'mapping': 'timeout',
|
297
|
+
'description': '命令执行超时时间(秒)', 'default': 60},
|
298
|
+
{'name': '捕获输出', 'mapping': 'capture_output',
|
299
|
+
'description': '是否捕获命令输出', 'default': True}
|
275
300
|
])
|
276
301
|
def execute_command(**kwargs):
|
277
302
|
"""执行系统命令
|