feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
from playwright.sync_api import Page
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
class FormHelper:
|
||||
"""表单辅助工具类"""
|
||||
|
||||
def __init__(self, page: Page):
|
||||
"""初始化表单辅助工具
|
||||
|
||||
Args:
|
||||
page: Playwright页面对象
|
||||
"""
|
||||
self.page = page
|
||||
|
||||
def fill_input_field(self, selector: str, value: str, timeout: int = 10000) -> None:
|
||||
"""填充输入框
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
value: 要填充的值
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.fill(selector, value)
|
||||
|
||||
def fill_textarea(self, selector: str, value: str, timeout: int = 10000) -> None:
|
||||
"""填充文本域
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
value: 要填充的值
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.fill(selector, value)
|
||||
|
||||
def select_option(self, selector: str, value: str, timeout: int = 10000) -> None:
|
||||
"""选择下拉选项
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
value: 要选择的值
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.select_option(selector, value)
|
||||
|
||||
def select_option_by_label(self, selector: str, label: str, timeout: int = 10000) -> None:
|
||||
"""通过标签选择下拉选项
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
label: 选项标签
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.select_option(selector, label=label)
|
||||
|
||||
def select_option_by_index(self, selector: str, index: int, timeout: int = 10000) -> None:
|
||||
"""通过索引选择下拉选项
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
index: 选项索引
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.select_option(selector, index=index)
|
||||
|
||||
def check_checkbox(self, selector: str, checked: bool = True, timeout: int = 10000) -> None:
|
||||
"""勾选或取消勾选复选框
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
checked: 是否勾选
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.check(selector, force=True) if checked else self.page.uncheck(
|
||||
selector, force=True
|
||||
)
|
||||
|
||||
def toggle_checkbox(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""切换复选框状态
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
checkbox = self.page.locator(selector)
|
||||
if checkbox.is_checked():
|
||||
checkbox.uncheck(force=True)
|
||||
else:
|
||||
checkbox.check(force=True)
|
||||
|
||||
def select_radio_button(self, name: str, value: str, timeout: int = 10000) -> None:
|
||||
"""选择单选按钮
|
||||
|
||||
Args:
|
||||
name: 单选按钮组名称
|
||||
value: 要选择的值
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
selector = f"input[type='radio'][name='{name}'][value='{value}']"
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.check(selector, force=True)
|
||||
|
||||
def upload_file(self, selector: str, file_path: str, timeout: int = 10000) -> None:
|
||||
"""上传文件
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
file_path: 文件路径
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.set_input_files(selector, file_path)
|
||||
|
||||
def fill_form(self, form_data: Dict[str, any], timeout: int = 10000) -> None:
|
||||
"""填充整个表单
|
||||
|
||||
Args:
|
||||
form_data: 表单数据字典,键为字段名,值为字段值
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
for field_name, field_value in form_data.items():
|
||||
if field_value is None or field_value == "":
|
||||
continue
|
||||
|
||||
selector = f"[name='{field_name}']"
|
||||
element = self.page.locator(selector)
|
||||
|
||||
if element.count() == 0:
|
||||
selector = f"#{field_name}"
|
||||
element = self.page.locator(selector)
|
||||
|
||||
if element.count() == 0:
|
||||
continue
|
||||
|
||||
element_type = element.get_attribute("type") or element.evaluate("el => el.tagName")
|
||||
|
||||
if element_type == "checkbox":
|
||||
self.check_checkbox(selector, field_value, timeout)
|
||||
elif element_type == "radio":
|
||||
self.select_radio_button(field_name, field_value, timeout)
|
||||
elif element_type == "select" or element_type == "SELECT":
|
||||
self.select_option(selector, field_value, timeout)
|
||||
elif element_type == "file":
|
||||
self.upload_file(selector, field_value, timeout)
|
||||
elif element_type == "textarea" or element_type == "TEXTAREA":
|
||||
self.fill_textarea(selector, field_value, timeout)
|
||||
else:
|
||||
self.fill_input_field(selector, str(field_value), timeout)
|
||||
|
||||
def submit_form(self, selector: str = "button[type='submit']", timeout: int = 10000) -> None:
|
||||
"""提交表单
|
||||
|
||||
Args:
|
||||
selector: 提交按钮选择器
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.click(selector)
|
||||
|
||||
def reset_form(self, selector: str = "button[type='reset']", timeout: int = 10000) -> None:
|
||||
"""重置表单
|
||||
|
||||
Args:
|
||||
selector: 重置按钮选择器
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.click(selector)
|
||||
|
||||
def clear_form(self, form_selector: str = "form") -> None:
|
||||
"""清空表单
|
||||
|
||||
Args:
|
||||
form_selector: 表单选择器
|
||||
"""
|
||||
form = self.page.locator(form_selector)
|
||||
|
||||
inputs = form.locator(
|
||||
"input[type='text'], input[type='email'], input[type='password'], input[type='tel']"
|
||||
)
|
||||
for i in range(inputs.count()):
|
||||
inputs.nth(i).fill("")
|
||||
|
||||
textareas = form.locator("textarea")
|
||||
for i in range(textareas.count()):
|
||||
textareas.nth(i).fill("")
|
||||
|
||||
def get_form_values(self, form_selector: str = "form") -> Dict[str, str]:
|
||||
"""获取表单值
|
||||
|
||||
Args:
|
||||
form_selector: 表单选择器
|
||||
|
||||
Returns:
|
||||
表单值字典
|
||||
"""
|
||||
form = self.page.locator(form_selector)
|
||||
values = {}
|
||||
|
||||
inputs = form.locator("input")
|
||||
for i in range(inputs.count()):
|
||||
input_element = inputs.nth(i)
|
||||
name = input_element.get_attribute("name")
|
||||
input_type = input_element.get_attribute("type")
|
||||
|
||||
if name and input_type not in ["submit", "reset", "button"]:
|
||||
if input_type == "checkbox":
|
||||
values[name] = str(input_element.is_checked())
|
||||
elif input_type == "radio":
|
||||
if input_element.is_checked():
|
||||
values[name] = input_element.get_attribute("value")
|
||||
else:
|
||||
values[name] = input_element.input_value()
|
||||
|
||||
selects = form.locator("select")
|
||||
for i in range(selects.count()):
|
||||
select_element = selects.nth(i)
|
||||
name = select_element.get_attribute("name")
|
||||
if name:
|
||||
values[name] = select_element.input_value()
|
||||
|
||||
textareas = form.locator("textarea")
|
||||
for i in range(textareas.count()):
|
||||
textarea_element = textareas.nth(i)
|
||||
name = textarea_element.get_attribute("name")
|
||||
if name:
|
||||
values[name] = textarea_element.input_value()
|
||||
|
||||
return values
|
||||
|
||||
def validate_form(self, form_data: Dict[str, any], form_selector: str = "form") -> bool:
|
||||
"""验证表单
|
||||
|
||||
Args:
|
||||
form_data: 期望的表单数据
|
||||
form_selector: 表单选择器
|
||||
|
||||
Returns:
|
||||
表单是否有效
|
||||
"""
|
||||
current_values = self.get_form_values(form_selector)
|
||||
return current_values == form_data
|
||||
|
||||
def is_field_required(self, selector: str) -> bool:
|
||||
"""检查字段是否必填
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
|
||||
Returns:
|
||||
字段是否必填
|
||||
"""
|
||||
element = self.page.locator(selector)
|
||||
return element.get_attribute("required") is not None
|
||||
|
||||
def is_field_valid(self, selector: str) -> bool:
|
||||
"""检查字段是否有效
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
|
||||
Returns:
|
||||
字段是否有效
|
||||
"""
|
||||
element = self.page.locator(selector)
|
||||
is_valid = element.get_attribute("data-valid")
|
||||
|
||||
if is_valid is not None:
|
||||
return is_valid.lower() == "true"
|
||||
|
||||
return not element.evaluate("el => el.checkValidity()")
|
||||
|
||||
def get_field_error(self, selector: str) -> Optional[str]:
|
||||
"""获取字段错误信息
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
|
||||
Returns:
|
||||
错误信息,如果没有错误则返回None
|
||||
"""
|
||||
error_selector = f"{selector} + ~ .error-message, {selector} ~ .error"
|
||||
error_element = self.page.locator(error_selector)
|
||||
|
||||
if error_element.count() > 0 and error_element.is_visible():
|
||||
return error_element.text_content()
|
||||
|
||||
return None
|
||||
|
||||
def wait_for_form_validation(self, timeout: int = 5000) -> None:
|
||||
"""等待表单验证完成
|
||||
|
||||
Args:
|
||||
timeout: 等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_timeout(timeout)
|
||||
|
||||
def fill_date_field(
|
||||
self, selector: str, date: str, format: str = "YYYY-MM-DD", timeout: int = 10000
|
||||
) -> None:
|
||||
"""填充日期字段
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
date: 日期字符串
|
||||
format: 日期格式
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
|
||||
date_input = self.page.locator(selector)
|
||||
date_input_type = date_input.get_attribute("type")
|
||||
|
||||
if date_input_type == "date":
|
||||
date_input.fill(date)
|
||||
else:
|
||||
date_input.click()
|
||||
self.page.wait_for_timeout(500)
|
||||
self.page.keyboard.type(date)
|
||||
self.page.keyboard.press("Enter")
|
||||
|
||||
def fill_number_field(self, selector: str, value: int, timeout: int = 10000) -> None:
|
||||
"""填充数字字段
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
value: 数字值
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
self.page.fill(selector, str(value))
|
||||
|
||||
def increment_number_field(self, selector: str, count: int = 1, timeout: int = 10000) -> None:
|
||||
"""增加数字字段值
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
count: 增加的数量
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
element = self.page.locator(selector)
|
||||
|
||||
for _ in range(count):
|
||||
element.press("ArrowUp")
|
||||
self.page.wait_for_timeout(100)
|
||||
|
||||
def decrement_number_field(self, selector: str, count: int = 1, timeout: int = 10000) -> None:
|
||||
"""减少数字字段值
|
||||
|
||||
Args:
|
||||
selector: CSS选择器
|
||||
count: 减少的数量
|
||||
timeout: 元素等待超时时间(毫秒)
|
||||
"""
|
||||
self.page.wait_for_selector(selector, timeout=timeout, state="visible")
|
||||
element = self.page.locator(selector)
|
||||
|
||||
for _ in range(count):
|
||||
element.press("ArrowDown")
|
||||
self.page.wait_for_timeout(100)
|
||||
Reference in New Issue
Block a user