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,364 @@
|
|
|
1
|
+
"""Element Plus Tree 组件关键字"""
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api import Page, Locator
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
|
|
6
|
+
from ..base_keyword import BaseKeyword
|
|
7
|
+
from ...reference import INFO, set_element_value
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ElTreeKeyword(BaseKeyword):
|
|
11
|
+
"""Element Plus Tree 树形控件关键字
|
|
12
|
+
|
|
13
|
+
处理树形控件的节点展开、选择、勾选等操作。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, page: Page):
|
|
17
|
+
super().__init__(page)
|
|
18
|
+
|
|
19
|
+
def el_tree_click_node(
|
|
20
|
+
self,
|
|
21
|
+
targeting: str,
|
|
22
|
+
element: str,
|
|
23
|
+
index: Optional[int] = None,
|
|
24
|
+
*,
|
|
25
|
+
content: str
|
|
26
|
+
) -> None:
|
|
27
|
+
"""点击树节点
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
targeting: 定位类型
|
|
31
|
+
element: 定位表达式
|
|
32
|
+
index: 索引
|
|
33
|
+
content: 节点文本或路径 (格式: "父节点/子节点")
|
|
34
|
+
"""
|
|
35
|
+
INFO(f"点击树节点: {content}")
|
|
36
|
+
|
|
37
|
+
tree = self.locator(targeting, element, index)
|
|
38
|
+
|
|
39
|
+
if '/' in str(content):
|
|
40
|
+
# 路径格式,逐级展开
|
|
41
|
+
nodes = [n.strip() for n in str(content).split('/')]
|
|
42
|
+
for i, node_text in enumerate(nodes):
|
|
43
|
+
node = tree.locator(f'.el-tree-node:has(.el-tree-node__label:text("{node_text}"))')
|
|
44
|
+
if node.count() > 0:
|
|
45
|
+
if i < len(nodes) - 1:
|
|
46
|
+
# 非最后一级,展开
|
|
47
|
+
expand_icon = node.first.locator('.el-tree-node__expand-icon')
|
|
48
|
+
if expand_icon.is_visible() and not node.first.locator('.is-expanded').count():
|
|
49
|
+
expand_icon.click()
|
|
50
|
+
self.page.wait_for_timeout(200)
|
|
51
|
+
else:
|
|
52
|
+
# 最后一级,点击
|
|
53
|
+
node.first.locator('.el-tree-node__content').click()
|
|
54
|
+
else:
|
|
55
|
+
# 直接匹配
|
|
56
|
+
node = tree.locator(f'.el-tree-node__label:text("{content}")')
|
|
57
|
+
node.first.click()
|
|
58
|
+
|
|
59
|
+
def el_tree_expand_node(
|
|
60
|
+
self,
|
|
61
|
+
targeting: str,
|
|
62
|
+
element: str,
|
|
63
|
+
index: Optional[int] = None,
|
|
64
|
+
*,
|
|
65
|
+
content: str
|
|
66
|
+
) -> None:
|
|
67
|
+
"""展开树节点
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
targeting: 定位类型
|
|
71
|
+
element: 定位表达式
|
|
72
|
+
index: 索引
|
|
73
|
+
content: 节点文本
|
|
74
|
+
"""
|
|
75
|
+
INFO(f"展开树节点: {content}")
|
|
76
|
+
|
|
77
|
+
tree = self.locator(targeting, element, index)
|
|
78
|
+
node = tree.locator(f'.el-tree-node:has(.el-tree-node__label:text("{content}"))')
|
|
79
|
+
|
|
80
|
+
if node.count() > 0:
|
|
81
|
+
expand_icon = node.first.locator('.el-tree-node__expand-icon')
|
|
82
|
+
if expand_icon.is_visible():
|
|
83
|
+
# 检查是否已展开
|
|
84
|
+
if not node.first.locator('.is-expanded').count():
|
|
85
|
+
expand_icon.click()
|
|
86
|
+
|
|
87
|
+
def el_tree_collapse_node(
|
|
88
|
+
self,
|
|
89
|
+
targeting: str,
|
|
90
|
+
element: str,
|
|
91
|
+
index: Optional[int] = None,
|
|
92
|
+
*,
|
|
93
|
+
content: str
|
|
94
|
+
) -> None:
|
|
95
|
+
"""折叠树节点
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
targeting: 定位类型
|
|
99
|
+
element: 定位表达式
|
|
100
|
+
index: 索引
|
|
101
|
+
content: 节点文本
|
|
102
|
+
"""
|
|
103
|
+
INFO(f"折叠树节点: {content}")
|
|
104
|
+
|
|
105
|
+
tree = self.locator(targeting, element, index)
|
|
106
|
+
node = tree.locator(f'.el-tree-node:has(.el-tree-node__label:text("{content}"))')
|
|
107
|
+
|
|
108
|
+
if node.count() > 0:
|
|
109
|
+
expand_icon = node.first.locator('.el-tree-node__expand-icon')
|
|
110
|
+
if expand_icon.is_visible():
|
|
111
|
+
# 检查是否已展开
|
|
112
|
+
if node.first.locator('.is-expanded').count():
|
|
113
|
+
expand_icon.click()
|
|
114
|
+
|
|
115
|
+
def el_tree_check_node(
|
|
116
|
+
self,
|
|
117
|
+
targeting: str,
|
|
118
|
+
element: str,
|
|
119
|
+
index: Optional[int] = None,
|
|
120
|
+
*,
|
|
121
|
+
content: str,
|
|
122
|
+
checked: bool = True
|
|
123
|
+
) -> None:
|
|
124
|
+
"""勾选树节点
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
targeting: 定位类型
|
|
128
|
+
element: 定位表达式
|
|
129
|
+
index: 索引
|
|
130
|
+
content: 节点文本
|
|
131
|
+
checked: 是否勾选
|
|
132
|
+
"""
|
|
133
|
+
INFO(f"{'勾选' if checked else '取消勾选'}树节点: {content}")
|
|
134
|
+
|
|
135
|
+
tree = self.locator(targeting, element, index)
|
|
136
|
+
node = tree.locator(f'.el-tree-node:has(.el-tree-node__label:text("{content}"))')
|
|
137
|
+
|
|
138
|
+
if node.count() > 0:
|
|
139
|
+
checkbox = node.first.locator('.el-checkbox')
|
|
140
|
+
if checkbox.is_visible():
|
|
141
|
+
is_checked = checkbox.locator('.is-checked').count() > 0
|
|
142
|
+
|
|
143
|
+
if checked and not is_checked:
|
|
144
|
+
checkbox.click()
|
|
145
|
+
elif not checked and is_checked:
|
|
146
|
+
checkbox.click()
|
|
147
|
+
|
|
148
|
+
def el_tree_check_nodes(
|
|
149
|
+
self,
|
|
150
|
+
targeting: str,
|
|
151
|
+
element: str,
|
|
152
|
+
index: Optional[int] = None,
|
|
153
|
+
*,
|
|
154
|
+
content: str
|
|
155
|
+
) -> None:
|
|
156
|
+
"""勾选多个树节点
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
targeting: 定位类型
|
|
160
|
+
element: 定位表达式
|
|
161
|
+
index: 索引
|
|
162
|
+
content: 节点文本列表 (格式: "节点1,节点2,节点3")
|
|
163
|
+
"""
|
|
164
|
+
nodes = [n.strip() for n in str(content).split(',')]
|
|
165
|
+
INFO(f"勾选多个树节点: {nodes}")
|
|
166
|
+
|
|
167
|
+
for node_text in nodes:
|
|
168
|
+
self.el_tree_check_node(targeting, element, index, content=node_text, checked=True)
|
|
169
|
+
|
|
170
|
+
def el_tree_expand_all(
|
|
171
|
+
self,
|
|
172
|
+
targeting: str,
|
|
173
|
+
element: str,
|
|
174
|
+
index: Optional[int] = None
|
|
175
|
+
) -> None:
|
|
176
|
+
"""展开所有节点
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
targeting: 定位类型
|
|
180
|
+
element: 定位表达式
|
|
181
|
+
index: 索引
|
|
182
|
+
"""
|
|
183
|
+
INFO("展开所有树节点")
|
|
184
|
+
|
|
185
|
+
tree = self.locator(targeting, element, index)
|
|
186
|
+
expand_icons = tree.locator('.el-tree-node:not(.is-expanded) .el-tree-node__expand-icon:not(.is-leaf)')
|
|
187
|
+
|
|
188
|
+
# 从上到下依次展开
|
|
189
|
+
while expand_icons.count() > 0:
|
|
190
|
+
expand_icons.first.click()
|
|
191
|
+
self.page.wait_for_timeout(100)
|
|
192
|
+
expand_icons = tree.locator('.el-tree-node:not(.is-expanded) .el-tree-node__expand-icon:not(.is-leaf)')
|
|
193
|
+
|
|
194
|
+
def el_tree_collapse_all(
|
|
195
|
+
self,
|
|
196
|
+
targeting: str,
|
|
197
|
+
element: str,
|
|
198
|
+
index: Optional[int] = None
|
|
199
|
+
) -> None:
|
|
200
|
+
"""折叠所有节点
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
targeting: 定位类型
|
|
204
|
+
element: 定位表达式
|
|
205
|
+
index: 索引
|
|
206
|
+
"""
|
|
207
|
+
INFO("折叠所有树节点")
|
|
208
|
+
|
|
209
|
+
tree = self.locator(targeting, element, index)
|
|
210
|
+
expanded = tree.locator('.el-tree-node.is-expanded .el-tree-node__expand-icon')
|
|
211
|
+
|
|
212
|
+
# 从下到上依次折叠
|
|
213
|
+
while expanded.count() > 0:
|
|
214
|
+
expanded.last.click()
|
|
215
|
+
self.page.wait_for_timeout(100)
|
|
216
|
+
expanded = tree.locator('.el-tree-node.is-expanded .el-tree-node__expand-icon')
|
|
217
|
+
|
|
218
|
+
def el_tree_search(
|
|
219
|
+
self,
|
|
220
|
+
targeting: str,
|
|
221
|
+
element: str,
|
|
222
|
+
index: Optional[int] = None,
|
|
223
|
+
*,
|
|
224
|
+
content: str,
|
|
225
|
+
input_selector: str = '.el-input__inner'
|
|
226
|
+
) -> None:
|
|
227
|
+
"""搜索树节点
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
targeting: 定位类型
|
|
231
|
+
element: 定位表达式
|
|
232
|
+
index: 索引
|
|
233
|
+
content: 搜索关键词
|
|
234
|
+
input_selector: 搜索输入框选择器
|
|
235
|
+
"""
|
|
236
|
+
INFO(f"搜索树: {content}")
|
|
237
|
+
|
|
238
|
+
tree = self.locator(targeting, element, index)
|
|
239
|
+
|
|
240
|
+
# 查找关联的搜索框(可能在树组件外部)
|
|
241
|
+
search_input = tree.locator(input_selector)
|
|
242
|
+
if not search_input.is_visible():
|
|
243
|
+
search_input = self.page.locator(input_selector).first
|
|
244
|
+
|
|
245
|
+
search_input.fill(str(content))
|
|
246
|
+
self.page.wait_for_timeout(300)
|
|
247
|
+
|
|
248
|
+
def el_tree_get_checked_nodes(
|
|
249
|
+
self,
|
|
250
|
+
targeting: str,
|
|
251
|
+
element: str,
|
|
252
|
+
index: Optional[int] = None,
|
|
253
|
+
*,
|
|
254
|
+
content: str = None
|
|
255
|
+
) -> List[str]:
|
|
256
|
+
"""获取已勾选的节点
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
targeting: 定位类型
|
|
260
|
+
element: 定位表达式
|
|
261
|
+
index: 索引
|
|
262
|
+
content: 缓存键名
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List[str]: 已勾选节点的文本列表
|
|
266
|
+
"""
|
|
267
|
+
tree = self.locator(targeting, element, index)
|
|
268
|
+
checked_nodes = tree.locator('.el-tree-node .el-checkbox.is-checked')
|
|
269
|
+
|
|
270
|
+
result = []
|
|
271
|
+
for i in range(checked_nodes.count()):
|
|
272
|
+
node = checked_nodes.nth(i)
|
|
273
|
+
label = node.locator('~ .el-tree-node__label, + .el-tree-node__label')
|
|
274
|
+
if label.count() > 0:
|
|
275
|
+
text = label.first.text_content()
|
|
276
|
+
if text:
|
|
277
|
+
result.append(text.strip())
|
|
278
|
+
|
|
279
|
+
INFO(f"已勾选节点: {result}")
|
|
280
|
+
|
|
281
|
+
if content:
|
|
282
|
+
set_element_value(content, result)
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
|
|
286
|
+
def el_tree_get_node_count(
|
|
287
|
+
self,
|
|
288
|
+
targeting: str,
|
|
289
|
+
element: str,
|
|
290
|
+
index: Optional[int] = None
|
|
291
|
+
) -> int:
|
|
292
|
+
"""获取节点总数
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
targeting: 定位类型
|
|
296
|
+
element: 定位表达式
|
|
297
|
+
index: 索引
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
int: 节点数量
|
|
301
|
+
"""
|
|
302
|
+
tree = self.locator(targeting, element, index)
|
|
303
|
+
count = tree.locator('.el-tree-node').count()
|
|
304
|
+
INFO(f"树节点总数: {count}")
|
|
305
|
+
return count
|
|
306
|
+
|
|
307
|
+
def el_tree_assert_node_exists(
|
|
308
|
+
self,
|
|
309
|
+
targeting: str,
|
|
310
|
+
element: str,
|
|
311
|
+
index: Optional[int] = None,
|
|
312
|
+
*,
|
|
313
|
+
content: str
|
|
314
|
+
) -> bool:
|
|
315
|
+
"""断言节点存在
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
targeting: 定位类型
|
|
319
|
+
element: 定位表达式
|
|
320
|
+
index: 索引
|
|
321
|
+
content: 节点文本
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
bool: 断言结果
|
|
325
|
+
"""
|
|
326
|
+
tree = self.locator(targeting, element, index)
|
|
327
|
+
node = tree.locator(f'.el-tree-node__label:text("{content}")')
|
|
328
|
+
result = node.count() > 0
|
|
329
|
+
|
|
330
|
+
INFO(f"节点存在断言: '{content}' -> {result}")
|
|
331
|
+
return result
|
|
332
|
+
|
|
333
|
+
def el_tree_assert_node_checked(
|
|
334
|
+
self,
|
|
335
|
+
targeting: str,
|
|
336
|
+
element: str,
|
|
337
|
+
index: Optional[int] = None,
|
|
338
|
+
*,
|
|
339
|
+
content: str,
|
|
340
|
+
expected: bool = True
|
|
341
|
+
) -> bool:
|
|
342
|
+
"""断言节点勾选状态
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
targeting: 定位类型
|
|
346
|
+
element: 定位表达式
|
|
347
|
+
index: 索引
|
|
348
|
+
content: 节点文本
|
|
349
|
+
expected: 期望的勾选状态
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
bool: 断言结果
|
|
353
|
+
"""
|
|
354
|
+
tree = self.locator(targeting, element, index)
|
|
355
|
+
node = tree.locator(f'.el-tree-node:has(.el-tree-node__label:text("{content}"))')
|
|
356
|
+
|
|
357
|
+
is_checked = False
|
|
358
|
+
if node.count() > 0:
|
|
359
|
+
checkbox = node.first.locator('.el-checkbox.is-checked')
|
|
360
|
+
is_checked = checkbox.count() > 0
|
|
361
|
+
|
|
362
|
+
result = is_checked == expected
|
|
363
|
+
INFO(f"节点勾选断言: '{content}' checked={is_checked}, expected={expected} -> {result}")
|
|
364
|
+
return result
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Element Plus Upload 组件关键字"""
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api import Page, Locator, FileChooser
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..base_keyword import BaseKeyword
|
|
8
|
+
from ...reference import INFO
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ElUploadKeyword(BaseKeyword):
|
|
12
|
+
"""Element Plus Upload 上传组件关键字
|
|
13
|
+
|
|
14
|
+
处理文件上传、拖拽上传、图片预览等操作。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, page: Page):
|
|
18
|
+
super().__init__(page)
|
|
19
|
+
|
|
20
|
+
def el_upload(
|
|
21
|
+
self,
|
|
22
|
+
targeting: str,
|
|
23
|
+
element: str,
|
|
24
|
+
index: Optional[int] = None,
|
|
25
|
+
*,
|
|
26
|
+
content: str
|
|
27
|
+
) -> None:
|
|
28
|
+
"""文件上传
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
targeting: 定位类型
|
|
32
|
+
element: 定位表达式
|
|
33
|
+
index: 索引
|
|
34
|
+
content: 文件路径(多个文件用逗号分隔)
|
|
35
|
+
"""
|
|
36
|
+
files = [f.strip() for f in str(content).split(',')]
|
|
37
|
+
INFO(f"上传文件: {files}")
|
|
38
|
+
|
|
39
|
+
upload = self.locator(targeting, element, index)
|
|
40
|
+
|
|
41
|
+
# 找到隐藏的 input[type=file]
|
|
42
|
+
file_input = upload.locator('input[type="file"]')
|
|
43
|
+
|
|
44
|
+
if file_input.count() > 0:
|
|
45
|
+
# 直接设置文件
|
|
46
|
+
if len(files) == 1:
|
|
47
|
+
file_input.set_input_files(files[0])
|
|
48
|
+
else:
|
|
49
|
+
file_input.set_input_files(files)
|
|
50
|
+
else:
|
|
51
|
+
# 通过 file chooser 处理
|
|
52
|
+
with self.page.expect_file_chooser() as fc_info:
|
|
53
|
+
upload.click()
|
|
54
|
+
file_chooser = fc_info.value
|
|
55
|
+
if len(files) == 1:
|
|
56
|
+
file_chooser.set_files(files[0])
|
|
57
|
+
else:
|
|
58
|
+
file_chooser.set_files(files)
|
|
59
|
+
|
|
60
|
+
def el_upload_drag(
|
|
61
|
+
self,
|
|
62
|
+
targeting: str,
|
|
63
|
+
element: str,
|
|
64
|
+
index: Optional[int] = None,
|
|
65
|
+
*,
|
|
66
|
+
content: str
|
|
67
|
+
) -> None:
|
|
68
|
+
"""拖拽上传
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
targeting: 定位类型
|
|
72
|
+
element: 定位表达式
|
|
73
|
+
index: 索引
|
|
74
|
+
content: 文件路径
|
|
75
|
+
"""
|
|
76
|
+
INFO(f"拖拽上传: {content}")
|
|
77
|
+
|
|
78
|
+
upload = self.locator(targeting, element, index)
|
|
79
|
+
drag_area = upload.locator('.el-upload-dragger, .el-upload--drag')
|
|
80
|
+
|
|
81
|
+
# 使用 Playwright 的文件拖拽 API
|
|
82
|
+
file_input = upload.locator('input[type="file"]')
|
|
83
|
+
if file_input.count() > 0:
|
|
84
|
+
file_input.set_input_files(str(content))
|
|
85
|
+
else:
|
|
86
|
+
with self.page.expect_file_chooser() as fc_info:
|
|
87
|
+
drag_area.click()
|
|
88
|
+
fc_info.value.set_files(str(content))
|
|
89
|
+
|
|
90
|
+
def el_upload_remove(
|
|
91
|
+
self,
|
|
92
|
+
targeting: str,
|
|
93
|
+
element: str,
|
|
94
|
+
index: Optional[int] = None,
|
|
95
|
+
*,
|
|
96
|
+
content: str = '0'
|
|
97
|
+
) -> None:
|
|
98
|
+
"""删除已上传文件
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
targeting: 定位类型
|
|
102
|
+
element: 定位表达式
|
|
103
|
+
index: 索引
|
|
104
|
+
content: 文件索引或文件名
|
|
105
|
+
"""
|
|
106
|
+
INFO(f"删除上传文件: {content}")
|
|
107
|
+
|
|
108
|
+
upload = self.locator(targeting, element, index)
|
|
109
|
+
|
|
110
|
+
if content.isdigit():
|
|
111
|
+
# 按索引删除
|
|
112
|
+
file_index = int(content)
|
|
113
|
+
file_items = upload.locator('.el-upload-list__item')
|
|
114
|
+
if file_items.count() > file_index:
|
|
115
|
+
item = file_items.nth(file_index)
|
|
116
|
+
item.hover()
|
|
117
|
+
delete_btn = item.locator('.el-upload-list__item-delete, .el-icon-delete')
|
|
118
|
+
if delete_btn.is_visible():
|
|
119
|
+
delete_btn.click()
|
|
120
|
+
else:
|
|
121
|
+
# 尝试点击关闭按钮
|
|
122
|
+
close_btn = item.locator('.el-icon-close')
|
|
123
|
+
if close_btn.is_visible():
|
|
124
|
+
close_btn.click()
|
|
125
|
+
else:
|
|
126
|
+
# 按文件名删除
|
|
127
|
+
file_item = upload.locator(f'.el-upload-list__item:has-text("{content}")')
|
|
128
|
+
if file_item.count() > 0:
|
|
129
|
+
file_item.first.hover()
|
|
130
|
+
delete_btn = file_item.first.locator('.el-upload-list__item-delete, .el-icon-delete, .el-icon-close')
|
|
131
|
+
delete_btn.click()
|
|
132
|
+
|
|
133
|
+
def el_upload_clear(
|
|
134
|
+
self,
|
|
135
|
+
targeting: str,
|
|
136
|
+
element: str,
|
|
137
|
+
index: Optional[int] = None
|
|
138
|
+
) -> None:
|
|
139
|
+
"""清除所有上传文件
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
targeting: 定位类型
|
|
143
|
+
element: 定位表达式
|
|
144
|
+
index: 索引
|
|
145
|
+
"""
|
|
146
|
+
INFO("清除所有上传文件")
|
|
147
|
+
|
|
148
|
+
upload = self.locator(targeting, element, index)
|
|
149
|
+
file_items = upload.locator('.el-upload-list__item')
|
|
150
|
+
|
|
151
|
+
# 从后往前删除
|
|
152
|
+
while file_items.count() > 0:
|
|
153
|
+
item = file_items.last
|
|
154
|
+
item.hover()
|
|
155
|
+
self.page.wait_for_timeout(100)
|
|
156
|
+
delete_btn = item.locator('.el-upload-list__item-delete, .el-icon-delete, .el-icon-close')
|
|
157
|
+
if delete_btn.is_visible():
|
|
158
|
+
delete_btn.click()
|
|
159
|
+
self.page.wait_for_timeout(200)
|
|
160
|
+
file_items = upload.locator('.el-upload-list__item')
|
|
161
|
+
|
|
162
|
+
def el_upload_preview(
|
|
163
|
+
self,
|
|
164
|
+
targeting: str,
|
|
165
|
+
element: str,
|
|
166
|
+
index: Optional[int] = None,
|
|
167
|
+
*,
|
|
168
|
+
content: str = '0'
|
|
169
|
+
) -> None:
|
|
170
|
+
"""预览上传文件
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
targeting: 定位类型
|
|
174
|
+
element: 定位表达式
|
|
175
|
+
index: 索引
|
|
176
|
+
content: 文件索引
|
|
177
|
+
"""
|
|
178
|
+
file_index = int(content)
|
|
179
|
+
INFO(f"预览文件: {file_index}")
|
|
180
|
+
|
|
181
|
+
upload = self.locator(targeting, element, index)
|
|
182
|
+
file_items = upload.locator('.el-upload-list__item')
|
|
183
|
+
|
|
184
|
+
if file_items.count() > file_index:
|
|
185
|
+
item = file_items.nth(file_index)
|
|
186
|
+
item.hover()
|
|
187
|
+
preview_btn = item.locator('.el-upload-list__item-preview, .el-icon-zoom-in')
|
|
188
|
+
if preview_btn.is_visible():
|
|
189
|
+
preview_btn.click()
|
|
190
|
+
else:
|
|
191
|
+
# 直接点击图片
|
|
192
|
+
item.locator('img').click()
|
|
193
|
+
|
|
194
|
+
def el_upload_get_file_count(
|
|
195
|
+
self,
|
|
196
|
+
targeting: str,
|
|
197
|
+
element: str,
|
|
198
|
+
index: Optional[int] = None
|
|
199
|
+
) -> int:
|
|
200
|
+
"""获取已上传文件数量
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
targeting: 定位类型
|
|
204
|
+
element: 定位表达式
|
|
205
|
+
index: 索引
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
int: 文件数量
|
|
209
|
+
"""
|
|
210
|
+
upload = self.locator(targeting, element, index)
|
|
211
|
+
count = upload.locator('.el-upload-list__item').count()
|
|
212
|
+
INFO(f"已上传文件数: {count}")
|
|
213
|
+
return count
|
|
214
|
+
|
|
215
|
+
def el_upload_get_file_names(
|
|
216
|
+
self,
|
|
217
|
+
targeting: str,
|
|
218
|
+
element: str,
|
|
219
|
+
index: Optional[int] = None
|
|
220
|
+
) -> List[str]:
|
|
221
|
+
"""获取已上传文件名列表
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
targeting: 定位类型
|
|
225
|
+
element: 定位表达式
|
|
226
|
+
index: 索引
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List[str]: 文件名列表
|
|
230
|
+
"""
|
|
231
|
+
upload = self.locator(targeting, element, index)
|
|
232
|
+
file_items = upload.locator('.el-upload-list__item')
|
|
233
|
+
|
|
234
|
+
names = []
|
|
235
|
+
for i in range(file_items.count()):
|
|
236
|
+
item = file_items.nth(i)
|
|
237
|
+
name = item.locator('.el-upload-list__item-name').text_content()
|
|
238
|
+
if name:
|
|
239
|
+
names.append(name.strip())
|
|
240
|
+
|
|
241
|
+
INFO(f"已上传文件: {names}")
|
|
242
|
+
return names
|
|
243
|
+
|
|
244
|
+
def el_upload_assert_count(
|
|
245
|
+
self,
|
|
246
|
+
targeting: str,
|
|
247
|
+
element: str,
|
|
248
|
+
index: Optional[int] = None,
|
|
249
|
+
*,
|
|
250
|
+
content: str,
|
|
251
|
+
mode: str = 'equals'
|
|
252
|
+
) -> bool:
|
|
253
|
+
"""断言上传文件数量
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
targeting: 定位类型
|
|
257
|
+
element: 定位表达式
|
|
258
|
+
index: 索引
|
|
259
|
+
content: 期望数量
|
|
260
|
+
mode: 断言模式
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
bool: 断言结果
|
|
264
|
+
"""
|
|
265
|
+
actual = self.el_upload_get_file_count(targeting, element, index)
|
|
266
|
+
expected = int(content)
|
|
267
|
+
|
|
268
|
+
mode_map = {
|
|
269
|
+
'equals': actual == expected,
|
|
270
|
+
'gt': actual > expected,
|
|
271
|
+
'lt': actual < expected,
|
|
272
|
+
'gte': actual >= expected,
|
|
273
|
+
'lte': actual <= expected,
|
|
274
|
+
}
|
|
275
|
+
result = mode_map.get(mode, actual == expected)
|
|
276
|
+
|
|
277
|
+
INFO(f"上传数量断言: {actual} {mode} {expected} -> {result}")
|
|
278
|
+
return result
|
|
279
|
+
|
|
280
|
+
def el_upload_wait_success(
|
|
281
|
+
self,
|
|
282
|
+
targeting: str,
|
|
283
|
+
element: str,
|
|
284
|
+
index: Optional[int] = None,
|
|
285
|
+
*,
|
|
286
|
+
content: str = '30000'
|
|
287
|
+
) -> None:
|
|
288
|
+
"""等待上传成功
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
targeting: 定位类型
|
|
292
|
+
element: 定位表达式
|
|
293
|
+
index: 索引
|
|
294
|
+
content: 超时时间 (毫秒)
|
|
295
|
+
"""
|
|
296
|
+
timeout = int(content)
|
|
297
|
+
INFO("等待上传完成")
|
|
298
|
+
|
|
299
|
+
upload = self.locator(targeting, element, index)
|
|
300
|
+
|
|
301
|
+
# 等待进度消失
|
|
302
|
+
progress = upload.locator('.el-upload-list__item-status-label .el-icon-loading, .el-progress')
|
|
303
|
+
try:
|
|
304
|
+
progress.wait_for(state='hidden', timeout=timeout)
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
# 等待成功状态
|
|
309
|
+
success = upload.locator('.el-upload-list__item.is-success, .el-icon-check')
|
|
310
|
+
try:
|
|
311
|
+
success.wait_for(state='visible', timeout=5000)
|
|
312
|
+
except Exception:
|
|
313
|
+
pass
|