首页 / Playwright 入门教程 / 表单操作:选择/勾选/聚焦/悬停

Playwright 入门教程

表单操作:选择/勾选/聚焦/悬停

本教程共 59 篇 · 第 18 篇 · 更新于 2026-08-04 · 约 9 分钟阅读

PlaywrightselectOptioncheckhoverfocus表单测试UI 自动化

18. 表单操作:选择/勾选/聚焦/悬停

本节目标:能熟练操作下拉框、复选框、单选按钮,并会用聚焦和悬停触发页面的动态行为。

上一章解决了点击和输入。表单里还剩几类控件,各有各的脾气。

下拉框不能靠点击选值,复选框不能靠点击切换(至少不该靠),悬停菜单更是新手的重灾区。这章一个个拆。

下拉选择:selectOption

原生 <select> 元素用 selectOption() 操作。它有三种匹配方式:

// 按 value 或 label 匹配(传字符串时两者都试)
await page.getByLabel('选择颜色').selectOption('blue');

// 明确按 label(用户看到的文字)匹配
await page.getByLabel('选择颜色').selectOption({ label: '蓝色' });

// 明确按 value(HTML 属性值)匹配
await page.getByLabel('选择颜色').selectOption({ value: 'blue' });

// 按序号匹配,从 0 开始
await page.getByLabel('选择颜色').selectOption({ index: 2 });

多选下拉传数组,一次选多项:

await page.getByLabel('选择多个颜色').selectOption(['red', 'green', 'blue']);

想清空选择,传空数组:

await page.getByLabel('选择颜色').selectOption([]);
Tip

优先用 { label: '中文文案' }value 是开发写在 HTML 里的内部标识,改起来没心理负担;label 是用户看得见的东西,改动会走评审。测试挂在后者上更稳。

selectOption() 的可操作性检查是:可见、启用。不检查稳定和接收事件,因为它不模拟鼠标点开下拉列表,而是直接设置选中项并触发 change 事件。

自定义下拉怎么办

现在很多组件库的「下拉框」根本不是 <select>,而是一堆 <div> 拼出来的。这种情况 selectOption() 用不了,只能按真实交互来写:

// 先点开,再点选项
await page.getByRole('combobox', { name: '城市' }).click();
await page.getByRole('option', { name: '杭州' }).click();

判断方法很简单:打开开发者工具看标签名。是 <select> 就用 selectOption,不是就老老实实点两下。

勾选与取消:check / uncheck / setChecked

复选框和单选按钮用 check()

// 勾选
await page.getByLabel('我已阅读并同意条款').check();

// 取消勾选(单选按钮不支持取消)
await page.getByLabel('订阅邮件').uncheck();

// 选中一个单选按钮
await page.getByLabel('XL 码').check();

这三个方法适用于 input[type=checkbox]input[type=radio],以及带 role="checkbox" 的自定义组件。

如果选中状态来自变量,用 setChecked() 更顺手:

const shouldSubscribe = true;
await page.getByLabel('订阅邮件').setChecked(shouldSubscribe);

它内部会判断当前状态,只在需要时才动手。

Warning

别用 click() 代替 check()click() 是「切换」语义,跑两遍就变回去了;check() 是「确保为选中」语义,跑几遍结果都一样。测试重跑、重试的时候,这个差别会救命。

check() 系列会跑完整的四项检查:可见、稳定、接收事件、启用。因为它确实模拟了一次真实点击,和 click() 同级。

顺手断言一下

改完状态最好确认一下,用专门的断言:

await page.getByLabel('订阅邮件').check();
await expect(page.getByLabel('订阅邮件')).toBeChecked();

// 断言未选中
await expect(page.getByLabel('接收广告')).not.toBeChecked();

toBeChecked() 会自动重试,不用担心状态更新有延迟。断言的完整体系在第 22 章讲。

聚焦与失焦:focus / blur

有些校验逻辑挂在 focusblur 事件上。比如密码框聚焦时弹出强度提示,失焦时才校验格式。

// 让元素获得焦点
await page.getByLabel('密码').focus();

// 移除焦点
await page.getByLabel('密码').blur();

这两个方法不做任何可操作性检查。它们直接调 DOM 的 focus() / blur(),元素被遮挡也照样生效。

典型用法是测「离开输入框才报错」这种场景:

await page.getByLabel('邮箱').fill('not-an-email');
await page.getByLabel('邮箱').blur();
await expect(page.getByText('邮箱格式不正确')).toBeVisible();

想确认焦点落在哪,用 toBeFocused()

await expect(page.getByLabel('用户名')).toBeFocused();

悬停:hover

鼠标移上去才展开的菜单、才显示的提示气泡,都靠 hover()

await page.getByRole('button', { name: '更多' }).hover();
await page.getByRole('menuitem', { name: '删除' }).click();

hover() 检查可见、稳定、接收事件,但不检查启用。禁用的元素也能悬停,这符合真实情况。

悬停是新手翻车最多的地方,翻车原因就两个。

一是菜单收得太快。 有些实现是鼠标一离开就收起,而 Playwright 移向下一个元素的路上正好触发了收起。先确认菜单项是不是宿主的子元素:是的话鼠标路径不会离开父容器,没问题;不是的话,考虑改用键盘导航。

二是悬停完没等展开。 hover() 之后直接 click(),菜单可能还在播展开动画。click() 的「稳定」检查通常能帮你兜住。真不放心就先加一条断言:

await page.getByRole('button', { name: '更多' }).hover();
await expect(page.getByRole('menu')).toBeVisible();
await page.getByRole('menuitem', { name: '删除' }).click();

各操作检查项对照

把这两章的方法放一起对比,心里更有数:

操作可见稳定接收事件启用可编辑
click() / dblclick()-
check() / uncheck() / setChecked()-
hover()--
fill() / clear()--
selectOption()---
focus() / blur()-----
press() / pressSequentially()-----

表里的「可编辑」是指:元素启用没有 readonly 属性。所以只读输入框上 fill() 会超时失败,focus() 却照样能用。

一个完整表单示例

import { test, expect } from '@playwright/test';

test('提交注册表单', async ({ page }) => {
  await page.goto('https://example.com/signup');

  await page.getByLabel('用户名').fill('mashangxue');
  await page.getByLabel('城市').selectOption({ label: '杭州' });
  await page.getByLabel('我已阅读并同意条款').check();

  // 失焦触发校验
  await page.getByLabel('邮箱').fill('me@example.com');
  await page.getByLabel('邮箱').blur();

  await expect(page.getByLabel('我已阅读并同意条款')).toBeChecked();
  await page.getByRole('button', { name: '注册' }).click();

  await expect(page).toHaveURL(/\/welcome/);
});

小结

  • 原生 <select>selectOption(),优先按 label 匹配;自定义下拉靠点击。
  • 勾选用 check() / uncheck(),状态来自变量时用 setChecked(),别用 click() 切换。
  • focus() / blur() 不做检查,专门用来触发焦点相关逻辑。
  • hover() 用于悬停菜单,配合断言确认菜单已展开更稳。
  • 记住各操作的检查项差异,能帮你快速定位「为什么这里报超时」。

下一章预告:键盘与鼠标高级操作——按键、组合键、坐标级鼠标控制和拖拽,高层 API 搞不定时往下降一层。