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.
Files changed (57) hide show
  1. kdtest_pw/__init__.py +50 -0
  2. kdtest_pw/action/__init__.py +7 -0
  3. kdtest_pw/action/base_keyword.py +292 -0
  4. kdtest_pw/action/element_plus/__init__.py +23 -0
  5. kdtest_pw/action/element_plus/el_cascader.py +263 -0
  6. kdtest_pw/action/element_plus/el_datepicker.py +324 -0
  7. kdtest_pw/action/element_plus/el_dialog.py +317 -0
  8. kdtest_pw/action/element_plus/el_form.py +443 -0
  9. kdtest_pw/action/element_plus/el_menu.py +456 -0
  10. kdtest_pw/action/element_plus/el_select.py +268 -0
  11. kdtest_pw/action/element_plus/el_table.py +442 -0
  12. kdtest_pw/action/element_plus/el_tree.py +364 -0
  13. kdtest_pw/action/element_plus/el_upload.py +313 -0
  14. kdtest_pw/action/key_retrieval.py +311 -0
  15. kdtest_pw/action/page_action.py +1129 -0
  16. kdtest_pw/api/__init__.py +6 -0
  17. kdtest_pw/api/api_keyword.py +251 -0
  18. kdtest_pw/api/request_handler.py +232 -0
  19. kdtest_pw/cases/__init__.py +6 -0
  20. kdtest_pw/cases/case_collector.py +182 -0
  21. kdtest_pw/cases/case_executor.py +359 -0
  22. kdtest_pw/cases/read/__init__.py +6 -0
  23. kdtest_pw/cases/read/cell_handler.py +305 -0
  24. kdtest_pw/cases/read/excel_reader.py +223 -0
  25. kdtest_pw/cli/__init__.py +5 -0
  26. kdtest_pw/cli/run.py +318 -0
  27. kdtest_pw/common.py +106 -0
  28. kdtest_pw/core/__init__.py +7 -0
  29. kdtest_pw/core/browser_manager.py +196 -0
  30. kdtest_pw/core/config_loader.py +235 -0
  31. kdtest_pw/core/page_context.py +228 -0
  32. kdtest_pw/data/__init__.py +5 -0
  33. kdtest_pw/data/init_data.py +105 -0
  34. kdtest_pw/data/static/elementData.yaml +59 -0
  35. kdtest_pw/data/static/parameters.json +24 -0
  36. kdtest_pw/plugins/__init__.py +6 -0
  37. kdtest_pw/plugins/element_plus_plugin/__init__.py +5 -0
  38. kdtest_pw/plugins/element_plus_plugin/elementData/elementData.yaml +144 -0
  39. kdtest_pw/plugins/element_plus_plugin/element_plus_plugin.py +237 -0
  40. kdtest_pw/plugins/element_plus_plugin/my.ini +23 -0
  41. kdtest_pw/plugins/plugin_base.py +180 -0
  42. kdtest_pw/plugins/plugin_loader.py +260 -0
  43. kdtest_pw/product.py +5 -0
  44. kdtest_pw/reference.py +99 -0
  45. kdtest_pw/utils/__init__.py +13 -0
  46. kdtest_pw/utils/built_in_function.py +376 -0
  47. kdtest_pw/utils/decorator.py +211 -0
  48. kdtest_pw/utils/log/__init__.py +6 -0
  49. kdtest_pw/utils/log/html_report.py +336 -0
  50. kdtest_pw/utils/log/logger.py +123 -0
  51. kdtest_pw/utils/public_script.py +366 -0
  52. kdtest_pw-2.0.0.dist-info/METADATA +169 -0
  53. kdtest_pw-2.0.0.dist-info/RECORD +57 -0
  54. kdtest_pw-2.0.0.dist-info/WHEEL +5 -0
  55. kdtest_pw-2.0.0.dist-info/entry_points.txt +2 -0
  56. kdtest_pw-2.0.0.dist-info/licenses/LICENSE +21 -0
  57. kdtest_pw-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,311 @@
1
+ """关键字分发器"""
2
+
3
+ from typing import Any, Dict, Optional, Callable, List, Tuple
4
+ from playwright.sync_api import Page
5
+
6
+ from .page_action import KeyWordTest
7
+ from ..reference import GSTORE, MODULEDATA, INFO
8
+
9
+
10
+ class KeyRetrieval:
11
+ """关键字分发器
12
+
13
+ 负责解析 Excel 中的关键字并分发到对应的方法执行。
14
+ 支持插件扩展关键字。
15
+ """
16
+
17
+ def __init__(self, page: Optional[Page] = None):
18
+ """初始化关键字分发器
19
+
20
+ Args:
21
+ page: Playwright Page 实例
22
+ """
23
+ self._page = page or GSTORE.get('page')
24
+ self._keyword_test: Optional[KeyWordTest] = None
25
+ self._plugin_keywords: Dict[str, Callable] = {}
26
+ self._element_data: Dict[str, Any] = {}
27
+
28
+ if self._page:
29
+ self._keyword_test = KeyWordTest(self._page)
30
+ GSTORE['keyWord'] = self._keyword_test
31
+
32
+ def set_page(self, page: Page) -> None:
33
+ """设置页面实例
34
+
35
+ Args:
36
+ page: Playwright Page 实例
37
+ """
38
+ self._page = page
39
+ self._keyword_test = KeyWordTest(page)
40
+ GSTORE['keyWord'] = self._keyword_test
41
+
42
+ def register_plugin_keyword(self, name: str, handler: Callable) -> None:
43
+ """注册插件关键字
44
+
45
+ Args:
46
+ name: 关键字名称
47
+ handler: 处理函数
48
+ """
49
+ self._plugin_keywords[name] = handler
50
+ INFO(f"注册插件关键字: {name}")
51
+
52
+ def register_plugin_keywords(self, keywords: Dict[str, Callable]) -> None:
53
+ """批量注册插件关键字
54
+
55
+ Args:
56
+ keywords: 关键字字典 {name: handler}
57
+ """
58
+ self._plugin_keywords.update(keywords)
59
+ INFO(f"注册插件关键字: {list(keywords.keys())}")
60
+
61
+ def set_element_data(self, data: Dict[str, Any]) -> None:
62
+ """设置元素数据(来自 elementData.yaml)
63
+
64
+ Args:
65
+ data: 元素数据字典
66
+ """
67
+ self._element_data = data
68
+
69
+ def resolve_element(self, element_path: str) -> Tuple[str, str, Optional[int]]:
70
+ """解析元素路径
71
+
72
+ 支持格式:
73
+ - "module/element" - 从 elementData 获取
74
+ - "module/element[0]" - 带索引
75
+ - "xpath://div" - 直接定位表达式
76
+ - "css:.class" - 直接定位表达式
77
+
78
+ Args:
79
+ element_path: 元素路径
80
+
81
+ Returns:
82
+ Tuple[targeting, element, index]: 定位类型、定位表达式、索引
83
+ """
84
+ index = None
85
+
86
+ # 检查是否有索引
87
+ if '[' in element_path and element_path.endswith(']'):
88
+ bracket_pos = element_path.rfind('[')
89
+ index_str = element_path[bracket_pos + 1:-1]
90
+ element_path = element_path[:bracket_pos]
91
+ try:
92
+ index = int(index_str)
93
+ except ValueError:
94
+ if index_str == 'first':
95
+ index = 0
96
+ elif index_str == 'last':
97
+ index = -1
98
+
99
+ # 检查是否是直接定位表达式
100
+ if '=' in element_path or element_path.startswith('//'):
101
+ if element_path.startswith('xpath=') or element_path.startswith('xpath:') or element_path.startswith('//'):
102
+ if element_path.startswith('xpath=') or element_path.startswith('xpath:'):
103
+ return 'xpath', element_path.split('=', 1)[1] if '=' in element_path else element_path[6:], index
104
+ return 'xpath', element_path, index
105
+ elif element_path.startswith('css=') or element_path.startswith('css:'):
106
+ return 'css', element_path.split('=', 1)[1] if '=' in element_path else element_path[4:], index
107
+ elif element_path.startswith('id='):
108
+ return 'id', element_path[3:], index
109
+ elif element_path.startswith('name='):
110
+ return 'name', element_path[5:], index
111
+ elif element_path.startswith('text='):
112
+ return 'text', element_path[5:], index
113
+
114
+ # 从 elementData 解析
115
+ if '/' in element_path:
116
+ parts = element_path.split('/')
117
+ if len(parts) >= 2:
118
+ module = parts[0]
119
+ element_name = '/'.join(parts[1:])
120
+
121
+ # 在 elementData 中查找
122
+ if module in self._element_data:
123
+ module_data = self._element_data[module]
124
+ if element_name in module_data:
125
+ element_info = module_data[element_name]
126
+ if isinstance(element_info, list) and len(element_info) >= 2:
127
+ return element_info[0], element_info[1], index
128
+
129
+ # 作为 CSS 选择器处理
130
+ return 'css', element_path, index
131
+
132
+ def execute(
133
+ self,
134
+ keyword: str,
135
+ locator_info: List[str] = None,
136
+ value: str = None,
137
+ **kwargs
138
+ ) -> Any:
139
+ """执行关键字
140
+
141
+ Args:
142
+ keyword: 关键字名称
143
+ locator_info: 定位信息列表 [element_path, ...] 或 [targeting, element, ...]
144
+ value: 操作值
145
+ **kwargs: 其他参数
146
+
147
+ Returns:
148
+ 执行结果
149
+ """
150
+ if not self._keyword_test:
151
+ raise RuntimeError("KeyWordTest 未初始化,请先调用 set_page()")
152
+
153
+ # 解析定位信息
154
+ targeting = None
155
+ element = None
156
+ index = None
157
+
158
+ if locator_info:
159
+ # 过滤空值
160
+ locator_info = [str(item).strip() for item in locator_info if item and str(item).strip()]
161
+
162
+ if len(locator_info) >= 1:
163
+ # 检查是否是 elementData 路径
164
+ first = locator_info[0]
165
+ if '/' in first or first.startswith('//') or '=' in first:
166
+ targeting, element, index = self.resolve_element(first)
167
+ # 如果有第二个参数且是数字,作为索引
168
+ if len(locator_info) >= 2 and locator_info[1]:
169
+ try:
170
+ index = int(locator_info[1])
171
+ except ValueError:
172
+ pass
173
+ else:
174
+ # 传统格式: [targeting, element, index?]
175
+ targeting = first
176
+ if len(locator_info) >= 2:
177
+ element = locator_info[1]
178
+ if len(locator_info) >= 3 and locator_info[2]:
179
+ try:
180
+ index = int(locator_info[2])
181
+ except ValueError:
182
+ if locator_info[2] == 'first':
183
+ index = 0
184
+ elif locator_info[2] == 'last':
185
+ index = -1
186
+
187
+ # 构建参数
188
+ params = {}
189
+ if targeting:
190
+ params['targeting'] = targeting
191
+ if element:
192
+ params['element'] = element
193
+ if index is not None:
194
+ params['index'] = index
195
+ if value is not None and value != '':
196
+ params['content'] = str(value)
197
+
198
+ # 合并额外参数
199
+ params.update(kwargs)
200
+
201
+ # 查找并执行关键字
202
+ # 1. 先检查插件关键字
203
+ if keyword in self._plugin_keywords:
204
+ handler = self._plugin_keywords[keyword]
205
+ return self._call_handler(handler, params)
206
+
207
+ # 2. 检查核心关键字
208
+ if hasattr(self._keyword_test, keyword):
209
+ method = getattr(self._keyword_test, keyword)
210
+ return self._call_method(method, params)
211
+
212
+ # 3. 关键字不存在
213
+ raise ValueError(f"未知关键字: {keyword}")
214
+
215
+ def _call_method(self, method: Callable, params: Dict[str, Any]) -> Any:
216
+ """调用方法
217
+
218
+ Args:
219
+ method: 方法
220
+ params: 参数字典
221
+
222
+ Returns:
223
+ 执行结果
224
+ """
225
+ import inspect
226
+ sig = inspect.signature(method)
227
+
228
+ # 分离位置参数和关键字参数
229
+ positional_args = []
230
+ keyword_args = {}
231
+
232
+ for name, param in sig.parameters.items():
233
+ if name in ('self',):
234
+ continue
235
+
236
+ if param.kind == inspect.Parameter.KEYWORD_ONLY:
237
+ # 关键字参数
238
+ if name in params:
239
+ keyword_args[name] = params[name]
240
+ elif param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY):
241
+ # 位置参数
242
+ if name in params:
243
+ positional_args.append(params[name])
244
+ elif param.default is not inspect.Parameter.empty:
245
+ # 有默认值,跳过
246
+ pass
247
+
248
+ return method(*positional_args, **keyword_args)
249
+
250
+ def _call_handler(self, handler: Callable, params: Dict[str, Any]) -> Any:
251
+ """调用处理函数
252
+
253
+ Args:
254
+ handler: 处理函数
255
+ params: 参数字典
256
+
257
+ Returns:
258
+ 执行结果
259
+ """
260
+ import inspect
261
+ sig = inspect.signature(handler)
262
+
263
+ # 检查是否接受 **kwargs
264
+ has_var_keyword = any(
265
+ p.kind == inspect.Parameter.VAR_KEYWORD
266
+ for p in sig.parameters.values()
267
+ )
268
+
269
+ if has_var_keyword:
270
+ return handler(**params)
271
+
272
+ # 过滤参数
273
+ valid_params = {
274
+ k: v for k, v in params.items()
275
+ if k in sig.parameters
276
+ }
277
+ return handler(**valid_params)
278
+
279
+ def get_available_keywords(self) -> List[str]:
280
+ """获取所有可用关键字
281
+
282
+ Returns:
283
+ List[str]: 关键字列表
284
+ """
285
+ keywords = []
286
+
287
+ # 核心关键字
288
+ if self._keyword_test:
289
+ for name in dir(self._keyword_test):
290
+ if not name.startswith('_') and callable(getattr(self._keyword_test, name)):
291
+ keywords.append(name)
292
+
293
+ # 插件关键字
294
+ keywords.extend(self._plugin_keywords.keys())
295
+
296
+ return sorted(set(keywords))
297
+
298
+ def has_keyword(self, keyword: str) -> bool:
299
+ """检查关键字是否存在
300
+
301
+ Args:
302
+ keyword: 关键字名称
303
+
304
+ Returns:
305
+ bool: 是否存在
306
+ """
307
+ if keyword in self._plugin_keywords:
308
+ return True
309
+ if self._keyword_test and hasattr(self._keyword_test, keyword):
310
+ return True
311
+ return False