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,443 @@
1
+ """Element Plus Form 组件关键字"""
2
+
3
+ from playwright.sync_api import Page, Locator
4
+ from typing import Optional, Dict, List, Any
5
+
6
+ from ..base_keyword import BaseKeyword
7
+ from ...reference import INFO, set_element_value
8
+
9
+
10
+ class ElFormKeyword(BaseKeyword):
11
+ """Element Plus Form 表单组件关键字
12
+
13
+ 处理表单验证、表单项操作、错误提示等。
14
+ """
15
+
16
+ def __init__(self, page: Page):
17
+ super().__init__(page)
18
+
19
+ def el_form_input(
20
+ self,
21
+ targeting: str,
22
+ element: str,
23
+ index: Optional[int] = None,
24
+ *,
25
+ content: str,
26
+ label: str = None
27
+ ) -> None:
28
+ """表单输入(可通过 label 定位)
29
+
30
+ Args:
31
+ targeting: 定位类型
32
+ element: 定位表达式
33
+ index: 索引
34
+ content: 输入内容
35
+ label: 表单项 label(可选,用于定位)
36
+ """
37
+ INFO(f"表单输入: {content}")
38
+
39
+ if label:
40
+ # 通过 label 定位
41
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
42
+ input_el = form_item.locator('.el-input__inner, .el-textarea__inner')
43
+ else:
44
+ form_item = self.locator(targeting, element, index)
45
+ input_el = form_item.locator('.el-input__inner, .el-textarea__inner')
46
+ if input_el.count() == 0:
47
+ input_el = form_item
48
+
49
+ input_el.first.fill(str(content))
50
+
51
+ def el_form_select(
52
+ self,
53
+ targeting: str,
54
+ element: str,
55
+ index: Optional[int] = None,
56
+ *,
57
+ content: str,
58
+ label: str = None
59
+ ) -> None:
60
+ """表单下拉选择
61
+
62
+ Args:
63
+ targeting: 定位类型
64
+ element: 定位表达式
65
+ index: 索引
66
+ content: 选项值
67
+ label: 表单项 label
68
+ """
69
+ INFO(f"表单选择: {content}")
70
+
71
+ if label:
72
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
73
+ select = form_item.locator('.el-select')
74
+ else:
75
+ form_item = self.locator(targeting, element, index)
76
+ select = form_item.locator('.el-select')
77
+ if select.count() == 0:
78
+ select = form_item
79
+
80
+ select.first.click()
81
+
82
+ # 选择选项
83
+ dropdown = self.page.locator('.el-select-dropdown:visible')
84
+ dropdown.wait_for(state='visible', timeout=5000)
85
+ option = dropdown.locator(f'.el-select-dropdown__item:has-text("{content}")')
86
+ option.first.click()
87
+
88
+ def el_form_date(
89
+ self,
90
+ targeting: str,
91
+ element: str,
92
+ index: Optional[int] = None,
93
+ *,
94
+ content: str,
95
+ label: str = None
96
+ ) -> None:
97
+ """表单日期选择
98
+
99
+ Args:
100
+ targeting: 定位类型
101
+ element: 定位表达式
102
+ index: 索引
103
+ content: 日期值
104
+ label: 表单项 label
105
+ """
106
+ INFO(f"表单日期: {content}")
107
+
108
+ if label:
109
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
110
+ date_picker = form_item.locator('.el-date-editor')
111
+ else:
112
+ form_item = self.locator(targeting, element, index)
113
+ date_picker = form_item.locator('.el-date-editor')
114
+ if date_picker.count() == 0:
115
+ date_picker = form_item
116
+
117
+ input_el = date_picker.first.locator('.el-input__inner')
118
+ input_el.click()
119
+ input_el.fill(str(content))
120
+ input_el.press('Enter')
121
+
122
+ def el_form_checkbox(
123
+ self,
124
+ targeting: str,
125
+ element: str,
126
+ index: Optional[int] = None,
127
+ *,
128
+ content: str = 'true',
129
+ label: str = None
130
+ ) -> None:
131
+ """表单复选框
132
+
133
+ Args:
134
+ targeting: 定位类型
135
+ element: 定位表达式
136
+ index: 索引
137
+ content: 是否选中
138
+ label: 表单项 label 或复选框文本
139
+ """
140
+ checked = str(content).lower() in ('true', '1', 'yes')
141
+ INFO(f"表单复选框: {checked}")
142
+
143
+ if label:
144
+ # 先尝试作为复选框文本匹配
145
+ checkbox = self.page.locator(f'.el-checkbox:has(.el-checkbox__label:text("{label}"))')
146
+ if checkbox.count() == 0:
147
+ # 再尝试作为表单项 label
148
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
149
+ checkbox = form_item.locator('.el-checkbox')
150
+ else:
151
+ form_item = self.locator(targeting, element, index)
152
+ checkbox = form_item.locator('.el-checkbox')
153
+ if checkbox.count() == 0:
154
+ checkbox = form_item
155
+
156
+ is_checked = checkbox.first.locator('.is-checked').count() > 0
157
+
158
+ if checked != is_checked:
159
+ checkbox.first.click()
160
+
161
+ def el_form_radio(
162
+ self,
163
+ targeting: str,
164
+ element: str,
165
+ index: Optional[int] = None,
166
+ *,
167
+ content: str,
168
+ label: str = None
169
+ ) -> None:
170
+ """表单单选框
171
+
172
+ Args:
173
+ targeting: 定位类型
174
+ element: 定位表达式
175
+ index: 索引
176
+ content: 单选框文本或值
177
+ label: 表单项 label
178
+ """
179
+ INFO(f"表单单选: {content}")
180
+
181
+ if label:
182
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
183
+ else:
184
+ form_item = self.locator(targeting, element, index)
185
+
186
+ radio = form_item.locator(f'.el-radio:has(.el-radio__label:text("{content}"))')
187
+ radio.first.click()
188
+
189
+ def el_form_switch(
190
+ self,
191
+ targeting: str,
192
+ element: str,
193
+ index: Optional[int] = None,
194
+ *,
195
+ content: str = 'true',
196
+ label: str = None
197
+ ) -> None:
198
+ """表单开关
199
+
200
+ Args:
201
+ targeting: 定位类型
202
+ element: 定位表达式
203
+ index: 索引
204
+ content: 开关状态
205
+ label: 表单项 label
206
+ """
207
+ on = str(content).lower() in ('true', '1', 'yes', 'on')
208
+ INFO(f"表单开关: {on}")
209
+
210
+ if label:
211
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
212
+ switch = form_item.locator('.el-switch')
213
+ else:
214
+ form_item = self.locator(targeting, element, index)
215
+ switch = form_item.locator('.el-switch')
216
+ if switch.count() == 0:
217
+ switch = form_item
218
+
219
+ is_on = switch.first.locator('.is-checked').count() > 0
220
+
221
+ if on != is_on:
222
+ switch.first.click()
223
+
224
+ def el_form_submit(
225
+ self,
226
+ targeting: str,
227
+ element: str,
228
+ index: Optional[int] = None,
229
+ *,
230
+ content: str = '提交'
231
+ ) -> None:
232
+ """提交表单
233
+
234
+ Args:
235
+ targeting: 定位类型
236
+ element: 定位表达式
237
+ index: 索引
238
+ content: 提交按钮文本
239
+ """
240
+ INFO(f"提交表单: {content}")
241
+
242
+ form = self.locator(targeting, element, index)
243
+ submit_btn = form.locator(f'.el-button:has-text("{content}")')
244
+
245
+ if submit_btn.count() > 0:
246
+ submit_btn.first.click()
247
+ else:
248
+ # 尝试提交按钮
249
+ form.locator('.el-button--primary').first.click()
250
+
251
+ def el_form_reset(
252
+ self,
253
+ targeting: str,
254
+ element: str,
255
+ index: Optional[int] = None,
256
+ *,
257
+ content: str = '重置'
258
+ ) -> None:
259
+ """重置表单
260
+
261
+ Args:
262
+ targeting: 定位类型
263
+ element: 定位表达式
264
+ index: 索引
265
+ content: 重置按钮文本
266
+ """
267
+ INFO(f"重置表单: {content}")
268
+
269
+ form = self.locator(targeting, element, index)
270
+ reset_btn = form.locator(f'.el-button:has-text("{content}")')
271
+
272
+ if reset_btn.count() > 0:
273
+ reset_btn.first.click()
274
+
275
+ def el_form_get_error(
276
+ self,
277
+ targeting: str,
278
+ element: str,
279
+ index: Optional[int] = None,
280
+ *,
281
+ label: str = None
282
+ ) -> str:
283
+ """获取表单项错误信息
284
+
285
+ Args:
286
+ targeting: 定位类型
287
+ element: 定位表达式
288
+ index: 索引
289
+ label: 表单项 label
290
+
291
+ Returns:
292
+ str: 错误信息
293
+ """
294
+ if label:
295
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
296
+ else:
297
+ form_item = self.locator(targeting, element, index)
298
+
299
+ error = form_item.locator('.el-form-item__error')
300
+ if error.is_visible():
301
+ return error.text_content().strip()
302
+
303
+ return ''
304
+
305
+ def el_form_get_all_errors(
306
+ self,
307
+ targeting: str,
308
+ element: str,
309
+ index: Optional[int] = None
310
+ ) -> List[str]:
311
+ """获取所有表单错误信息
312
+
313
+ Args:
314
+ targeting: 定位类型
315
+ element: 定位表达式
316
+ index: 索引
317
+
318
+ Returns:
319
+ List[str]: 错误信息列表
320
+ """
321
+ form = self.locator(targeting, element, index)
322
+ errors = form.locator('.el-form-item__error')
323
+
324
+ result = []
325
+ for i in range(errors.count()):
326
+ text = errors.nth(i).text_content()
327
+ if text:
328
+ result.append(text.strip())
329
+
330
+ INFO(f"表单错误: {result}")
331
+ return result
332
+
333
+ def el_form_assert_error(
334
+ self,
335
+ targeting: str,
336
+ element: str,
337
+ index: Optional[int] = None,
338
+ *,
339
+ content: str,
340
+ label: str = None,
341
+ mode: str = 'equals'
342
+ ) -> bool:
343
+ """断言表单错误
344
+
345
+ Args:
346
+ targeting: 定位类型
347
+ element: 定位表达式
348
+ index: 索引
349
+ content: 期望的错误信息
350
+ label: 表单项 label
351
+ mode: 断言模式
352
+
353
+ Returns:
354
+ bool: 断言结果
355
+ """
356
+ actual = self.el_form_get_error(targeting, element, index, label=label)
357
+
358
+ if mode == 'equals':
359
+ result = actual == content
360
+ elif mode == 'contains':
361
+ result = content in actual
362
+ elif mode == 'exists':
363
+ result = len(actual) > 0
364
+ else:
365
+ result = actual == content
366
+
367
+ INFO(f"表单错误断言: '{actual}' {mode} '{content}' -> {result}")
368
+ return result
369
+
370
+ def el_form_assert_no_error(
371
+ self,
372
+ targeting: str,
373
+ element: str,
374
+ index: Optional[int] = None,
375
+ *,
376
+ label: str = None
377
+ ) -> bool:
378
+ """断言表单项无错误
379
+
380
+ Args:
381
+ targeting: 定位类型
382
+ element: 定位表达式
383
+ index: 索引
384
+ label: 表单项 label
385
+
386
+ Returns:
387
+ bool: 断言结果
388
+ """
389
+ error = self.el_form_get_error(targeting, element, index, label=label)
390
+ result = len(error) == 0
391
+
392
+ INFO(f"无错误断言: {result}")
393
+ return result
394
+
395
+ def el_form_fill(
396
+ self,
397
+ targeting: str,
398
+ element: str,
399
+ index: Optional[int] = None,
400
+ *,
401
+ content: str
402
+ ) -> None:
403
+ """批量填写表单
404
+
405
+ Args:
406
+ targeting: 定位类型
407
+ element: 定位表达式
408
+ index: 索引
409
+ content: JSON 格式的表单数据 {"label1": "value1", "label2": "value2"}
410
+ """
411
+ import json
412
+ data = json.loads(str(content))
413
+
414
+ INFO(f"批量填写表单: {list(data.keys())}")
415
+
416
+ for label, value in data.items():
417
+ form_item = self.page.locator(f'.el-form-item:has(.el-form-item__label:text("{label}"))')
418
+
419
+ if form_item.count() == 0:
420
+ INFO(f"未找到表单项: {label}", "WARNING")
421
+ continue
422
+
423
+ # 判断表单项类型
424
+ if form_item.locator('.el-select').count() > 0:
425
+ # 下拉选择
426
+ self.el_form_select('css', '.el-form-item', label=label, content=str(value))
427
+ elif form_item.locator('.el-date-editor').count() > 0:
428
+ # 日期选择
429
+ self.el_form_date('css', '.el-form-item', label=label, content=str(value))
430
+ elif form_item.locator('.el-switch').count() > 0:
431
+ # 开关
432
+ self.el_form_switch('css', '.el-form-item', label=label, content=str(value))
433
+ elif form_item.locator('.el-checkbox').count() > 0:
434
+ # 复选框
435
+ self.el_form_checkbox('css', '.el-form-item', label=label, content=str(value))
436
+ elif form_item.locator('.el-radio').count() > 0:
437
+ # 单选框
438
+ self.el_form_radio('css', '.el-form-item', label=label, content=str(value))
439
+ else:
440
+ # 默认输入框
441
+ self.el_form_input('css', '.el-form-item', label=label, content=str(value))
442
+
443
+ self.page.wait_for_timeout(100)