pytest-dsl 0.10.0__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.
- pytest_dsl/cli.py +533 -89
- pytest_dsl/core/custom_keyword_manager.py +1 -4
- pytest_dsl/core/keyword_manager.py +77 -3
- pytest_dsl/core/plugin_discovery.py +38 -1
- pytest_dsl/templates/keywords_report.html +862 -0
- {pytest_dsl-0.10.0.dist-info → pytest_dsl-0.11.0.dist-info}/METADATA +2 -1
- {pytest_dsl-0.10.0.dist-info → pytest_dsl-0.11.0.dist-info}/RECORD +11 -10
- {pytest_dsl-0.10.0.dist-info → pytest_dsl-0.11.0.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.10.0.dist-info → pytest_dsl-0.11.0.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.10.0.dist-info → pytest_dsl-0.11.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.10.0.dist-info → pytest_dsl-0.11.0.dist-info}/top_level.txt +0 -0
@@ -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
|
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:
|
@@ -16,8 +16,14 @@ class KeywordManager:
|
|
16
16
|
self._keywords: Dict[str, Dict] = {}
|
17
17
|
self.current_context = None
|
18
18
|
|
19
|
-
def register(self, name: str, parameters: List[Dict]):
|
20
|
-
"""关键字注册装饰器
|
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
|
+
"""
|
21
27
|
def decorator(func: Callable) -> Callable:
|
22
28
|
@functools.wraps(func)
|
23
29
|
def wrapper(**kwargs):
|
@@ -40,15 +46,71 @@ class KeywordManager:
|
|
40
46
|
# 自动添加 step_name 到 mapping 中
|
41
47
|
mapping["步骤名称"] = "step_name"
|
42
48
|
|
43
|
-
|
49
|
+
# 构建关键字信息,包含来源信息
|
50
|
+
keyword_info = {
|
44
51
|
'func': wrapper,
|
45
52
|
'mapping': mapping,
|
46
53
|
'parameters': param_list,
|
47
54
|
'defaults': defaults # 存储默认值
|
48
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
|
49
65
|
return wrapper
|
50
66
|
return decorator
|
51
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
|
+
|
52
114
|
def execute(self, keyword_name: str, **params: Any) -> Any:
|
53
115
|
"""执行关键字"""
|
54
116
|
keyword_info = self._keywords.get(keyword_name)
|
@@ -84,6 +146,18 @@ class KeywordManager:
|
|
84
146
|
|
85
147
|
return keyword_info
|
86
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
|
+
|
87
161
|
def _log_execution(self, keyword_name: str, params: Dict, result: Any) -> None:
|
88
162
|
"""记录关键字执行结果"""
|
89
163
|
allure.attach(
|
@@ -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
|
-
|
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
|
发现并加载所有已安装的关键字插件
|