Skip to content

第 10 章 · 表单与复杂组件交互

本章目标:掌握下拉框、复选框、文件上传、原生对话框等复杂控件的自动化方法,分清 fillpress_sequentially 的适用边界,学会处理日期控件这类"难点专业户"。

10.1 下拉与选择类控件

原生 <select>select_option 处理,支持按 value、label 或 index 定位,也支持一次多选:

python
# 单选:值或 label 匹配即可
page.get_by_label("Choose a color").select_option("blue")
page.get_by_label("Choose a color").select_option(label="Blue")

# 多选:传入列表
page.get_by_label("Choose multiple colors").select_option(["red", "green", "blue"])

# 按 index 选择
page.get_by_label("Choose a color").select_option(index=2)

复选框和单选钮统一用 check() / uncheck() / set_checked(bool)——set_checked 同时适用于 input[type=checkbox]input[type=radio][role=checkbox]

python
page.get_by_label("I agree to the terms above").check()
expect(page.get_by_label("Subscribe to newsletter")).to_be_checked()
page.get_by_label("XL").set_checked(True)     # 幂等:已是该状态则不动作

10.2 文件上传的三种姿势

方式一(最常用):直接对 <input type="file"> 设置文件,无需打开系统对话框:

python
# 单文件
page.get_by_label("Upload file").set_input_files("myfile.pdf")
# 多文件
page.get_by_label("Upload files").set_input_files(["file1.txt", "file2.txt"])
# 上传整个目录
page.get_by_label("Upload directory").set_input_files("mydir")
# 清空已选文件
page.get_by_label("Upload file").set_input_files([])

方式二:从内存上传 Buffer,测试"生成报告再上传"这类流程时特别有用:

python
page.get_by_label("Upload file").set_input_files(
    files=[{"name": "test.txt", "mimeType": "text/plain", "buffer": b"hello e2e"}]
)

方式三:input 是点击后才动态创建的,用 expect_file_chooser 接住系统文件选择器事件:

python
with page.expect_file_chooser() as fc_info:
    page.get_by_text("选择文件").click()
fc_info.value.set_files("myfile.pdf")     # 同样可以传列表/Buffer

10.3 原生对话框:alert / confirm / prompt

Playwright 默认自动 dismiss 所有对话框。要接受输入或点确定,必须在触发动作之前注册 dialog 处理器:

python
# 自动接受所有 confirm/alert
page.on("dialog", lambda dialog: dialog.accept())

# prompt 需要带输入文本;也可以按类型分别处理
def handle(dialog):
    if dialog.type == "prompt":
        dialog.accept("张三")
    else:
        dialog.accept()

page.on("dialog", handle)
page.get_by_role("button", name="删除").click()

监听器必须真正处理对话框

官方文档特别警告:page.on("dialog") 的回调如果只 print 不 accept/dismiss,后续动作会永久卡死——Web 对话框是模态的,会阻塞页面执行。只读 dialog.message 而不处理是最常见的翻车点。

10.4 fill、press_sequentially 与键盘操作

fill 直接设置值并触发 input 事件(清空原有内容),适用于 <input><textarea>[contenteditable],是表单填充的默认选择。需要逐键模拟真实敲击时(依赖 keydown 计数的自动补全、掩码输入框),用 press_sequentially——它是旧 type() 方法的继任者:

python
page.get_by_role("textbox").fill("Peter")                       # 一次性填入
page.locator("#area").press_sequentially("Hello!", delay=50)   # 逐键输入

# 组合键与功能键
page.get_by_text("Submit").press("Enter")
page.get_by_role("textbox").press("Control+ArrowRight")

# 悬停与拖拽
page.get_by_text("Item").hover()
source.drag_to(page.get_by_role("listitem", name="done"))      # locator.drag_to

fillpress_sequentially 的本质区别:前者是"设值",后者是完整的键盘事件序列(keydown/keypress/keyup 全套)。给普通输入框逐键打字只会白白拖慢测试。

10.5 日期控件的处理策略

日期控件分两类,策略完全不同:

  • 原生 <input type="date">:直接 fill 标准格式字符串即可,不要去点日历面板:
    python
    page.get_by_label("Birth date").fill("2020-02-02")
    page.get_by_label("Appointment time").fill("13:15")            # 时间
    page.get_by_label("Local time").fill("2020-03-02T05:15")       # 本地日期时间
  • JS 日历组件(Element UI/AntD 等):优先看它是否支持在 input 里键入(可聚焦后 fill);必须点选时,定位到年月切换按钮循环点击到目标月份,再 get_by_role("cell", name=...) 点具体日期。切忌用坐标硬算位置——分辨率一变就碎。

本章小结

  • <select>select_option(value/label/index),多选传列表;
  • 文件上传首选 set_input_files(含目录、Buffer、清空三种形态),动态创建的 input 用 expect_file_chooser
  • 对话框默认自动 dismiss;自定义监听器必须 accept/dismiss 收尾否则卡死;
  • fill 是设值、press_sequentially 才是逐键模拟(替代已弃用的 type);
  • 日期控件:原生直接 fill,组件化日历走定位点击。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. locator.type() 方法在当前版本中的状态是?

2. 没有注册 page.on("dialog") 监听器时,页面弹出 alert 会怎样?

3. 关于 dialog 监听器的正确写法是?

4. 测试"生成内容后上传"的场景,最方便的上传方式是?

🛠️ 动手实践

  1. 编写测试:一个 multi-select 下拉中选中三个城市并断言提交后的请求体(结合第 8 章的 expect_response)。
  2. 用 Buffer 方式构造一个 3MB 的假 CSV 上传,断言服务端返回的解析行数。
  3. 为一个带 confirm 的删除按钮写测试:分别验证"确认删除"与"取消删除"两种路径的界面结果。

下一章:截图、视频与 Trace Viewer