首页 / Playwright 入门教程 / 键盘与鼠标高级操作

Playwright 入门教程

键盘与鼠标高级操作

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

Playwrightkeyboardmousepress拖拽快捷键UI 自动化

19. 键盘与鼠标高级操作

本节目标:会用按键和快捷键操作页面,会用底层鼠标 API 完成拖拽、滚动这类高层 API 搞不定的动作。

前两章的 API 都是「面向元素」的:找到元素,对它做点什么。

这一章往下走一层,直接操作输入设备。什么时候需要?回车提交、Ctrl+A 全选、拖动排序、无限滚动加载——这些动作没有对应的高层方法,只能自己拼。

按键:locator.press

给某个元素发一次按键,用 press()

// 在搜索框里按回车
await page.getByRole('searchbox').press('Enter');

// 光标按 Ctrl 向右移动一个词
await page.getByRole('textbox').press('Control+ArrowRight');

// 输入一个美元符号
await page.getByRole('textbox').press('$');

press() 先聚焦元素,再产生一次完整的击键:keydownkeypresskeyup

它接受两类参数。一类是按键名,也就是 KeyboardEvent.key 里的那些取值(另外也认 KeyADigit0 这种键位写法):

Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape,
ArrowDown, ArrowUp, ArrowLeft, ArrowRight, End, Home, Enter,
Insert, PageDown, PageUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ

另一类是单个字符,比如 "a""#""中"

组合键的写法与陷阱

用加号连接修饰键和主键。支持的修饰键有 ShiftControlAltMeta,还有跨平台的 ControlOrMeta

await page.getByRole('textbox').press('Control+o');
await page.getByRole('textbox').press('Control+Shift+T');
await page.getByRole('textbox').press('Shift+ArrowLeft');

修饰键会被按住,直到主键按完才松开。这符合真实键盘的行为。

这里有个特别容易错的点。看这两行:

await page.locator('#name').press('Shift+A');  // 产生大写 A
await page.locator('#name').press('Shift+a');  // 产生小写 a

第二行为什么是小写?因为 Playwright 把 Shift+a 理解成「按住 Shift,再按小写 a 键」,效果就像你开着大写锁定又按了 Shift——两次反转,回到小写。

Warning

想打大写字母,主键就写大写。Shift+A 才是 A,Shift+a 是 a。这个规则不直觉,但记住就好。

单字符也是区分大小写的。press('a')press('A') 结果不同。

page.keyboard:不针对元素的键盘

locator.press() 会先聚焦元素。有时候你不想改焦点,只想对「当前焦点所在处」发按键,这时用 page.keyboard

// 对当前焦点元素按回车
await page.keyboard.press('Enter');

// 按住和松开分开控制
await page.keyboard.down('Shift');
await page.getByText('第 1 项').click();
await page.getByText('第 5 项').click();
await page.keyboard.up('Shift');

// 直接插入文本,不产生按键事件,速度快
await page.keyboard.insertText('一段中文');

down() / up() 这对方法在「按住修饰键做多个动作」时很有用,上面的范围多选就是典型场景。

insertText() 值得单独说一句。它绕过按键事件直接改值,特别适合输入中文——真实的中文输入涉及输入法组合,逐字符模拟反而不准。

Warning

还有个 page.keyboard.type(),官方文档已经标为「不建议使用」。大多数场景该用 locator.fill();页面真的盯着按键做事,就用 locator.pressSequentially()。老代码里见到 keyboard.type() 顺手换掉。

Note

press()pressSequentially()不做可操作性检查。它们假设你已经知道焦点在哪。如果元素还没渲染出来,按键就丢了,且不会报错。稳妥做法是先 click()focus() 一下。

page.mouse:坐标级的鼠标控制

page.mouse 用页面坐标(CSS 像素)工作,原点在视口左上角。

// 移动到坐标
await page.mouse.move(100, 200);

// 按下左键
await page.mouse.down();

// 移动(拖拽过程)
await page.mouse.move(300, 200);

// 松开
await page.mouse.up();

// 在坐标处点击一次(move + down + up 的快捷方式)
await page.mouse.click(150, 150);

// 双击
await page.mouse.dblclick(150, 150);

坐标从哪来?一般不硬编码,而是从元素算:

const box = await page.getByTestId('slider-handle').boundingBox();
if (box) {
  await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
  await page.mouse.down();
  await page.mouse.move(box.x + 200, box.y + box.height / 2, { steps: 10 });
  await page.mouse.up();
}

steps 参数把一次移动拆成多小步。滑块、画布这类监听 mousemove 的组件必须给 steps,否则它们只收到起点和终点两个事件,中间过程全丢。

滚轮:mouse.wheel

滚动通常不用手动做——Playwright 在操作前会自动把元素滚进视口。但下面两种情况需要手动:

一是要触发无限滚动加载。 最可靠的做法不是滚动多少像素,而是把底部元素滚进视野:

await page.getByText('页脚版权信息').scrollIntoViewIfNeeded();

二是要滚动某个内部容器。 先把鼠标悬停到容器上,再滚:

await page.getByTestId('scroll-container').hover();
await page.mouse.wheel(0, 300);  // 水平 0,垂直向下 300

也可以直接改 DOM 属性,更精确:

await page.getByTestId('scroll-container').evaluate(el => {
  el.scrollTop += 100;
});

拖拽:dragTo 与手动拖拽

大多数拖拽用一行搞定:

await page.locator('#item-to-drag').dragTo(page.locator('#drop-zone'));

dragTo() 内部依次做了:悬停源元素 → 按下左键 → 移动到目标元素 → 松开左键。

如果页面用的是 HTML5 拖放 API,依赖 dragover 事件,dragTo() 有可能不触发。这时手动拼,并且把目标悬停两次

await page.locator('#item-to-drag').hover();
await page.mouse.down();
await page.locator('#drop-zone').hover();
await page.locator('#drop-zone').hover();  // 第二次,确保发出 dragover
await page.mouse.up();
Tip

「悬停两次」不是玄学。部分浏览器需要至少两次鼠标移动才会派发 dragover。第二次 hover 制造了这个移动。拖拽测试挂了先试这招。

什么时候该降到这一层

我的判断标准很简单:高层 API 能表达的,绝不用底层 API

原因是底层 API 没有自动等待。page.mouse.click(100, 200) 不管那个坐标上有没有元素、元素是不是还在动,点了就是点了。测试会变脆。

只有这几种情况我才往下走:

  1. 需要按住修饰键完成多个动作(范围多选、多选文件)。
  2. 操作画布、滑块、地图这类靠坐标交互的组件。
  3. 拖拽在 dragTo() 下不生效。
  4. 需要精确控制滚动位置(截图对比场景)。

小结

  • 单次按键用 locator.press(),它会先聚焦;不想改焦点用 page.keyboard.press()
  • 组合键用加号连接,大写字母主键要写大写:Shift+A
  • 按住修饰键做多步操作,用 keyboard.down() / keyboard.up() 包起来。
  • 中文输入优先 keyboard.insertText(),别逐字符敲。
  • 鼠标移动记得给 steps,否则监听 mousemove 的组件收不到中间过程。
  • 拖拽先试 dragTo(),不行再手动拼并悬停两次。
  • 底层 API 不带自动等待,能用高层就别用底层。

下一章预告:文件上传与下载——用 setInputFiles 绕过系统弹窗,用 waitForEvent('download') 接住下载。