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,268 @@
|
|
|
1
|
+
"""Element Plus Select 组件关键字"""
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api import Page, Locator
|
|
4
|
+
from typing import Optional, List, Union
|
|
5
|
+
|
|
6
|
+
from ..base_keyword import BaseKeyword
|
|
7
|
+
from ...reference import INFO
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ElSelectKeyword(BaseKeyword):
|
|
11
|
+
"""Element Plus Select 组件关键字
|
|
12
|
+
|
|
13
|
+
处理 el-select 下拉选择器,包括单选、多选、可搜索等模式。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, page: Page):
|
|
17
|
+
super().__init__(page)
|
|
18
|
+
|
|
19
|
+
def el_select(
|
|
20
|
+
self,
|
|
21
|
+
targeting: str,
|
|
22
|
+
element: str,
|
|
23
|
+
index: Optional[int] = None,
|
|
24
|
+
*,
|
|
25
|
+
content: str,
|
|
26
|
+
by: str = 'text'
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Element Plus 下拉选择
|
|
29
|
+
|
|
30
|
+
处理 teleport 到 body 的下拉面板。
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
targeting: 定位类型
|
|
34
|
+
element: 定位表达式
|
|
35
|
+
index: 索引
|
|
36
|
+
content: 选项值
|
|
37
|
+
by: 选择方式 (text, value, index)
|
|
38
|
+
"""
|
|
39
|
+
INFO(f"Element Plus 选择: {content}")
|
|
40
|
+
|
|
41
|
+
# 点击触发下拉
|
|
42
|
+
select = self.locator(targeting, element, index)
|
|
43
|
+
select.click()
|
|
44
|
+
|
|
45
|
+
# 等待下拉面板出现(teleport 到 body)
|
|
46
|
+
dropdown = self.page.locator('.el-select-dropdown:visible, .el-popper:visible')
|
|
47
|
+
dropdown.wait_for(state='visible', timeout=5000)
|
|
48
|
+
|
|
49
|
+
# 等待动画完成
|
|
50
|
+
self.page.wait_for_timeout(100)
|
|
51
|
+
|
|
52
|
+
# 选择选项
|
|
53
|
+
if by == 'text':
|
|
54
|
+
option_loc = dropdown.locator(f'.el-select-dropdown__item:has-text("{content}")')
|
|
55
|
+
elif by == 'value':
|
|
56
|
+
option_loc = dropdown.locator(f'.el-select-dropdown__item[data-value="{content}"]')
|
|
57
|
+
elif by == 'index':
|
|
58
|
+
option_loc = dropdown.locator('.el-select-dropdown__item').nth(int(content))
|
|
59
|
+
else:
|
|
60
|
+
option_loc = dropdown.locator(f'.el-select-dropdown__item:has-text("{content}")')
|
|
61
|
+
|
|
62
|
+
# 确保选项可见
|
|
63
|
+
if option_loc.count() > 0:
|
|
64
|
+
option_loc.first.scroll_into_view_if_needed()
|
|
65
|
+
option_loc.first.click()
|
|
66
|
+
else:
|
|
67
|
+
INFO(f"未找到选项: {content}", "WARNING")
|
|
68
|
+
|
|
69
|
+
# 等待下拉面板关闭
|
|
70
|
+
try:
|
|
71
|
+
dropdown.wait_for(state='hidden', timeout=3000)
|
|
72
|
+
except Exception:
|
|
73
|
+
# 如果面板没关闭,按 Escape 关闭
|
|
74
|
+
self.page.keyboard.press('Escape')
|
|
75
|
+
|
|
76
|
+
def el_select_multiple(
|
|
77
|
+
self,
|
|
78
|
+
targeting: str,
|
|
79
|
+
element: str,
|
|
80
|
+
index: Optional[int] = None,
|
|
81
|
+
*,
|
|
82
|
+
content: str
|
|
83
|
+
) -> None:
|
|
84
|
+
"""多选下拉
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
targeting: 定位类型
|
|
88
|
+
element: 定位表达式
|
|
89
|
+
index: 索引
|
|
90
|
+
content: 选项值(多个用逗号分隔)
|
|
91
|
+
"""
|
|
92
|
+
options = [opt.strip() for opt in str(content).split(',')]
|
|
93
|
+
INFO(f"Element Plus 多选: {options}")
|
|
94
|
+
|
|
95
|
+
# 点击打开下拉
|
|
96
|
+
select = self.locator(targeting, element, index)
|
|
97
|
+
select.click()
|
|
98
|
+
|
|
99
|
+
dropdown = self.page.locator('.el-select-dropdown:visible, .el-popper:visible')
|
|
100
|
+
dropdown.wait_for(state='visible', timeout=5000)
|
|
101
|
+
self.page.wait_for_timeout(100)
|
|
102
|
+
|
|
103
|
+
# 逐个选择
|
|
104
|
+
for opt in options:
|
|
105
|
+
option_loc = dropdown.locator(f'.el-select-dropdown__item:has-text("{opt}")')
|
|
106
|
+
if option_loc.count() > 0:
|
|
107
|
+
option_loc.first.click()
|
|
108
|
+
self.page.wait_for_timeout(100)
|
|
109
|
+
|
|
110
|
+
# 点击外部关闭
|
|
111
|
+
self.page.keyboard.press('Escape')
|
|
112
|
+
|
|
113
|
+
def el_select_clear(
|
|
114
|
+
self,
|
|
115
|
+
targeting: str,
|
|
116
|
+
element: str,
|
|
117
|
+
index: Optional[int] = None
|
|
118
|
+
) -> None:
|
|
119
|
+
"""清除选择
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
targeting: 定位类型
|
|
123
|
+
element: 定位表达式
|
|
124
|
+
index: 索引
|
|
125
|
+
"""
|
|
126
|
+
INFO("清除 Select 选择")
|
|
127
|
+
select = self.locator(targeting, element, index)
|
|
128
|
+
|
|
129
|
+
# 悬停显示清除按钮
|
|
130
|
+
select.hover()
|
|
131
|
+
self.page.wait_for_timeout(200)
|
|
132
|
+
|
|
133
|
+
# 点击清除按钮
|
|
134
|
+
clear_btn = select.locator('.el-select__clear, .el-icon-circle-close')
|
|
135
|
+
if clear_btn.is_visible():
|
|
136
|
+
clear_btn.click()
|
|
137
|
+
|
|
138
|
+
def el_select_search(
|
|
139
|
+
self,
|
|
140
|
+
targeting: str,
|
|
141
|
+
element: str,
|
|
142
|
+
index: Optional[int] = None,
|
|
143
|
+
*,
|
|
144
|
+
content: str,
|
|
145
|
+
select_first: bool = True
|
|
146
|
+
) -> None:
|
|
147
|
+
"""可搜索下拉框
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
targeting: 定位类型
|
|
151
|
+
element: 定位表达式
|
|
152
|
+
index: 索引
|
|
153
|
+
content: 搜索关键词
|
|
154
|
+
select_first: 是否选择第一个结果
|
|
155
|
+
"""
|
|
156
|
+
INFO(f"搜索并选择: {content}")
|
|
157
|
+
select = self.locator(targeting, element, index)
|
|
158
|
+
|
|
159
|
+
# 点击打开
|
|
160
|
+
select.click()
|
|
161
|
+
self.page.wait_for_timeout(200)
|
|
162
|
+
|
|
163
|
+
# 找到输入框并输入
|
|
164
|
+
input_el = select.locator('.el-select__input, .el-input__inner')
|
|
165
|
+
if input_el.count() > 0:
|
|
166
|
+
input_el.first.fill(str(content))
|
|
167
|
+
self.page.wait_for_timeout(300) # 等待搜索
|
|
168
|
+
|
|
169
|
+
# 选择结果
|
|
170
|
+
if select_first:
|
|
171
|
+
dropdown = self.page.locator('.el-select-dropdown:visible, .el-popper:visible')
|
|
172
|
+
if dropdown.is_visible():
|
|
173
|
+
first_option = dropdown.locator('.el-select-dropdown__item:not(.is-disabled)').first
|
|
174
|
+
if first_option.is_visible():
|
|
175
|
+
first_option.click()
|
|
176
|
+
|
|
177
|
+
def el_select_get_value(
|
|
178
|
+
self,
|
|
179
|
+
targeting: str,
|
|
180
|
+
element: str,
|
|
181
|
+
index: Optional[int] = None
|
|
182
|
+
) -> str:
|
|
183
|
+
"""获取当前选中值
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
targeting: 定位类型
|
|
187
|
+
element: 定位表达式
|
|
188
|
+
index: 索引
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
str: 选中的值
|
|
192
|
+
"""
|
|
193
|
+
select = self.locator(targeting, element, index)
|
|
194
|
+
|
|
195
|
+
# 尝试获取选中的标签
|
|
196
|
+
tags = select.locator('.el-select__tags .el-tag')
|
|
197
|
+
if tags.count() > 0:
|
|
198
|
+
# 多选模式
|
|
199
|
+
values = [tag.text_content().strip() for tag in tags.all()]
|
|
200
|
+
return ','.join(values)
|
|
201
|
+
|
|
202
|
+
# 单选模式
|
|
203
|
+
input_el = select.locator('.el-input__inner, .el-select__input')
|
|
204
|
+
if input_el.count() > 0:
|
|
205
|
+
value = input_el.first.get_attribute('value') or input_el.first.text_content()
|
|
206
|
+
return (value or '').strip()
|
|
207
|
+
|
|
208
|
+
return ''
|
|
209
|
+
|
|
210
|
+
def el_select_assert(
|
|
211
|
+
self,
|
|
212
|
+
targeting: str,
|
|
213
|
+
element: str,
|
|
214
|
+
index: Optional[int] = None,
|
|
215
|
+
*,
|
|
216
|
+
content: str,
|
|
217
|
+
mode: str = 'equals'
|
|
218
|
+
) -> bool:
|
|
219
|
+
"""断言选中值
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
targeting: 定位类型
|
|
223
|
+
element: 定位表达式
|
|
224
|
+
index: 索引
|
|
225
|
+
content: 期望值
|
|
226
|
+
mode: 断言模式
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
bool: 断言结果
|
|
230
|
+
"""
|
|
231
|
+
actual = self.el_select_get_value(targeting, element, index)
|
|
232
|
+
|
|
233
|
+
if mode == 'equals':
|
|
234
|
+
result = actual == content
|
|
235
|
+
elif mode == 'contains':
|
|
236
|
+
result = content in actual
|
|
237
|
+
else:
|
|
238
|
+
result = actual == content
|
|
239
|
+
|
|
240
|
+
INFO(f"Select 断言: '{actual}' {mode} '{content}' -> {result}")
|
|
241
|
+
return result
|
|
242
|
+
|
|
243
|
+
def el_select_option_count(
|
|
244
|
+
self,
|
|
245
|
+
targeting: str,
|
|
246
|
+
element: str,
|
|
247
|
+
index: Optional[int] = None
|
|
248
|
+
) -> int:
|
|
249
|
+
"""获取选项数量
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
targeting: 定位类型
|
|
253
|
+
element: 定位表达式
|
|
254
|
+
index: 索引
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
int: 选项数量
|
|
258
|
+
"""
|
|
259
|
+
select = self.locator(targeting, element, index)
|
|
260
|
+
select.click()
|
|
261
|
+
|
|
262
|
+
dropdown = self.page.locator('.el-select-dropdown:visible')
|
|
263
|
+
dropdown.wait_for(state='visible', timeout=3000)
|
|
264
|
+
|
|
265
|
+
count = dropdown.locator('.el-select-dropdown__item').count()
|
|
266
|
+
|
|
267
|
+
self.page.keyboard.press('Escape')
|
|
268
|
+
return count
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Element Plus Table 组件关键字"""
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api import Page, Locator
|
|
4
|
+
from typing import Optional, List, Dict, Any
|
|
5
|
+
|
|
6
|
+
from ..base_keyword import BaseKeyword
|
|
7
|
+
from ...reference import INFO, set_element_value
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ElTableKeyword(BaseKeyword):
|
|
11
|
+
"""Element Plus Table 组件关键字
|
|
12
|
+
|
|
13
|
+
处理表格的行列操作、数据获取、排序、筛选等。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, page: Page):
|
|
17
|
+
super().__init__(page)
|
|
18
|
+
|
|
19
|
+
def el_table_click_row(
|
|
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: 行号(从0开始)
|
|
34
|
+
"""
|
|
35
|
+
row_index = int(content)
|
|
36
|
+
INFO(f"点击表格第 {row_index} 行")
|
|
37
|
+
|
|
38
|
+
table = self.locator(targeting, element, index)
|
|
39
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
40
|
+
rows.nth(row_index).click()
|
|
41
|
+
|
|
42
|
+
def el_table_click_cell(
|
|
43
|
+
self,
|
|
44
|
+
targeting: str,
|
|
45
|
+
element: str,
|
|
46
|
+
index: Optional[int] = None,
|
|
47
|
+
*,
|
|
48
|
+
content: str
|
|
49
|
+
) -> None:
|
|
50
|
+
"""点击表格单元格
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
targeting: 定位类型
|
|
54
|
+
element: 定位表达式
|
|
55
|
+
index: 表格索引
|
|
56
|
+
content: 行列号 (格式: "row,col" 从0开始)
|
|
57
|
+
"""
|
|
58
|
+
parts = str(content).split(',')
|
|
59
|
+
row_index = int(parts[0])
|
|
60
|
+
col_index = int(parts[1]) if len(parts) > 1 else 0
|
|
61
|
+
|
|
62
|
+
INFO(f"点击表格单元格: ({row_index}, {col_index})")
|
|
63
|
+
|
|
64
|
+
table = self.locator(targeting, element, index)
|
|
65
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
66
|
+
cells = rows.nth(row_index).locator('td')
|
|
67
|
+
cells.nth(col_index).click()
|
|
68
|
+
|
|
69
|
+
def el_table_get_cell_text(
|
|
70
|
+
self,
|
|
71
|
+
targeting: str,
|
|
72
|
+
element: str,
|
|
73
|
+
index: Optional[int] = None,
|
|
74
|
+
*,
|
|
75
|
+
content: str,
|
|
76
|
+
key: str = None
|
|
77
|
+
) -> str:
|
|
78
|
+
"""获取单元格文本
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
targeting: 定位类型
|
|
82
|
+
element: 定位表达式
|
|
83
|
+
index: 表格索引
|
|
84
|
+
content: 行列号 (格式: "row,col")
|
|
85
|
+
key: 缓存键名
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
str: 单元格文本
|
|
89
|
+
"""
|
|
90
|
+
parts = str(content).split(',')
|
|
91
|
+
row_index = int(parts[0])
|
|
92
|
+
col_index = int(parts[1]) if len(parts) > 1 else 0
|
|
93
|
+
|
|
94
|
+
table = self.locator(targeting, element, index)
|
|
95
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
96
|
+
cells = rows.nth(row_index).locator('td')
|
|
97
|
+
text = cells.nth(col_index).text_content() or ''
|
|
98
|
+
text = text.strip()
|
|
99
|
+
|
|
100
|
+
INFO(f"单元格 ({row_index}, {col_index}) 文本: {text}")
|
|
101
|
+
|
|
102
|
+
if key:
|
|
103
|
+
set_element_value(key, text)
|
|
104
|
+
|
|
105
|
+
return text
|
|
106
|
+
|
|
107
|
+
def el_table_get_row_count(
|
|
108
|
+
self,
|
|
109
|
+
targeting: str,
|
|
110
|
+
element: str,
|
|
111
|
+
index: Optional[int] = None,
|
|
112
|
+
*,
|
|
113
|
+
content: str = None
|
|
114
|
+
) -> int:
|
|
115
|
+
"""获取表格行数
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
targeting: 定位类型
|
|
119
|
+
element: 定位表达式
|
|
120
|
+
index: 表格索引
|
|
121
|
+
content: 缓存键名
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
int: 行数
|
|
125
|
+
"""
|
|
126
|
+
table = self.locator(targeting, element, index)
|
|
127
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
128
|
+
count = rows.count()
|
|
129
|
+
|
|
130
|
+
INFO(f"表格行数: {count}")
|
|
131
|
+
|
|
132
|
+
if content:
|
|
133
|
+
set_element_value(content, count)
|
|
134
|
+
|
|
135
|
+
return count
|
|
136
|
+
|
|
137
|
+
def el_table_get_column_data(
|
|
138
|
+
self,
|
|
139
|
+
targeting: str,
|
|
140
|
+
element: str,
|
|
141
|
+
index: Optional[int] = None,
|
|
142
|
+
*,
|
|
143
|
+
content: str,
|
|
144
|
+
key: str = None
|
|
145
|
+
) -> List[str]:
|
|
146
|
+
"""获取某一列的所有数据
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
targeting: 定位类型
|
|
150
|
+
element: 定位表达式
|
|
151
|
+
index: 表格索引
|
|
152
|
+
content: 列号(从0开始)
|
|
153
|
+
key: 缓存键名
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List[str]: 列数据列表
|
|
157
|
+
"""
|
|
158
|
+
col_index = int(content)
|
|
159
|
+
|
|
160
|
+
table = self.locator(targeting, element, index)
|
|
161
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
162
|
+
|
|
163
|
+
data = []
|
|
164
|
+
for i in range(rows.count()):
|
|
165
|
+
cells = rows.nth(i).locator('td')
|
|
166
|
+
if cells.count() > col_index:
|
|
167
|
+
text = cells.nth(col_index).text_content() or ''
|
|
168
|
+
data.append(text.strip())
|
|
169
|
+
|
|
170
|
+
INFO(f"列 {col_index} 数据: {data}")
|
|
171
|
+
|
|
172
|
+
if key:
|
|
173
|
+
set_element_value(key, data)
|
|
174
|
+
|
|
175
|
+
return data
|
|
176
|
+
|
|
177
|
+
def el_table_select_row(
|
|
178
|
+
self,
|
|
179
|
+
targeting: str,
|
|
180
|
+
element: str,
|
|
181
|
+
index: Optional[int] = None,
|
|
182
|
+
*,
|
|
183
|
+
content: str
|
|
184
|
+
) -> None:
|
|
185
|
+
"""选择表格行(复选框)
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
targeting: 定位类型
|
|
189
|
+
element: 定位表达式
|
|
190
|
+
index: 表格索引
|
|
191
|
+
content: 行号或 "all"
|
|
192
|
+
"""
|
|
193
|
+
table = self.locator(targeting, element, index)
|
|
194
|
+
|
|
195
|
+
if str(content).lower() == 'all':
|
|
196
|
+
# 全选
|
|
197
|
+
INFO("全选表格")
|
|
198
|
+
header_checkbox = table.locator('.el-table__header-wrapper .el-checkbox')
|
|
199
|
+
header_checkbox.click()
|
|
200
|
+
else:
|
|
201
|
+
row_index = int(content)
|
|
202
|
+
INFO(f"选择表格第 {row_index} 行")
|
|
203
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
204
|
+
checkbox = rows.nth(row_index).locator('.el-checkbox')
|
|
205
|
+
checkbox.click()
|
|
206
|
+
|
|
207
|
+
def el_table_select_rows(
|
|
208
|
+
self,
|
|
209
|
+
targeting: str,
|
|
210
|
+
element: str,
|
|
211
|
+
index: Optional[int] = None,
|
|
212
|
+
*,
|
|
213
|
+
content: str
|
|
214
|
+
) -> None:
|
|
215
|
+
"""选择多行
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
targeting: 定位类型
|
|
219
|
+
element: 定位表达式
|
|
220
|
+
index: 表格索引
|
|
221
|
+
content: 行号列表 (格式: "0,1,2" 或 "0-5")
|
|
222
|
+
"""
|
|
223
|
+
table = self.locator(targeting, element, index)
|
|
224
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
225
|
+
|
|
226
|
+
# 解析行号
|
|
227
|
+
row_indices = []
|
|
228
|
+
if '-' in str(content):
|
|
229
|
+
parts = str(content).split('-')
|
|
230
|
+
row_indices = list(range(int(parts[0]), int(parts[1]) + 1))
|
|
231
|
+
else:
|
|
232
|
+
row_indices = [int(i.strip()) for i in str(content).split(',')]
|
|
233
|
+
|
|
234
|
+
INFO(f"选择表格行: {row_indices}")
|
|
235
|
+
|
|
236
|
+
for row_idx in row_indices:
|
|
237
|
+
checkbox = rows.nth(row_idx).locator('.el-checkbox')
|
|
238
|
+
checkbox.click()
|
|
239
|
+
|
|
240
|
+
def el_table_sort(
|
|
241
|
+
self,
|
|
242
|
+
targeting: str,
|
|
243
|
+
element: str,
|
|
244
|
+
index: Optional[int] = None,
|
|
245
|
+
*,
|
|
246
|
+
content: str
|
|
247
|
+
) -> None:
|
|
248
|
+
"""点击列头排序
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
targeting: 定位类型
|
|
252
|
+
element: 定位表达式
|
|
253
|
+
index: 表格索引
|
|
254
|
+
content: 列号或列名
|
|
255
|
+
"""
|
|
256
|
+
INFO(f"表格排序: {content}")
|
|
257
|
+
|
|
258
|
+
table = self.locator(targeting, element, index)
|
|
259
|
+
header_cells = table.locator('.el-table__header-wrapper th')
|
|
260
|
+
|
|
261
|
+
if content.isdigit():
|
|
262
|
+
# 列号
|
|
263
|
+
col_index = int(content)
|
|
264
|
+
header_cells.nth(col_index).click()
|
|
265
|
+
else:
|
|
266
|
+
# 列名
|
|
267
|
+
header_cell = table.locator(f'.el-table__header-wrapper th:has-text("{content}")')
|
|
268
|
+
header_cell.click()
|
|
269
|
+
|
|
270
|
+
def el_table_filter(
|
|
271
|
+
self,
|
|
272
|
+
targeting: str,
|
|
273
|
+
element: str,
|
|
274
|
+
index: Optional[int] = None,
|
|
275
|
+
*,
|
|
276
|
+
content: str,
|
|
277
|
+
column: str
|
|
278
|
+
) -> None:
|
|
279
|
+
"""表格筛选
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
targeting: 定位类型
|
|
283
|
+
element: 定位表达式
|
|
284
|
+
index: 表格索引
|
|
285
|
+
content: 筛选值
|
|
286
|
+
column: 列号或列名
|
|
287
|
+
"""
|
|
288
|
+
INFO(f"表格筛选列 {column}: {content}")
|
|
289
|
+
|
|
290
|
+
table = self.locator(targeting, element, index)
|
|
291
|
+
|
|
292
|
+
# 找到筛选按钮
|
|
293
|
+
if column.isdigit():
|
|
294
|
+
header_cell = table.locator('.el-table__header-wrapper th').nth(int(column))
|
|
295
|
+
else:
|
|
296
|
+
header_cell = table.locator(f'.el-table__header-wrapper th:has-text("{column}")')
|
|
297
|
+
|
|
298
|
+
filter_btn = header_cell.locator('.el-table__column-filter-trigger')
|
|
299
|
+
filter_btn.click()
|
|
300
|
+
|
|
301
|
+
# 等待筛选面板
|
|
302
|
+
self.page.wait_for_timeout(200)
|
|
303
|
+
|
|
304
|
+
# 选择筛选值
|
|
305
|
+
filter_panel = self.page.locator('.el-table-filter:visible')
|
|
306
|
+
checkbox = filter_panel.locator(f'.el-checkbox:has-text("{content}")')
|
|
307
|
+
checkbox.click()
|
|
308
|
+
|
|
309
|
+
# 确认筛选
|
|
310
|
+
confirm_btn = filter_panel.locator('.el-table-filter__bottom button:has-text("筛选")')
|
|
311
|
+
confirm_btn.click()
|
|
312
|
+
|
|
313
|
+
def el_table_expand_row(
|
|
314
|
+
self,
|
|
315
|
+
targeting: str,
|
|
316
|
+
element: str,
|
|
317
|
+
index: Optional[int] = None,
|
|
318
|
+
*,
|
|
319
|
+
content: str
|
|
320
|
+
) -> None:
|
|
321
|
+
"""展开表格行
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
targeting: 定位类型
|
|
325
|
+
element: 定位表达式
|
|
326
|
+
index: 表格索引
|
|
327
|
+
content: 行号
|
|
328
|
+
"""
|
|
329
|
+
row_index = int(content)
|
|
330
|
+
INFO(f"展开表格第 {row_index} 行")
|
|
331
|
+
|
|
332
|
+
table = self.locator(targeting, element, index)
|
|
333
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
334
|
+
expand_btn = rows.nth(row_index).locator('.el-table__expand-icon')
|
|
335
|
+
expand_btn.click()
|
|
336
|
+
|
|
337
|
+
def el_table_click_button(
|
|
338
|
+
self,
|
|
339
|
+
targeting: str,
|
|
340
|
+
element: str,
|
|
341
|
+
index: Optional[int] = None,
|
|
342
|
+
*,
|
|
343
|
+
content: str,
|
|
344
|
+
row: str,
|
|
345
|
+
column: str = None
|
|
346
|
+
) -> None:
|
|
347
|
+
"""点击行内按钮
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
targeting: 定位类型
|
|
351
|
+
element: 定位表达式
|
|
352
|
+
index: 表格索引
|
|
353
|
+
content: 按钮文本
|
|
354
|
+
row: 行号
|
|
355
|
+
column: 列号(可选)
|
|
356
|
+
"""
|
|
357
|
+
row_index = int(row)
|
|
358
|
+
INFO(f"点击第 {row_index} 行的 '{content}' 按钮")
|
|
359
|
+
|
|
360
|
+
table = self.locator(targeting, element, index)
|
|
361
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
362
|
+
row_el = rows.nth(row_index)
|
|
363
|
+
|
|
364
|
+
if column:
|
|
365
|
+
cell = row_el.locator('td').nth(int(column))
|
|
366
|
+
btn = cell.locator(f'.el-button:has-text("{content}"), a:has-text("{content}")')
|
|
367
|
+
else:
|
|
368
|
+
btn = row_el.locator(f'.el-button:has-text("{content}"), a:has-text("{content}")')
|
|
369
|
+
|
|
370
|
+
btn.first.click()
|
|
371
|
+
|
|
372
|
+
def el_table_search_row(
|
|
373
|
+
self,
|
|
374
|
+
targeting: str,
|
|
375
|
+
element: str,
|
|
376
|
+
index: Optional[int] = None,
|
|
377
|
+
*,
|
|
378
|
+
content: str,
|
|
379
|
+
column: str
|
|
380
|
+
) -> int:
|
|
381
|
+
"""搜索包含指定文本的行
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
targeting: 定位类型
|
|
385
|
+
element: 定位表达式
|
|
386
|
+
index: 表格索引
|
|
387
|
+
content: 搜索文本
|
|
388
|
+
column: 搜索列号
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
int: 找到的行号,未找到返回 -1
|
|
392
|
+
"""
|
|
393
|
+
col_index = int(column)
|
|
394
|
+
|
|
395
|
+
table = self.locator(targeting, element, index)
|
|
396
|
+
rows = table.locator('.el-table__body-wrapper .el-table__row')
|
|
397
|
+
|
|
398
|
+
for i in range(rows.count()):
|
|
399
|
+
cells = rows.nth(i).locator('td')
|
|
400
|
+
if cells.count() > col_index:
|
|
401
|
+
text = cells.nth(col_index).text_content() or ''
|
|
402
|
+
if content in text:
|
|
403
|
+
INFO(f"找到包含 '{content}' 的行: {i}")
|
|
404
|
+
return i
|
|
405
|
+
|
|
406
|
+
INFO(f"未找到包含 '{content}' 的行", "WARNING")
|
|
407
|
+
return -1
|
|
408
|
+
|
|
409
|
+
def el_table_assert_cell(
|
|
410
|
+
self,
|
|
411
|
+
targeting: str,
|
|
412
|
+
element: str,
|
|
413
|
+
index: Optional[int] = None,
|
|
414
|
+
*,
|
|
415
|
+
content: str,
|
|
416
|
+
expected: str,
|
|
417
|
+
mode: str = 'equals'
|
|
418
|
+
) -> bool:
|
|
419
|
+
"""断言单元格内容
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
targeting: 定位类型
|
|
423
|
+
element: 定位表达式
|
|
424
|
+
index: 表格索引
|
|
425
|
+
content: 行列号 (格式: "row,col")
|
|
426
|
+
expected: 期望值
|
|
427
|
+
mode: 断言模式
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
bool: 断言结果
|
|
431
|
+
"""
|
|
432
|
+
actual = self.el_table_get_cell_text(targeting, element, index, content=content)
|
|
433
|
+
|
|
434
|
+
if mode == 'equals':
|
|
435
|
+
result = actual == expected
|
|
436
|
+
elif mode == 'contains':
|
|
437
|
+
result = expected in actual
|
|
438
|
+
else:
|
|
439
|
+
result = actual == expected
|
|
440
|
+
|
|
441
|
+
INFO(f"单元格断言: '{actual}' {mode} '{expected}' -> {result}")
|
|
442
|
+
return result
|