pytest-dsl 0.9.1__py3-none-any.whl → 0.11.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.
@@ -1,11 +1,8 @@
1
- from typing import Dict, Any, List, Optional
2
1
  import os
3
- import pathlib
4
2
  from pytest_dsl.core.lexer import get_lexer
5
3
  from pytest_dsl.core.parser import get_parser, Node
6
4
  from pytest_dsl.core.dsl_executor import DSLExecutor
7
5
  from pytest_dsl.core.keyword_manager import keyword_manager
8
- from pytest_dsl.core.context import TestContext
9
6
 
10
7
 
11
8
  class CustomKeywordManager:
@@ -127,7 +124,7 @@ class CustomKeywordManager:
127
124
  return
128
125
 
129
126
  for node in statements_node.children:
130
- if node.type == 'CustomKeyword':
127
+ if node.type in ['CustomKeyword', 'Function']:
131
128
  self._register_custom_keyword(node, file_path)
132
129
 
133
130
  def _register_custom_keyword(self, node: Node, file_path: str) -> None:
@@ -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:
@@ -15,8 +16,14 @@ class KeywordManager:
15
16
  self._keywords: Dict[str, Dict] = {}
16
17
  self.current_context = None
17
18
 
18
- def register(self, name: str, parameters: List[Dict]):
19
- """关键字注册装饰器"""
19
+ def register(self, name: str, parameters: List[Dict], source_info: Optional[Dict] = None):
20
+ """关键字注册装饰器
21
+
22
+ Args:
23
+ name: 关键字名称
24
+ parameters: 参数列表
25
+ source_info: 来源信息,包含 source_type, source_name, module_name 等
26
+ """
20
27
  def decorator(func: Callable) -> Callable:
21
28
  @functools.wraps(func)
22
29
  def wrapper(**kwargs):
@@ -34,24 +41,94 @@ class KeywordManager:
34
41
 
35
42
  param_list = [Parameter(**p) for p in parameters]
36
43
  mapping = {p.name: p.mapping for p in param_list}
44
+ defaults = {p.mapping: p.default for p in param_list if p.default is not None}
37
45
 
38
46
  # 自动添加 step_name 到 mapping 中
39
47
  mapping["步骤名称"] = "step_name"
40
48
 
41
- self._keywords[name] = {
49
+ # 构建关键字信息,包含来源信息
50
+ keyword_info = {
42
51
  'func': wrapper,
43
52
  'mapping': mapping,
44
- 'parameters': param_list
53
+ 'parameters': param_list,
54
+ 'defaults': defaults # 存储默认值
45
55
  }
56
+
57
+ # 添加来源信息
58
+ if source_info:
59
+ keyword_info.update(source_info)
60
+ else:
61
+ # 尝试从函数模块推断来源信息
62
+ keyword_info.update(self._infer_source_info(func))
63
+
64
+ self._keywords[name] = keyword_info
46
65
  return wrapper
47
66
  return decorator
48
67
 
68
+ def _infer_source_info(self, func: Callable) -> Dict:
69
+ """从函数推断来源信息"""
70
+ source_info = {}
71
+
72
+ if hasattr(func, '__module__'):
73
+ module_name = func.__module__
74
+ source_info['module_name'] = module_name
75
+
76
+ if module_name.startswith('pytest_dsl.keywords'):
77
+ # 内置关键字
78
+ source_info['source_type'] = 'builtin'
79
+ source_info['source_name'] = 'pytest-dsl内置'
80
+ elif 'pytest_dsl' in module_name:
81
+ # pytest-dsl相关但不是内置的
82
+ source_info['source_type'] = 'internal'
83
+ source_info['source_name'] = 'pytest-dsl'
84
+ else:
85
+ # 第三方插件或用户自定义
86
+ source_info['source_type'] = 'external'
87
+ # 提取可能的包名
88
+ parts = module_name.split('.')
89
+ if len(parts) > 1:
90
+ source_info['source_name'] = parts[0]
91
+ else:
92
+ source_info['source_name'] = module_name
93
+
94
+ return source_info
95
+
96
+ def register_with_source(self, name: str, parameters: List[Dict],
97
+ source_type: str, source_name: str, **kwargs):
98
+ """带来源信息的关键字注册装饰器
99
+
100
+ Args:
101
+ name: 关键字名称
102
+ parameters: 参数列表
103
+ source_type: 来源类型 (builtin, plugin, local, remote, project_custom)
104
+ source_name: 来源名称 (插件名、文件路径等)
105
+ **kwargs: 其他来源相关信息
106
+ """
107
+ source_info = {
108
+ 'source_type': source_type,
109
+ 'source_name': source_name,
110
+ **kwargs
111
+ }
112
+ return self.register(name, parameters, source_info)
113
+
49
114
  def execute(self, keyword_name: str, **params: Any) -> Any:
50
115
  """执行关键字"""
51
116
  keyword_info = self._keywords.get(keyword_name)
52
117
  if not keyword_info:
53
118
  raise KeyError(f"未注册的关键字: {keyword_name}")
54
- return keyword_info['func'](**params)
119
+
120
+ # 应用默认值
121
+ final_params = {}
122
+ defaults = keyword_info.get('defaults', {})
123
+
124
+ # 首先设置所有默认值
125
+ for param_key, default_value in defaults.items():
126
+ final_params[param_key] = default_value
127
+
128
+ # 然后用传入的参数覆盖默认值
129
+ final_params.update(params)
130
+
131
+ return keyword_info['func'](**final_params)
55
132
 
56
133
  def get_keyword_info(self, keyword_name: str) -> Dict:
57
134
  """获取关键字信息"""
@@ -69,6 +146,18 @@ class KeywordManager:
69
146
 
70
147
  return keyword_info
71
148
 
149
+ def get_keywords_by_source(self) -> Dict[str, List[str]]:
150
+ """按来源分组获取关键字"""
151
+ by_source = {}
152
+
153
+ for name, info in self._keywords.items():
154
+ source_name = info.get('source_name', '未知来源')
155
+ if source_name not in by_source:
156
+ by_source[source_name] = []
157
+ by_source[source_name].append(name)
158
+
159
+ return by_source
160
+
72
161
  def _log_execution(self, keyword_name: str, params: Dict, result: Any) -> None:
73
162
  """记录关键字执行结果"""
74
163
  allure.attach(
@@ -99,8 +188,9 @@ class KeywordManager:
99
188
  description="自定义的步骤名称,用于在报告中显示"
100
189
  ))
101
190
  for param in info['parameters']:
191
+ default_info = f" (默认值: {param.default})" if param.default is not None else ""
102
192
  docs.append(
103
- f" {param.name} ({param.mapping}): {param.description}")
193
+ f" {param.name} ({param.mapping}): {param.description}{default_info}")
104
194
  docs.append("")
105
195
  return "\n".join(docs)
106
196
 
@@ -62,7 +62,28 @@ def load_plugin_keywords(plugin_name: str) -> None:
62
62
 
63
63
  # 如果插件有register_keywords函数,调用它
64
64
  if hasattr(plugin, 'register_keywords') and callable(plugin.register_keywords):
65
- plugin.register_keywords(keyword_manager)
65
+ # 创建一个包装的关键字管理器,自动添加来源信息
66
+ class PluginKeywordManager:
67
+ def __init__(self, original_manager, plugin_name):
68
+ self.original_manager = original_manager
69
+ self.plugin_name = plugin_name
70
+
71
+ def register(self, name: str, parameters):
72
+ """带插件来源信息的注册方法"""
73
+ return self.original_manager.register_with_source(
74
+ name, parameters,
75
+ source_type='plugin',
76
+ source_name=plugin_name,
77
+ module_name=plugin_name
78
+ )
79
+
80
+ def __getattr__(self, name):
81
+ # 代理其他方法到原始管理器
82
+ return getattr(self.original_manager, name)
83
+
84
+ plugin_manager = PluginKeywordManager(keyword_manager, plugin_name)
85
+ plugin.register_keywords(plugin_manager)
86
+ print(f"通过register_keywords加载插件: {plugin_name}")
66
87
  return
67
88
 
68
89
  # 否则,遍历包中的所有模块
@@ -71,13 +92,29 @@ def load_plugin_keywords(plugin_name: str) -> None:
71
92
  if not is_pkg:
72
93
  try:
73
94
  module = importlib.import_module(name)
95
+ print(f"加载插件模块: {name}")
74
96
  # 模块已导入,关键字装饰器会自动注册
97
+ # 但我们需要在导入后更新来源信息
98
+ _update_keywords_source_info(plugin_name, name)
75
99
  except ImportError as e:
76
100
  print(f"无法导入模块 {name}: {e}")
77
101
  except ImportError as e:
78
102
  print(f"无法导入插件 {plugin_name}: {e}")
79
103
 
80
104
 
105
+ def _update_keywords_source_info(plugin_name: str, module_name: str):
106
+ """更新模块中关键字的来源信息"""
107
+ # 找到可能是新注册的关键字
108
+ for keyword_name, keyword_info in keyword_manager._keywords.items():
109
+ if keyword_info.get('module_name') == module_name:
110
+ # 更新来源信息
111
+ keyword_info.update({
112
+ 'source_type': 'plugin',
113
+ 'source_name': plugin_name,
114
+ 'plugin_module': module_name
115
+ })
116
+
117
+
81
118
  def load_all_plugins() -> None:
82
119
  """
83
120
  发现并加载所有已安装的关键字插件
@@ -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, matches, and, or, not)
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', 'description': '断言条件表达式,例如: "${value} == 100" 或 "1 + 1 == 2"'},
100
- {'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
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, matches, in, and, or, not
121
+ # 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains,
122
+ # matches, in, and, or, not
120
123
  # 格式: "left_value operator right_value" 或 "boolean_expression"
121
- operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains", "matches", "in", "and", "or", "not"]
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(condition)
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(f"{message}. 无法解析条件表达式: {condition}. 错误: {str(e)}")
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': '禁用客户端配置中的授权机制,默认为false'},
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', 'description': '等待的秒数,可以是小数'}
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', 0))
36
+ seconds = float(kwargs.get('seconds', 1))
37
+
36
38
  with allure.step(f"等待 {seconds} 秒"):
37
39
  time.sleep(seconds)
38
- return True
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', 'description': '时间格式,例如 "%Y-%m-%d %H:%M:%S",默认返回时间戳'},
43
- {'name': '时区', 'mapping': 'timezone', 'description': '时区,例如 "Asia/Shanghai",默认为本地时区'}
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
- time_format = kwargs.get('format')
56
- timezone = kwargs.get('timezone')
63
+ format_str = kwargs.get('format', 'timestamp')
64
+ timezone_str = kwargs.get('timezone', 'Asia/Shanghai')
57
65
 
58
66
  # 获取当前时间
59
- if timezone:
67
+ if timezone_str and timezone_str != 'local':
60
68
  import pytz
61
69
  try:
62
- tz = pytz.timezone(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 time_format:
83
+ if format_str and format_str != 'timestamp':
76
84
  try:
77
- result = current_time.strftime(time_format)
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 = str(current_time)
92
+ result = current_time.strftime('%Y-%m-%d %H:%M:%S')
85
93
  else:
86
94
  # 返回时间戳
87
- result = str(int(current_time.timestamp()))
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', 'description': '随机字符串的长度,默认为8'},
101
+ {'name': '长度', 'mapping': 'length',
102
+ 'description': '随机字符串的长度', 'default': 8},
94
103
  {'name': '类型', 'mapping': 'type',
95
- 'description': '字符类型:字母(letters)、数字(digits)、字母数字(alphanumeric)、全部(all),默认为字母数字'}
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', 'description': '随机数的最小值,默认为0'},
138
- {'name': '最大值', 'mapping': 'max', 'description': '随机数的最大值,默认为100'},
139
- {'name': '小数位数', 'mapping': 'decimals', 'description': '小数位数,默认为0(整数)'}
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
- 'description': '操作类型:拼接(concat)、替换(replace)、分割(split)、大写(upper)、小写(lower)、去空格(strip)'},
188
+ 'description': '操作类型:拼接(concat)、替换(replace)、分割(split)、大写(upper)、小写(lower)、去空格(strip)',
189
+ 'default': 'strip'},
176
190
  {'name': '字符串', 'mapping': 'string', 'description': '要操作的字符串'},
177
- {'name': '参数1', 'mapping': 'param1', 'description': '操作参数1,根据操作类型不同而不同'},
178
- {'name': '参数2', 'mapping': 'param2', 'description': '操作参数2,根据操作类型不同而不同'}
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
- result = string.split(str(param1))
208
- if param2 and param2.isdigit():
209
- # 如果提供了索引,返回指定位置的元素
210
- index = int(param2)
211
- if 0 <= index < len(result):
212
- result = result[index]
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参数2: {param2}\n结果: {result}",
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
- 'description': '日志级别:DEBUG, INFO, WARNING, ERROR, CRITICAL,默认为INFO'},
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', 'description': '命令执行超时时间(秒),默认为60秒'},
274
- {'name': '捕获输出', 'mapping': 'capture_output', 'description': '是否捕获命令输出,默认为True'}
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
  """执行系统命令