Files
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

369 lines
12 KiB
Python

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)