08ea5fbe98
添加用户管理视图、API和状态管理文件
369 lines
12 KiB
Python
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)
|