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,324 @@
|
|
|
1
|
+
"""Element Plus DatePicker 组件关键字"""
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api import Page, Locator
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
|
|
7
|
+
from ..base_keyword import BaseKeyword
|
|
8
|
+
from ...reference import INFO
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ElDatePickerKeyword(BaseKeyword):
|
|
12
|
+
"""Element Plus DatePicker 组件关键字
|
|
13
|
+
|
|
14
|
+
处理日期选择器、日期时间选择器、日期范围选择器。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, page: Page):
|
|
18
|
+
super().__init__(page)
|
|
19
|
+
|
|
20
|
+
def el_datepicker(
|
|
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: 日期 (格式: YYYY-MM-DD)
|
|
35
|
+
"""
|
|
36
|
+
INFO(f"选择日期: {content}")
|
|
37
|
+
|
|
38
|
+
picker = self.locator(targeting, element, index)
|
|
39
|
+
input_el = picker.locator('.el-input__inner')
|
|
40
|
+
|
|
41
|
+
# 清除并输入日期
|
|
42
|
+
input_el.click()
|
|
43
|
+
input_el.fill(str(content))
|
|
44
|
+
input_el.press('Enter')
|
|
45
|
+
|
|
46
|
+
# 等待面板关闭
|
|
47
|
+
self.page.wait_for_timeout(200)
|
|
48
|
+
|
|
49
|
+
def el_datepicker_panel(
|
|
50
|
+
self,
|
|
51
|
+
targeting: str,
|
|
52
|
+
element: str,
|
|
53
|
+
index: Optional[int] = None,
|
|
54
|
+
*,
|
|
55
|
+
content: str
|
|
56
|
+
) -> None:
|
|
57
|
+
"""通过面板选择日期
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
targeting: 定位类型
|
|
61
|
+
element: 定位表达式
|
|
62
|
+
index: 索引
|
|
63
|
+
content: 日期 (格式: YYYY-MM-DD 或 YYYY/MM/DD)
|
|
64
|
+
"""
|
|
65
|
+
INFO(f"面板选择日期: {content}")
|
|
66
|
+
|
|
67
|
+
# 解析日期
|
|
68
|
+
date_str = str(content).replace('/', '-')
|
|
69
|
+
parts = date_str.split('-')
|
|
70
|
+
year = int(parts[0])
|
|
71
|
+
month = int(parts[1])
|
|
72
|
+
day = int(parts[2])
|
|
73
|
+
|
|
74
|
+
picker = self.locator(targeting, element, index)
|
|
75
|
+
picker.click()
|
|
76
|
+
|
|
77
|
+
# 等待面板出现
|
|
78
|
+
panel = self.page.locator('.el-date-picker:visible, .el-picker-panel:visible')
|
|
79
|
+
panel.wait_for(state='visible', timeout=5000)
|
|
80
|
+
|
|
81
|
+
# 导航到目标年月
|
|
82
|
+
self._navigate_to_month(panel, year, month)
|
|
83
|
+
|
|
84
|
+
# 点击日期
|
|
85
|
+
day_cells = panel.locator(f'.el-date-table td.available')
|
|
86
|
+
for i in range(day_cells.count()):
|
|
87
|
+
cell = day_cells.nth(i)
|
|
88
|
+
cell_text = cell.locator('span').text_content()
|
|
89
|
+
if cell_text and cell_text.strip() == str(day):
|
|
90
|
+
cell.click()
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
def el_datepicker_today(
|
|
94
|
+
self,
|
|
95
|
+
targeting: str,
|
|
96
|
+
element: str,
|
|
97
|
+
index: Optional[int] = None
|
|
98
|
+
) -> None:
|
|
99
|
+
"""选择今天
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
targeting: 定位类型
|
|
103
|
+
element: 定位表达式
|
|
104
|
+
index: 索引
|
|
105
|
+
"""
|
|
106
|
+
INFO("选择今天")
|
|
107
|
+
|
|
108
|
+
picker = self.locator(targeting, element, index)
|
|
109
|
+
picker.click()
|
|
110
|
+
|
|
111
|
+
panel = self.page.locator('.el-date-picker:visible, .el-picker-panel:visible')
|
|
112
|
+
panel.wait_for(state='visible', timeout=5000)
|
|
113
|
+
|
|
114
|
+
# 点击今天按钮
|
|
115
|
+
today_btn = panel.locator('.el-picker-panel__footer').get_by_text('今天')
|
|
116
|
+
if today_btn.is_visible():
|
|
117
|
+
today_btn.click()
|
|
118
|
+
else:
|
|
119
|
+
# 点击当前日期单元格
|
|
120
|
+
panel.locator('.el-date-table td.today').click()
|
|
121
|
+
|
|
122
|
+
def el_datetimepicker(
|
|
123
|
+
self,
|
|
124
|
+
targeting: str,
|
|
125
|
+
element: str,
|
|
126
|
+
index: Optional[int] = None,
|
|
127
|
+
*,
|
|
128
|
+
content: str
|
|
129
|
+
) -> None:
|
|
130
|
+
"""日期时间选择
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
targeting: 定位类型
|
|
134
|
+
element: 定位表达式
|
|
135
|
+
index: 索引
|
|
136
|
+
content: 日期时间 (格式: YYYY-MM-DD HH:mm:ss)
|
|
137
|
+
"""
|
|
138
|
+
INFO(f"选择日期时间: {content}")
|
|
139
|
+
|
|
140
|
+
picker = self.locator(targeting, element, index)
|
|
141
|
+
input_el = picker.locator('.el-input__inner')
|
|
142
|
+
|
|
143
|
+
input_el.click()
|
|
144
|
+
input_el.fill(str(content))
|
|
145
|
+
input_el.press('Enter')
|
|
146
|
+
|
|
147
|
+
self.page.wait_for_timeout(200)
|
|
148
|
+
|
|
149
|
+
def el_daterange(
|
|
150
|
+
self,
|
|
151
|
+
targeting: str,
|
|
152
|
+
element: str,
|
|
153
|
+
index: Optional[int] = None,
|
|
154
|
+
*,
|
|
155
|
+
content: str
|
|
156
|
+
) -> None:
|
|
157
|
+
"""日期范围选择
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
targeting: 定位类型
|
|
161
|
+
element: 定位表达式
|
|
162
|
+
index: 索引
|
|
163
|
+
content: 日期范围 (格式: "YYYY-MM-DD,YYYY-MM-DD" 或 "YYYY-MM-DD ~ YYYY-MM-DD")
|
|
164
|
+
"""
|
|
165
|
+
# 解析日期范围
|
|
166
|
+
if '~' in str(content):
|
|
167
|
+
dates = str(content).split('~')
|
|
168
|
+
elif ',' in str(content):
|
|
169
|
+
dates = str(content).split(',')
|
|
170
|
+
else:
|
|
171
|
+
dates = [content, content]
|
|
172
|
+
|
|
173
|
+
start_date = dates[0].strip()
|
|
174
|
+
end_date = dates[1].strip() if len(dates) > 1 else start_date
|
|
175
|
+
|
|
176
|
+
INFO(f"选择日期范围: {start_date} ~ {end_date}")
|
|
177
|
+
|
|
178
|
+
picker = self.locator(targeting, element, index)
|
|
179
|
+
picker.click()
|
|
180
|
+
|
|
181
|
+
panel = self.page.locator('.el-date-range-picker:visible, .el-picker-panel:visible')
|
|
182
|
+
panel.wait_for(state='visible', timeout=5000)
|
|
183
|
+
|
|
184
|
+
# 输入开始和结束日期
|
|
185
|
+
inputs = panel.locator('.el-date-range-picker__editors input, .el-range-input')
|
|
186
|
+
if inputs.count() >= 2:
|
|
187
|
+
inputs.nth(0).fill(start_date)
|
|
188
|
+
inputs.nth(1).fill(end_date)
|
|
189
|
+
|
|
190
|
+
# 点击确认
|
|
191
|
+
confirm_btn = panel.locator('.el-picker-panel__footer .el-button--primary, .el-button:has-text("确定")')
|
|
192
|
+
if confirm_btn.is_visible():
|
|
193
|
+
confirm_btn.click()
|
|
194
|
+
else:
|
|
195
|
+
self.page.keyboard.press('Enter')
|
|
196
|
+
else:
|
|
197
|
+
# 直接输入到 range-input
|
|
198
|
+
range_inputs = picker.locator('.el-range-input')
|
|
199
|
+
if range_inputs.count() >= 2:
|
|
200
|
+
range_inputs.nth(0).fill(start_date)
|
|
201
|
+
range_inputs.nth(1).fill(end_date)
|
|
202
|
+
self.page.keyboard.press('Enter')
|
|
203
|
+
|
|
204
|
+
def el_datepicker_clear(
|
|
205
|
+
self,
|
|
206
|
+
targeting: str,
|
|
207
|
+
element: str,
|
|
208
|
+
index: Optional[int] = None
|
|
209
|
+
) -> None:
|
|
210
|
+
"""清除日期
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
targeting: 定位类型
|
|
214
|
+
element: 定位表达式
|
|
215
|
+
index: 索引
|
|
216
|
+
"""
|
|
217
|
+
INFO("清除日期")
|
|
218
|
+
|
|
219
|
+
picker = self.locator(targeting, element, index)
|
|
220
|
+
picker.hover()
|
|
221
|
+
self.page.wait_for_timeout(200)
|
|
222
|
+
|
|
223
|
+
clear_btn = picker.locator('.el-input__clear, .el-icon-circle-close')
|
|
224
|
+
if clear_btn.is_visible():
|
|
225
|
+
clear_btn.click()
|
|
226
|
+
|
|
227
|
+
def el_datepicker_get_value(
|
|
228
|
+
self,
|
|
229
|
+
targeting: str,
|
|
230
|
+
element: str,
|
|
231
|
+
index: Optional[int] = None
|
|
232
|
+
) -> str:
|
|
233
|
+
"""获取日期值
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
targeting: 定位类型
|
|
237
|
+
element: 定位表达式
|
|
238
|
+
index: 索引
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
str: 日期值
|
|
242
|
+
"""
|
|
243
|
+
picker = self.locator(targeting, element, index)
|
|
244
|
+
input_el = picker.locator('.el-input__inner, .el-range-input')
|
|
245
|
+
|
|
246
|
+
if input_el.count() > 1:
|
|
247
|
+
# 范围选择器
|
|
248
|
+
start = input_el.nth(0).input_value() or ''
|
|
249
|
+
end = input_el.nth(1).input_value() or ''
|
|
250
|
+
return f"{start} ~ {end}"
|
|
251
|
+
elif input_el.count() == 1:
|
|
252
|
+
return input_el.first.input_value() or ''
|
|
253
|
+
|
|
254
|
+
return ''
|
|
255
|
+
|
|
256
|
+
def el_datepicker_shortcut(
|
|
257
|
+
self,
|
|
258
|
+
targeting: str,
|
|
259
|
+
element: str,
|
|
260
|
+
index: Optional[int] = None,
|
|
261
|
+
*,
|
|
262
|
+
content: str
|
|
263
|
+
) -> None:
|
|
264
|
+
"""点击快捷选项
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
targeting: 定位类型
|
|
268
|
+
element: 定位表达式
|
|
269
|
+
index: 索引
|
|
270
|
+
content: 快捷选项文本(如"最近一周"、"最近一个月")
|
|
271
|
+
"""
|
|
272
|
+
INFO(f"点击快捷选项: {content}")
|
|
273
|
+
|
|
274
|
+
picker = self.locator(targeting, element, index)
|
|
275
|
+
picker.click()
|
|
276
|
+
|
|
277
|
+
panel = self.page.locator('.el-picker-panel:visible')
|
|
278
|
+
panel.wait_for(state='visible', timeout=5000)
|
|
279
|
+
|
|
280
|
+
# 点击快捷选项
|
|
281
|
+
shortcut = panel.locator(f'.el-picker-panel__sidebar button:has-text("{content}"), .el-picker-panel__shortcut:has-text("{content}")')
|
|
282
|
+
if shortcut.is_visible():
|
|
283
|
+
shortcut.click()
|
|
284
|
+
|
|
285
|
+
def _navigate_to_month(self, panel: Locator, year: int, month: int) -> None:
|
|
286
|
+
"""导航到指定年月
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
panel: 面板 Locator
|
|
290
|
+
year: 年
|
|
291
|
+
month: 月
|
|
292
|
+
"""
|
|
293
|
+
# 获取当前显示的年月
|
|
294
|
+
header = panel.locator('.el-date-picker__header')
|
|
295
|
+
|
|
296
|
+
# 最多尝试 120 次(10年)
|
|
297
|
+
for _ in range(120):
|
|
298
|
+
# 获取当前年月
|
|
299
|
+
year_label = header.locator('.el-date-picker__header-label').first
|
|
300
|
+
month_label = header.locator('.el-date-picker__header-label').last
|
|
301
|
+
|
|
302
|
+
current_year_text = year_label.text_content() or ''
|
|
303
|
+
current_month_text = month_label.text_content() or ''
|
|
304
|
+
|
|
305
|
+
# 提取数字
|
|
306
|
+
current_year = int(''.join(filter(str.isdigit, current_year_text)) or '0')
|
|
307
|
+
current_month = int(''.join(filter(str.isdigit, current_month_text)) or '0')
|
|
308
|
+
|
|
309
|
+
if current_year == year and current_month == month:
|
|
310
|
+
break
|
|
311
|
+
|
|
312
|
+
# 计算差值
|
|
313
|
+
diff = (year - current_year) * 12 + (month - current_month)
|
|
314
|
+
|
|
315
|
+
if diff > 0:
|
|
316
|
+
# 往后
|
|
317
|
+
next_btn = header.locator('.el-icon-arrow-right, [class*="arrow-right"]').last
|
|
318
|
+
next_btn.click()
|
|
319
|
+
else:
|
|
320
|
+
# 往前
|
|
321
|
+
prev_btn = header.locator('.el-icon-arrow-left, [class*="arrow-left"]').first
|
|
322
|
+
prev_btn.click()
|
|
323
|
+
|
|
324
|
+
self.page.wait_for_timeout(100)
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Element Plus Dialog 组件关键字"""
|
|
2
|
+
|
|
3
|
+
from playwright.sync_api import Page, Locator
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ..base_keyword import BaseKeyword
|
|
7
|
+
from ...reference import INFO
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ElDialogKeyword(BaseKeyword):
|
|
11
|
+
"""Element Plus Dialog 组件关键字
|
|
12
|
+
|
|
13
|
+
处理对话框、消息框、通知等弹出层组件。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, page: Page):
|
|
17
|
+
super().__init__(page)
|
|
18
|
+
|
|
19
|
+
def el_dialog_wait(self, *, content: str = '10000') -> None:
|
|
20
|
+
"""等待对话框出现
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
content: 超时时间 (毫秒)
|
|
24
|
+
"""
|
|
25
|
+
timeout = int(content)
|
|
26
|
+
INFO("等待对话框出现")
|
|
27
|
+
|
|
28
|
+
dialog = self.page.locator('.el-dialog__wrapper:visible, .el-overlay:visible .el-dialog')
|
|
29
|
+
dialog.wait_for(state='visible', timeout=timeout)
|
|
30
|
+
|
|
31
|
+
def el_dialog_close(self) -> None:
|
|
32
|
+
"""关闭对话框"""
|
|
33
|
+
INFO("关闭对话框")
|
|
34
|
+
|
|
35
|
+
# 尝试点击关闭按钮
|
|
36
|
+
close_btn = self.page.locator('.el-dialog__wrapper:visible .el-dialog__headerbtn, .el-overlay:visible .el-dialog__headerbtn')
|
|
37
|
+
if close_btn.is_visible():
|
|
38
|
+
close_btn.click()
|
|
39
|
+
else:
|
|
40
|
+
# 按 Escape 关闭
|
|
41
|
+
self.page.keyboard.press('Escape')
|
|
42
|
+
|
|
43
|
+
# 等待关闭
|
|
44
|
+
try:
|
|
45
|
+
self.page.locator('.el-dialog:visible').wait_for(state='hidden', timeout=3000)
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def el_dialog_confirm(self, *, content: str = '确定') -> None:
|
|
50
|
+
"""点击对话框确认按钮
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
content: 按钮文本
|
|
54
|
+
"""
|
|
55
|
+
INFO(f"点击对话框按钮: {content}")
|
|
56
|
+
|
|
57
|
+
dialog = self.page.locator('.el-dialog__wrapper:visible, .el-overlay:visible .el-dialog')
|
|
58
|
+
confirm_btn = dialog.locator(f'.el-dialog__footer .el-button:has-text("{content}")')
|
|
59
|
+
|
|
60
|
+
if confirm_btn.is_visible():
|
|
61
|
+
confirm_btn.click()
|
|
62
|
+
else:
|
|
63
|
+
# 尝试 Primary 按钮
|
|
64
|
+
primary_btn = dialog.locator('.el-dialog__footer .el-button--primary')
|
|
65
|
+
if primary_btn.is_visible():
|
|
66
|
+
primary_btn.click()
|
|
67
|
+
|
|
68
|
+
def el_dialog_cancel(self, *, content: str = '取消') -> None:
|
|
69
|
+
"""点击对话框取消按钮
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
content: 按钮文本
|
|
73
|
+
"""
|
|
74
|
+
INFO(f"点击取消按钮: {content}")
|
|
75
|
+
|
|
76
|
+
dialog = self.page.locator('.el-dialog__wrapper:visible, .el-overlay:visible .el-dialog')
|
|
77
|
+
cancel_btn = dialog.locator(f'.el-dialog__footer .el-button:has-text("{content}")')
|
|
78
|
+
|
|
79
|
+
if cancel_btn.is_visible():
|
|
80
|
+
cancel_btn.click()
|
|
81
|
+
else:
|
|
82
|
+
# 尝试非 Primary 按钮
|
|
83
|
+
default_btn = dialog.locator('.el-dialog__footer .el-button:not(.el-button--primary)').first
|
|
84
|
+
if default_btn.is_visible():
|
|
85
|
+
default_btn.click()
|
|
86
|
+
|
|
87
|
+
def el_dialog_input(
|
|
88
|
+
self,
|
|
89
|
+
targeting: str = None,
|
|
90
|
+
element: str = None,
|
|
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
|
+
dialog = self.page.locator('.el-dialog:visible')
|
|
106
|
+
|
|
107
|
+
if targeting and element:
|
|
108
|
+
input_el = self.locator(targeting, element, index)
|
|
109
|
+
else:
|
|
110
|
+
input_el = dialog.locator('.el-input__inner, .el-textarea__inner').first
|
|
111
|
+
|
|
112
|
+
input_el.fill(str(content))
|
|
113
|
+
|
|
114
|
+
def el_dialog_get_title(self) -> str:
|
|
115
|
+
"""获取对话框标题
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
str: 对话框标题
|
|
119
|
+
"""
|
|
120
|
+
dialog = self.page.locator('.el-dialog:visible')
|
|
121
|
+
title = dialog.locator('.el-dialog__title').text_content() or ''
|
|
122
|
+
return title.strip()
|
|
123
|
+
|
|
124
|
+
def el_dialog_get_content(self) -> str:
|
|
125
|
+
"""获取对话框内容
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
str: 对话框内容
|
|
129
|
+
"""
|
|
130
|
+
dialog = self.page.locator('.el-dialog:visible')
|
|
131
|
+
content = dialog.locator('.el-dialog__body').text_content() or ''
|
|
132
|
+
return content.strip()
|
|
133
|
+
|
|
134
|
+
# ==================== MessageBox ====================
|
|
135
|
+
|
|
136
|
+
def el_message_box_wait(self, *, content: str = '10000') -> None:
|
|
137
|
+
"""等待 MessageBox 出现
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
content: 超时时间 (毫秒)
|
|
141
|
+
"""
|
|
142
|
+
timeout = int(content)
|
|
143
|
+
INFO("等待 MessageBox")
|
|
144
|
+
|
|
145
|
+
box = self.page.locator('.el-message-box__wrapper:visible, .el-overlay:visible .el-message-box')
|
|
146
|
+
box.wait_for(state='visible', timeout=timeout)
|
|
147
|
+
|
|
148
|
+
def el_message_box_confirm(self, *, content: str = 'true') -> None:
|
|
149
|
+
"""处理 MessageBox 确认框
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
content: 是否确认 (true/false)
|
|
153
|
+
"""
|
|
154
|
+
accept = str(content).lower() in ('true', '1', 'yes', '确定')
|
|
155
|
+
INFO(f"MessageBox: {'确认' if accept else '取消'}")
|
|
156
|
+
|
|
157
|
+
box = self.page.locator('.el-message-box__wrapper:visible, .el-overlay:visible .el-message-box')
|
|
158
|
+
box.wait_for(state='visible', timeout=5000)
|
|
159
|
+
|
|
160
|
+
if accept:
|
|
161
|
+
box.locator('.el-message-box__btns .el-button--primary').click()
|
|
162
|
+
else:
|
|
163
|
+
box.locator('.el-message-box__btns .el-button:not(.el-button--primary)').first.click()
|
|
164
|
+
|
|
165
|
+
def el_message_box_input(self, *, content: str) -> None:
|
|
166
|
+
"""MessageBox prompt 输入
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
content: 输入内容
|
|
170
|
+
"""
|
|
171
|
+
INFO(f"MessageBox 输入: {content}")
|
|
172
|
+
|
|
173
|
+
box = self.page.locator('.el-message-box:visible')
|
|
174
|
+
input_el = box.locator('.el-message-box__input .el-input__inner')
|
|
175
|
+
input_el.fill(str(content))
|
|
176
|
+
|
|
177
|
+
def el_message_box_get_message(self) -> str:
|
|
178
|
+
"""获取 MessageBox 消息
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
str: 消息内容
|
|
182
|
+
"""
|
|
183
|
+
box = self.page.locator('.el-message-box:visible')
|
|
184
|
+
message = box.locator('.el-message-box__message').text_content() or ''
|
|
185
|
+
return message.strip()
|
|
186
|
+
|
|
187
|
+
# ==================== Message ====================
|
|
188
|
+
|
|
189
|
+
def el_message_wait(self, *, content: str = '5000') -> None:
|
|
190
|
+
"""等待 Message 消息出现
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
content: 超时时间 (毫秒)
|
|
194
|
+
"""
|
|
195
|
+
timeout = int(content)
|
|
196
|
+
INFO("等待 Message")
|
|
197
|
+
|
|
198
|
+
message = self.page.locator('.el-message:visible')
|
|
199
|
+
message.wait_for(state='visible', timeout=timeout)
|
|
200
|
+
|
|
201
|
+
def el_message_get_text(self) -> str:
|
|
202
|
+
"""获取 Message 消息文本
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
str: 消息文本
|
|
206
|
+
"""
|
|
207
|
+
message = self.page.locator('.el-message:visible')
|
|
208
|
+
text = message.locator('.el-message__content').text_content() or ''
|
|
209
|
+
return text.strip()
|
|
210
|
+
|
|
211
|
+
def el_message_assert(self, *, content: str, mode: str = 'contains') -> bool:
|
|
212
|
+
"""断言 Message 消息
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
content: 期望内容
|
|
216
|
+
mode: 断言模式
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
bool: 断言结果
|
|
220
|
+
"""
|
|
221
|
+
actual = self.el_message_get_text()
|
|
222
|
+
|
|
223
|
+
if mode == 'equals':
|
|
224
|
+
result = actual == content
|
|
225
|
+
elif mode == 'contains':
|
|
226
|
+
result = content in actual
|
|
227
|
+
else:
|
|
228
|
+
result = content in actual
|
|
229
|
+
|
|
230
|
+
INFO(f"Message 断言: '{actual}' {mode} '{content}' -> {result}")
|
|
231
|
+
return result
|
|
232
|
+
|
|
233
|
+
def el_message_close(self) -> None:
|
|
234
|
+
"""关闭 Message 消息"""
|
|
235
|
+
INFO("关闭 Message")
|
|
236
|
+
|
|
237
|
+
message = self.page.locator('.el-message:visible')
|
|
238
|
+
close_btn = message.locator('.el-message__closeBtn, .el-icon-close')
|
|
239
|
+
if close_btn.is_visible():
|
|
240
|
+
close_btn.click()
|
|
241
|
+
|
|
242
|
+
# ==================== Notification ====================
|
|
243
|
+
|
|
244
|
+
def el_notification_wait(self, *, content: str = '5000') -> None:
|
|
245
|
+
"""等待 Notification 通知出现
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
content: 超时时间 (毫秒)
|
|
249
|
+
"""
|
|
250
|
+
timeout = int(content)
|
|
251
|
+
INFO("等待 Notification")
|
|
252
|
+
|
|
253
|
+
notification = self.page.locator('.el-notification:visible')
|
|
254
|
+
notification.wait_for(state='visible', timeout=timeout)
|
|
255
|
+
|
|
256
|
+
def el_notification_get_title(self) -> str:
|
|
257
|
+
"""获取 Notification 标题
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
str: 通知标题
|
|
261
|
+
"""
|
|
262
|
+
notification = self.page.locator('.el-notification:visible')
|
|
263
|
+
title = notification.locator('.el-notification__title').text_content() or ''
|
|
264
|
+
return title.strip()
|
|
265
|
+
|
|
266
|
+
def el_notification_get_content(self) -> str:
|
|
267
|
+
"""获取 Notification 内容
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
str: 通知内容
|
|
271
|
+
"""
|
|
272
|
+
notification = self.page.locator('.el-notification:visible')
|
|
273
|
+
content = notification.locator('.el-notification__content').text_content() or ''
|
|
274
|
+
return content.strip()
|
|
275
|
+
|
|
276
|
+
def el_notification_close(self) -> None:
|
|
277
|
+
"""关闭 Notification"""
|
|
278
|
+
INFO("关闭 Notification")
|
|
279
|
+
|
|
280
|
+
notification = self.page.locator('.el-notification:visible')
|
|
281
|
+
close_btn = notification.locator('.el-notification__closeBtn')
|
|
282
|
+
if close_btn.is_visible():
|
|
283
|
+
close_btn.click()
|
|
284
|
+
|
|
285
|
+
# ==================== Drawer ====================
|
|
286
|
+
|
|
287
|
+
def el_drawer_wait(self, *, content: str = '10000') -> None:
|
|
288
|
+
"""等待 Drawer 抽屉出现
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
content: 超时时间 (毫秒)
|
|
292
|
+
"""
|
|
293
|
+
timeout = int(content)
|
|
294
|
+
INFO("等待 Drawer")
|
|
295
|
+
|
|
296
|
+
drawer = self.page.locator('.el-drawer:visible')
|
|
297
|
+
drawer.wait_for(state='visible', timeout=timeout)
|
|
298
|
+
|
|
299
|
+
def el_drawer_close(self) -> None:
|
|
300
|
+
"""关闭 Drawer"""
|
|
301
|
+
INFO("关闭 Drawer")
|
|
302
|
+
|
|
303
|
+
close_btn = self.page.locator('.el-drawer:visible .el-drawer__close-btn')
|
|
304
|
+
if close_btn.is_visible():
|
|
305
|
+
close_btn.click()
|
|
306
|
+
else:
|
|
307
|
+
self.page.keyboard.press('Escape')
|
|
308
|
+
|
|
309
|
+
def el_drawer_get_title(self) -> str:
|
|
310
|
+
"""获取 Drawer 标题
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
str: 抽屉标题
|
|
314
|
+
"""
|
|
315
|
+
drawer = self.page.locator('.el-drawer:visible')
|
|
316
|
+
title = drawer.locator('.el-drawer__title').text_content() or ''
|
|
317
|
+
return title.strip()
|