kdtest-pw 2.0.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.
- kdtest_pw/__init__.py +50 -0
- kdtest_pw/action/__init__.py +7 -0
- kdtest_pw/action/base_keyword.py +292 -0
- kdtest_pw/action/element_plus/__init__.py +23 -0
- kdtest_pw/action/element_plus/el_cascader.py +263 -0
- kdtest_pw/action/element_plus/el_datepicker.py +324 -0
- kdtest_pw/action/element_plus/el_dialog.py +317 -0
- kdtest_pw/action/element_plus/el_form.py +443 -0
- kdtest_pw/action/element_plus/el_menu.py +456 -0
- kdtest_pw/action/element_plus/el_select.py +268 -0
- kdtest_pw/action/element_plus/el_table.py +442 -0
- kdtest_pw/action/element_plus/el_tree.py +364 -0
- kdtest_pw/action/element_plus/el_upload.py +313 -0
- kdtest_pw/action/key_retrieval.py +311 -0
- kdtest_pw/action/page_action.py +1129 -0
- kdtest_pw/api/__init__.py +6 -0
- kdtest_pw/api/api_keyword.py +251 -0
- kdtest_pw/api/request_handler.py +232 -0
- kdtest_pw/cases/__init__.py +6 -0
- kdtest_pw/cases/case_collector.py +182 -0
- kdtest_pw/cases/case_executor.py +359 -0
- kdtest_pw/cases/read/__init__.py +6 -0
- kdtest_pw/cases/read/cell_handler.py +305 -0
- kdtest_pw/cases/read/excel_reader.py +223 -0
- kdtest_pw/cli/__init__.py +5 -0
- kdtest_pw/cli/run.py +318 -0
- kdtest_pw/common.py +106 -0
- kdtest_pw/core/__init__.py +7 -0
- kdtest_pw/core/browser_manager.py +196 -0
- kdtest_pw/core/config_loader.py +235 -0
- kdtest_pw/core/page_context.py +228 -0
- kdtest_pw/data/__init__.py +5 -0
- kdtest_pw/data/init_data.py +105 -0
- kdtest_pw/data/static/elementData.yaml +59 -0
- kdtest_pw/data/static/parameters.json +24 -0
- kdtest_pw/plugins/__init__.py +6 -0
- kdtest_pw/plugins/element_plus_plugin/__init__.py +5 -0
- kdtest_pw/plugins/element_plus_plugin/elementData/elementData.yaml +144 -0
- kdtest_pw/plugins/element_plus_plugin/element_plus_plugin.py +237 -0
- kdtest_pw/plugins/element_plus_plugin/my.ini +23 -0
- kdtest_pw/plugins/plugin_base.py +180 -0
- kdtest_pw/plugins/plugin_loader.py +260 -0
- kdtest_pw/product.py +5 -0
- kdtest_pw/reference.py +99 -0
- kdtest_pw/utils/__init__.py +13 -0
- kdtest_pw/utils/built_in_function.py +376 -0
- kdtest_pw/utils/decorator.py +211 -0
- kdtest_pw/utils/log/__init__.py +6 -0
- kdtest_pw/utils/log/html_report.py +336 -0
- kdtest_pw/utils/log/logger.py +123 -0
- kdtest_pw/utils/public_script.py +366 -0
- kdtest_pw-2.0.0.dist-info/METADATA +169 -0
- kdtest_pw-2.0.0.dist-info/RECORD +57 -0
- kdtest_pw-2.0.0.dist-info/WHEEL +5 -0
- kdtest_pw-2.0.0.dist-info/entry_points.txt +2 -0
- kdtest_pw-2.0.0.dist-info/licenses/LICENSE +21 -0
- kdtest_pw-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""插件基类"""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Dict, Any, Callable, List, Optional
|
|
5
|
+
from playwright.sync_api import Page
|
|
6
|
+
|
|
7
|
+
from ..reference import MODULEDATA, INFO
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PluginBase(ABC):
|
|
11
|
+
"""插件基类
|
|
12
|
+
|
|
13
|
+
所有插件必须继承此类并实现必要方法。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# 插件元信息
|
|
17
|
+
name: str = "base_plugin"
|
|
18
|
+
version: str = "1.0.0"
|
|
19
|
+
description: str = "Base plugin"
|
|
20
|
+
author: str = ""
|
|
21
|
+
|
|
22
|
+
def __init__(self, page: Page = None):
|
|
23
|
+
"""初始化插件
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
page: Playwright Page 实例
|
|
27
|
+
"""
|
|
28
|
+
self._page = page
|
|
29
|
+
self._keywords: Dict[str, Callable] = {}
|
|
30
|
+
self._element_data: Dict[str, Any] = {}
|
|
31
|
+
self._config: Dict[str, Any] = {}
|
|
32
|
+
self._initialized = False
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def setup(self) -> None:
|
|
36
|
+
"""插件初始化
|
|
37
|
+
|
|
38
|
+
子类必须实现此方法,用于:
|
|
39
|
+
- 注册关键字
|
|
40
|
+
- 加载元素数据
|
|
41
|
+
- 初始化配置
|
|
42
|
+
"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def teardown(self) -> None:
|
|
46
|
+
"""插件清理
|
|
47
|
+
|
|
48
|
+
子类可选实现,用于清理资源。
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
def register_keyword(self, name: str, handler: Callable) -> None:
|
|
53
|
+
"""注册关键字
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name: 关键字名称
|
|
57
|
+
handler: 处理函数
|
|
58
|
+
"""
|
|
59
|
+
self._keywords[name] = handler
|
|
60
|
+
INFO(f"[{self.name}] 注册关键字: {name}")
|
|
61
|
+
|
|
62
|
+
def register_keywords(self, keywords: Dict[str, Callable]) -> None:
|
|
63
|
+
"""批量注册关键字
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
keywords: 关键字字典
|
|
67
|
+
"""
|
|
68
|
+
for name, handler in keywords.items():
|
|
69
|
+
self.register_keyword(name, handler)
|
|
70
|
+
|
|
71
|
+
def load_element_data(self, data: Dict[str, Any]) -> None:
|
|
72
|
+
"""加载元素数据
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
data: 元素数据字典
|
|
76
|
+
"""
|
|
77
|
+
self._element_data.update(data)
|
|
78
|
+
INFO(f"[{self.name}] 加载元素数据: {len(data)} 个模块")
|
|
79
|
+
|
|
80
|
+
def load_element_data_from_file(self, file_path: str) -> None:
|
|
81
|
+
"""从文件加载元素数据
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
file_path: YAML 文件路径
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
import yaml
|
|
88
|
+
from pathlib import Path
|
|
89
|
+
|
|
90
|
+
path = Path(file_path)
|
|
91
|
+
if not path.exists():
|
|
92
|
+
INFO(f"[{self.name}] 元素数据文件不存在: {file_path}", "WARNING")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
96
|
+
data = yaml.safe_load(f) or {}
|
|
97
|
+
self.load_element_data(data)
|
|
98
|
+
|
|
99
|
+
except ImportError:
|
|
100
|
+
INFO(f"[{self.name}] PyYAML 未安装,无法加载 YAML 文件", "WARNING")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
INFO(f"[{self.name}] 加载元素数据失败: {e}", "ERROR")
|
|
103
|
+
|
|
104
|
+
def load_config(self, config: Dict[str, Any]) -> None:
|
|
105
|
+
"""加载配置
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
config: 配置字典
|
|
109
|
+
"""
|
|
110
|
+
self._config.update(config)
|
|
111
|
+
|
|
112
|
+
def load_config_from_file(self, file_path: str) -> None:
|
|
113
|
+
"""从 INI 文件加载配置
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
file_path: INI 文件路径
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
import configparser
|
|
120
|
+
from pathlib import Path
|
|
121
|
+
|
|
122
|
+
path = Path(file_path)
|
|
123
|
+
if not path.exists():
|
|
124
|
+
INFO(f"[{self.name}] 配置文件不存在: {file_path}", "WARNING")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
config = configparser.ConfigParser()
|
|
128
|
+
config.read(str(path), encoding='utf-8')
|
|
129
|
+
|
|
130
|
+
for section in config.sections():
|
|
131
|
+
self._config[section] = dict(config.items(section))
|
|
132
|
+
|
|
133
|
+
INFO(f"[{self.name}] 加载配置: {list(self._config.keys())}")
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
INFO(f"[{self.name}] 加载配置失败: {e}", "ERROR")
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def keywords(self) -> Dict[str, Callable]:
|
|
140
|
+
"""获取所有关键字"""
|
|
141
|
+
return self._keywords.copy()
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def element_data(self) -> Dict[str, Any]:
|
|
145
|
+
"""获取元素数据"""
|
|
146
|
+
return self._element_data.copy()
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def config(self) -> Dict[str, Any]:
|
|
150
|
+
"""获取配置"""
|
|
151
|
+
return self._config.copy()
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def page(self) -> Optional[Page]:
|
|
155
|
+
"""获取 Page 实例"""
|
|
156
|
+
return self._page
|
|
157
|
+
|
|
158
|
+
def set_page(self, page: Page) -> None:
|
|
159
|
+
"""设置 Page 实例
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
page: Playwright Page 实例
|
|
163
|
+
"""
|
|
164
|
+
self._page = page
|
|
165
|
+
|
|
166
|
+
def initialize(self) -> None:
|
|
167
|
+
"""初始化插件(内部调用)"""
|
|
168
|
+
if not self._initialized:
|
|
169
|
+
self.setup()
|
|
170
|
+
self._initialized = True
|
|
171
|
+
|
|
172
|
+
# 存储到全局
|
|
173
|
+
MODULEDATA[self.name] = {
|
|
174
|
+
'keywords': self._keywords,
|
|
175
|
+
'element_data': self._element_data,
|
|
176
|
+
'config': self._config,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
def __repr__(self) -> str:
|
|
180
|
+
return f"<Plugin {self.name} v{self.version}>"
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""动态插件加载器"""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import importlib.util
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Dict, List, Optional, Type
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from playwright.sync_api import Page
|
|
10
|
+
|
|
11
|
+
from .plugin_base import PluginBase
|
|
12
|
+
from ..reference import MODULEDATA, INFO
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginLoader:
|
|
16
|
+
"""动态插件加载器
|
|
17
|
+
|
|
18
|
+
从指定目录或模块加载插件。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, page: Page = None):
|
|
22
|
+
"""初始化加载器
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
page: Playwright Page 实例
|
|
26
|
+
"""
|
|
27
|
+
self._page = page
|
|
28
|
+
self._plugins: Dict[str, PluginBase] = {}
|
|
29
|
+
self._plugin_paths: List[Path] = []
|
|
30
|
+
|
|
31
|
+
def add_plugin_path(self, path: str) -> None:
|
|
32
|
+
"""添加插件搜索路径
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
path: 插件目录路径
|
|
36
|
+
"""
|
|
37
|
+
plugin_path = Path(path)
|
|
38
|
+
if plugin_path.exists() and plugin_path.is_dir():
|
|
39
|
+
self._plugin_paths.append(plugin_path)
|
|
40
|
+
INFO(f"添加插件路径: {plugin_path}")
|
|
41
|
+
|
|
42
|
+
def load_from_path(self, path: str) -> Optional[PluginBase]:
|
|
43
|
+
"""从路径加载单个插件
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path: 插件目录或模块路径
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
PluginBase: 加载的插件实例
|
|
50
|
+
"""
|
|
51
|
+
plugin_path = Path(path)
|
|
52
|
+
|
|
53
|
+
if plugin_path.is_dir():
|
|
54
|
+
# 目录形式的插件
|
|
55
|
+
return self._load_from_directory(plugin_path)
|
|
56
|
+
elif plugin_path.is_file() and plugin_path.suffix == '.py':
|
|
57
|
+
# 单文件插件
|
|
58
|
+
return self._load_from_file(plugin_path)
|
|
59
|
+
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def _load_from_directory(self, directory: Path) -> Optional[PluginBase]:
|
|
63
|
+
"""从目录加载插件
|
|
64
|
+
|
|
65
|
+
目录结构:
|
|
66
|
+
plugin_name/
|
|
67
|
+
├── __init__.py
|
|
68
|
+
├── plugin_name_plugin.py (包含插件类)
|
|
69
|
+
├── my.ini (可选配置)
|
|
70
|
+
└── elementData/
|
|
71
|
+
└── elementData.yaml (可选元素数据)
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
directory: 插件目录
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
PluginBase: 插件实例
|
|
78
|
+
"""
|
|
79
|
+
plugin_name = directory.name
|
|
80
|
+
|
|
81
|
+
# 查找插件主文件
|
|
82
|
+
plugin_file = directory / f"{plugin_name}_plugin.py"
|
|
83
|
+
if not plugin_file.exists():
|
|
84
|
+
# 尝试其他命名
|
|
85
|
+
plugin_files = list(directory.glob("*_plugin.py"))
|
|
86
|
+
if plugin_files:
|
|
87
|
+
plugin_file = plugin_files[0]
|
|
88
|
+
else:
|
|
89
|
+
INFO(f"未找到插件主文件: {directory}", "WARNING")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
# 加载模块
|
|
93
|
+
spec = importlib.util.spec_from_file_location(plugin_name, plugin_file)
|
|
94
|
+
if spec is None or spec.loader is None:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
module = importlib.util.module_from_spec(spec)
|
|
98
|
+
sys.modules[plugin_name] = module
|
|
99
|
+
spec.loader.exec_module(module)
|
|
100
|
+
|
|
101
|
+
# 查找插件类
|
|
102
|
+
plugin_class = self._find_plugin_class(module)
|
|
103
|
+
if not plugin_class:
|
|
104
|
+
INFO(f"未找到 PluginBase 子类: {plugin_file}", "WARNING")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
# 创建实例
|
|
108
|
+
plugin = plugin_class(self._page)
|
|
109
|
+
|
|
110
|
+
# 加载配置
|
|
111
|
+
config_file = directory / "my.ini"
|
|
112
|
+
if config_file.exists():
|
|
113
|
+
plugin.load_config_from_file(str(config_file))
|
|
114
|
+
|
|
115
|
+
# 加载元素数据
|
|
116
|
+
element_data_file = directory / "elementData" / "elementData.yaml"
|
|
117
|
+
if element_data_file.exists():
|
|
118
|
+
plugin.load_element_data_from_file(str(element_data_file))
|
|
119
|
+
|
|
120
|
+
# 初始化
|
|
121
|
+
plugin.initialize()
|
|
122
|
+
|
|
123
|
+
self._plugins[plugin.name] = plugin
|
|
124
|
+
INFO(f"加载插件: {plugin}")
|
|
125
|
+
|
|
126
|
+
return plugin
|
|
127
|
+
|
|
128
|
+
def _load_from_file(self, file_path: Path) -> Optional[PluginBase]:
|
|
129
|
+
"""从单文件加载插件
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
file_path: 插件文件路径
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
PluginBase: 插件实例
|
|
136
|
+
"""
|
|
137
|
+
plugin_name = file_path.stem
|
|
138
|
+
|
|
139
|
+
spec = importlib.util.spec_from_file_location(plugin_name, file_path)
|
|
140
|
+
if spec is None or spec.loader is None:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
module = importlib.util.module_from_spec(spec)
|
|
144
|
+
sys.modules[plugin_name] = module
|
|
145
|
+
spec.loader.exec_module(module)
|
|
146
|
+
|
|
147
|
+
plugin_class = self._find_plugin_class(module)
|
|
148
|
+
if not plugin_class:
|
|
149
|
+
INFO(f"未找到 PluginBase 子类: {file_path}", "WARNING")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
plugin = plugin_class(self._page)
|
|
153
|
+
plugin.initialize()
|
|
154
|
+
|
|
155
|
+
self._plugins[plugin.name] = plugin
|
|
156
|
+
INFO(f"加载插件: {plugin}")
|
|
157
|
+
|
|
158
|
+
return plugin
|
|
159
|
+
|
|
160
|
+
def _find_plugin_class(self, module) -> Optional[Type[PluginBase]]:
|
|
161
|
+
"""在模块中查找 PluginBase 子类
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
module: Python 模块
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
PluginBase 子类
|
|
168
|
+
"""
|
|
169
|
+
for name in dir(module):
|
|
170
|
+
obj = getattr(module, name)
|
|
171
|
+
if (
|
|
172
|
+
isinstance(obj, type) and
|
|
173
|
+
issubclass(obj, PluginBase) and
|
|
174
|
+
obj is not PluginBase
|
|
175
|
+
):
|
|
176
|
+
return obj
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
def load_builtin_plugins(self) -> None:
|
|
180
|
+
"""加载内置插件"""
|
|
181
|
+
# 加载 Element Plus 插件
|
|
182
|
+
builtin_path = Path(__file__).parent / "element_plus_plugin"
|
|
183
|
+
if builtin_path.exists():
|
|
184
|
+
self.load_from_path(str(builtin_path))
|
|
185
|
+
|
|
186
|
+
def discover_plugins(self) -> List[PluginBase]:
|
|
187
|
+
"""从所有插件路径发现并加载插件
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List[PluginBase]: 加载的插件列表
|
|
191
|
+
"""
|
|
192
|
+
plugins = []
|
|
193
|
+
|
|
194
|
+
for plugin_path in self._plugin_paths:
|
|
195
|
+
for item in plugin_path.iterdir():
|
|
196
|
+
if item.is_dir() and not item.name.startswith('_'):
|
|
197
|
+
plugin = self._load_from_directory(item)
|
|
198
|
+
if plugin:
|
|
199
|
+
plugins.append(plugin)
|
|
200
|
+
elif item.is_file() and item.suffix == '.py' and not item.name.startswith('_'):
|
|
201
|
+
plugin = self._load_from_file(item)
|
|
202
|
+
if plugin:
|
|
203
|
+
plugins.append(plugin)
|
|
204
|
+
|
|
205
|
+
return plugins
|
|
206
|
+
|
|
207
|
+
def get_plugin(self, name: str) -> Optional[PluginBase]:
|
|
208
|
+
"""获取插件
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
name: 插件名称
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
PluginBase: 插件实例
|
|
215
|
+
"""
|
|
216
|
+
return self._plugins.get(name)
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def plugins(self) -> Dict[str, PluginBase]:
|
|
220
|
+
"""获取所有加载的插件"""
|
|
221
|
+
return self._plugins.copy()
|
|
222
|
+
|
|
223
|
+
def get_all_keywords(self) -> Dict[str, callable]:
|
|
224
|
+
"""获取所有插件的关键字
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Dict[str, Callable]: 关键字字典
|
|
228
|
+
"""
|
|
229
|
+
keywords = {}
|
|
230
|
+
for plugin in self._plugins.values():
|
|
231
|
+
keywords.update(plugin.keywords)
|
|
232
|
+
return keywords
|
|
233
|
+
|
|
234
|
+
def get_all_element_data(self) -> Dict[str, any]:
|
|
235
|
+
"""获取所有插件的元素数据
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Dict[str, Any]: 元素数据字典
|
|
239
|
+
"""
|
|
240
|
+
element_data = {}
|
|
241
|
+
for plugin in self._plugins.values():
|
|
242
|
+
element_data.update(plugin.element_data)
|
|
243
|
+
return element_data
|
|
244
|
+
|
|
245
|
+
def set_page(self, page: Page) -> None:
|
|
246
|
+
"""设置 Page 实例
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
page: Playwright Page 实例
|
|
250
|
+
"""
|
|
251
|
+
self._page = page
|
|
252
|
+
for plugin in self._plugins.values():
|
|
253
|
+
plugin.set_page(page)
|
|
254
|
+
|
|
255
|
+
def unload_all(self) -> None:
|
|
256
|
+
"""卸载所有插件"""
|
|
257
|
+
for plugin in self._plugins.values():
|
|
258
|
+
plugin.teardown()
|
|
259
|
+
self._plugins.clear()
|
|
260
|
+
INFO("已卸载所有插件")
|
kdtest_pw/product.py
ADDED
kdtest_pw/reference.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""全局存储模块
|
|
2
|
+
|
|
3
|
+
提供全局变量存储,用于在模块间共享数据。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
# 全局存储 - 核心组件引用
|
|
9
|
+
GSTORE: Dict[str, Any] = {
|
|
10
|
+
'browser': None, # Browser实例
|
|
11
|
+
'context': None, # BrowserContext实例
|
|
12
|
+
'page': None, # 当前Page实例
|
|
13
|
+
'keyWord': None, # KeyWordTest实例
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# 全局系统数据存储
|
|
17
|
+
GSDSTORE: Dict[str, Any] = {
|
|
18
|
+
'START': {}, # 启动参数
|
|
19
|
+
'WORKPATH': {}, # 工作路径
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# 模块数据存储 - 插件数据
|
|
23
|
+
MODULEDATA: Dict[str, Any] = {}
|
|
24
|
+
|
|
25
|
+
# 私有数据存储
|
|
26
|
+
PRIVATEDATA: Dict[str, Any] = {
|
|
27
|
+
'PAGES': [], # 多页面列表
|
|
28
|
+
'ELEVALUE': {}, # 元素值缓存
|
|
29
|
+
'CURRENT_CASE': None, # 当前用例名称
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# 用例统计数据
|
|
33
|
+
CASESDATA: Dict[str, int] = {
|
|
34
|
+
'total': 0,
|
|
35
|
+
'passed': 0,
|
|
36
|
+
'failed': 0,
|
|
37
|
+
'skipped': 0,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def INFO(message: str, level: str = "INFO") -> None:
|
|
42
|
+
"""统一日志输出
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
message: 日志消息
|
|
46
|
+
level: 日志级别 (INFO, WARNING, ERROR, DEBUG)
|
|
47
|
+
"""
|
|
48
|
+
print(f"[{level}] {message}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_global(key: str, default: Any = None) -> Any:
|
|
52
|
+
"""获取全局存储的值
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
key: 键名
|
|
56
|
+
default: 默认值
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
存储的值或默认值
|
|
60
|
+
"""
|
|
61
|
+
return GSTORE.get(key, default)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_global(key: str, value: Any) -> None:
|
|
65
|
+
"""设置全局存储的值
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
key: 键名
|
|
69
|
+
value: 值
|
|
70
|
+
"""
|
|
71
|
+
GSTORE[key] = value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_element_value(key: str, default: Any = None) -> Any:
|
|
75
|
+
"""获取缓存的元素值
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
key: 键名
|
|
79
|
+
default: 默认值
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
缓存的元素值或默认值
|
|
83
|
+
"""
|
|
84
|
+
return PRIVATEDATA['ELEVALUE'].get(key, default)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def set_element_value(key: str, value: Any) -> None:
|
|
88
|
+
"""设置元素值缓存
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
key: 键名
|
|
92
|
+
value: 值
|
|
93
|
+
"""
|
|
94
|
+
PRIVATEDATA['ELEVALUE'][key] = value
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def clear_element_values() -> None:
|
|
98
|
+
"""清除所有元素值缓存"""
|
|
99
|
+
PRIVATEDATA['ELEVALUE'].clear()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""工具模块"""
|
|
2
|
+
|
|
3
|
+
from .decorator import retry, timeout, catch_exception
|
|
4
|
+
from .built_in_function import BuiltInFunction
|
|
5
|
+
from .public_script import PublicScript
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'retry',
|
|
9
|
+
'timeout',
|
|
10
|
+
'catch_exception',
|
|
11
|
+
'BuiltInFunction',
|
|
12
|
+
'PublicScript',
|
|
13
|
+
]
|