首页
/ Playwright Mouse API 详解:用 page.mouse 精确模拟点击、双击与滚轮滚动

Playwright Mouse API 详解:用 page.mouse 精确模拟点击、双击与滚轮滚动

2026-09-04 17:41:36作者:毕习沙Eudora

本文围绕 Playwright 的 Mouse API 参考文档 展开,讲清 page.mouse 六个核心方法(clickdblclickmovedownupwheel)的坐标系约定、全部参数默认值与底层事件派发机制。读完后,你不仅能正确使用这套低级鼠标 API 完成点击、拖拽类交互和程序化滚动,还能结合仓库源码理解每次调用最终如何变成浏览器端的真实鼠标事件。

Mouse 类与坐标系:以视口左上角为原点

Mouse 类以主框架(main frame)视口左上角为原点、以 CSS 像素为单位操作鼠标。所有 xy 参数都是相对于主框架视口的 CSS 像素坐标,而不是某个元素坐标——这与 locator.click() 这类"点击元素中心"的 API 有本质区别,Mouse 是真正操作光标的低级接口。

每个 page 对象都拥有一个独立的 Mouse 实例,通过 Page.mouse 属性访问。以 JS 为例,用 page.mouse 描绘一个 100x100 的方形的完整示例(官方文档中的原始示例):

// Using ‘page.mouse’ to trace a 100x100 square.
await page.mouse.move(0, 0);
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.move(100, 100);
await page.mouse.move(100, 0);
await page.mouse.move(0, 0);
await page.mouse.up();

同一操作在其他语言中的等价写法:

// Java
page.mouse().move(0, 0);
page.mouse().down();
page.mouse().move(0, 100);
page.mouse().move(100, 100);
page.mouse().move(100, 0);
page.mouse().move(0, 0);
page.mouse().up();
# Python(同步与 async 两种写法均适用,async 时加 await)
page.mouse.move(0, 0)
page.mouse.down()
page.mouse.move(0, 100)
page.mouse.move(100, 100)
page.mouse.move(100, 0)
page.mouse.move(0, 0)
page.mouse.up()
// C#
await Page.Mouse.MoveAsync(0, 0);
await Page.Mouse.DownAsync();
await Page.Mouse.MoveAsync(0, 100);
await Page.Mouse.MoveAsync(100, 100);
await Page.Mouse.MoveAsync(100, 0);
await Page.Mouse.MoveAsync(0, 0);
await Page.Mouse.UpAsync();

这个"先 down() 再多次 move() 最后 up()"的组合正是实现按住鼠标拖出轨迹(drawing、拖拽、圈选)的标准模式,因为 down 之后光标移动会携带 mousedown 状态。

调试提示:如果想观察鼠标移动到了哪里,可以用 Trace Viewer 或 Playwright Inspector,每次鼠标操作都会在轨迹图中显示一个红点标记鼠标位置。红点机制来自服务端的 instrumentation 回调,见文末"事件派发链路"一节。

Mouse.click:move + down + up 的快捷方式

click(x, y, options)movedownup 三个操作的组合快捷方式,参数与默认值如下(参数说明源自 docs/src/api/params.md):

参数 类型 默认值 说明
x float 必填 相对主框架视口左上角的 X 坐标(CSS 像素)
y float 必填 相对主框架视口左上角的 Y 坐标(CSS 像素)
button "left" | "middle" | "right" left 使用的鼠标按键
clickCount int 1 对应 DOM UIEvent.detail 的点击次数
delay float 0 mousedownmouseup 之间的等待毫秒数

delay 的意义在于模拟真实用户"按住一段时间"的点击(部分页面用 press 时长区分点击与长按)。从 服务端 Mouse.click 的实现 可以看到两条执行路径:

  • 设置了 delay:先 move 到位,然后对每个 clickCount 依次执行 down → 等待 delay 毫秒 → up,中间点击之间也插入 delay 间隔;
  • 未设置 delay(默认路径):Playwright 将 move、down、up 尽量并发发射Promise.all),以最快完成整个点击序列;只有当 steps > 1 时才会先等待移动插值完成再按键。

这一细节解释了为什么默认的 click 非常快——它并不会人为放慢节奏,需要"拟人"节奏时请显式传 delaysteps

客户端侧,Mouse.click 只是把参数转发到 page 的通信通道,真正的动作编排发生在驱动端(server/input.ts)。

Mouse.dblclick:双键按下与 clickCount=2

dblclick(x, y, options) 等价于 move → down → up → down → up 两个完整点击序列。它支持的选项是 click 的严格子集:

  • xy:同上,主框架视口坐标(CSS 像素);
  • button:默认 left
  • delay:默认 0。

客户端实现 看,dblclick 并非独立协议消息,而是直接调用 click(x, y, { ...options, clickCount: 2 })——即通过 clickCount: 2 让浏览器把第二次 mousedown 识别为双击(detail = 2),这正是 DOM 中 dblclick 事件能被页面正确触发的关键。C# 中该方法别名为 DblClickAsync

Mouse.move:steps 参数控制插值步数

move(x, y, options) 派发 mousemove 事件,唯一的可选项是 steps

  • steps(int):默认为 1。设为 n 时,Playwright 会在"当前光标位置"到目标点之间发送 n线性插值mousemove 事件;设为 1 时只在终点发出单个 mousemove 事件。

服务端 move 的实现 清晰地展示了插值逻辑:

const { steps = 1 } = options;
const fromX = this._x;
const fromY = this._y;
this._x = x;
this._y = y;
for (let i = 1; i <= steps; i++) {
  const middleX = fromX + (x - fromX) * (i / steps);
  const middleY = fromY + (y - fromY) * (i / steps);
  await this._raw.move(progress, middleX, middleY, this._lastButton, this._buttons, this._keyboard._modifiers(), !!options.forClick);
}

几个从源码可以确认的事实:

  1. Mouse 内部维护了持久化坐标_x_y 字段,初始为 0,0),每次 move 的插值起点是上次结束位置,因此多次 move 调用会形成连续轨迹;
  2. 每次 move 会携带当前按住的按键集合_buttons)和键盘修饰键状态_keyboard._modifiers()),所以拖拽(down 之后 move)发出的 mousemove 事件带有正确的 buttons 标志位;
  3. steps > 1 的连续移动更容易通过一些检测"瞬移式"鼠标轨迹的前端逻辑,做拖拽动画、Canvas 绘制等测试时建议使用。

Mouse.down 与 Mouse.up:手动控制按键生命周期

  • down(options):派发 mousedown 事件;
  • up(options):派发 mouseup 事件。

两者的可选项相同:

参数 默认值 说明
button left 鼠标按键,可选 left / middle / right
clickCount 1 对应 UIEvent.detail

服务端 down/up 实现 看,down 会把指定按键加入内部 _buttons 集合并记录 _lastButtonup 则从集合中移除对应按键并把 _lastButton 复位为 'none'。这意味着多个按键可以处于同时按下的状态(例如先 down({button:'left'})down({button:'right'})),事件中的 buttons 位掩码会如实反映,这对测试多选、右键菜单与左键并发等场景很重要。

Mouse.wheel:程序化滚动页面

wheel(deltaX, deltaY) 自 v1.15 引入,派发 wheel 事件,通常用于手动滚动页面。两个参数均为 float:

  • deltaX:水平滚动像素数;
  • deltaY:垂直滚动像素数。

官方文档特别注明:wheel 事件若未被页面处理可能引起滚动,且该方法不会等待滚动动画结束就返回——因此在 wheel 之后断言滚动位置前,建议配合 page.waitForFunction 或等待懒加载内容出现。

关于滚动,Playwright 的输入指南给出了一条重要原则:大多数时候 Playwright 会在动作前自动滚动元素进入视口,无需手动滚动。真正需要手动滚动的场景是触发无限列表加载、或把页面定位到特定截图位置。文档推荐的可靠做法分两档:

  1. 让特定元素可见(首选):await page.getByText('Footer text').scrollIntoViewIfNeeded();
  2. 精确控制滚动:先把鼠标悬停到目标容器上,再使用 wheel;或者直接用 evaluate 修改 scrollTop
// Position the mouse and scroll with the mouse wheel
await page.getByTestId('scrolling-container').hover();
await page.mouse.wheel(0, 10);

// Alternatively, programmatically scroll a specific element
await page.getByTestId('scrolling-container').evaluate(e => e.scrollTop += 100);

注意 wheel 作用在鼠标当前悬停的位置——这就是为什么先 hover()wheel():滚轮事件发送到光标所在元素,若光标停在页面根上则滚动整个页面,停在可滚动容器上则滚动容器。Chromium 的 wheel 实现 正是把当前坐标与 delta 一起通过 CDP 的 Input.dispatchMouseEventtype: 'mouseWheel')发给浏览器。

事件派发链路:从 page.mouse 到浏览器

结合源码,一次 page.mouse.click(100, 200) 的完整调用链是:

  1. 客户端client 侧 Mouse 类 把参数打包,通过 _page._channel.mouseClick(...) 发送(内部使用 kNoTimeout 常量,不受 API 默认超时约束);
  2. DispatcherpageDispatcher 收到协议消息后调用 this._page.mouse.apiWheel/apiClick/...api* 方法会先触发 instrumentation.onBeforeInputAction——这就是 Trace Viewer 里红点的来源;
  3. 服务端 Mouseserver/input.ts 中的 Mouse 类 维护坐标、按键集合、修饰键,完成插值与点击序列编排,再委托给各浏览器的 RawMouse 实现;
  4. 浏览器适配层:例如 Chromium 的 crInputmove/down/up/wheel 分别映射为 CDP 的 mouseMovedmousePressedmouseReleasedmouseWheel 事件类型;Firefox 与 WebKit 有各自的对应实现,从而保证三套浏览器收到一致语义的鼠标事件。

从这条链路可以推断两点实践结论:其一,Mouse API 发出的是浏览器原生输入管线认可的真实输入事件(而非 el.click() 这类合成 DOM 事件),因此能触发 :active 伪类、原生拖拽、pointerdown 等依赖真实输入的行为;其二,每次输入动作都会进入 instrumentation 与 Trace 记录,输入序列在 Trace Viewer 中是可回放、可定位的。

适用场景与选型建议

  • 优先用元素级 API:能用 locator.click() / locator.dblclick() 就不要用 page.mouse,前者自带可操作性等待、自动滚动和坐标计算;
  • 用 Mouse API 的典型场景:Canvas 绘图与拖拽轨迹(down → 多次 move → up)、需要精确视口坐标的点击(点击空白区域、contextmenu 右键 click(x, y, { button: 'right' }))、测试滚轮驱动的分页/懒加载(hover + wheel)、以及需要"按住 N 毫秒"的长点击(clickdelay 或手动 down/wait/up);
  • 参数速记button 默认 leftclickCount 默认 1、delay 默认 0、steps 默认 1;wheel 不等待滚动动画完成。

以上行为均以当前仓库源码与 Mouse 参考文档 为准,参数共享定义可查阅 docs/src/api/params.md 中的 input-buttoninput-click-countinput-down-up-delayinput-mousemove-steps 条目。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389