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,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()