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)