Playwright AndroidInput 详解:坐标级 tap、drag、swipe、press 与 type 输入 API 实战
本文以 Playwright 官方 API 文档 class-androidinput.md 为蓝本,系统讲解 AndroidInput 类的五个输入方法(tap、drag、swipe、press、type)的参数语义与执行时序,并结合 客户端实现、协议规范 和 服务端分发器 的源码,说明这些方法在 Playwright 内部的真实调用链,帮助你在 Android 真机/模拟器测试中编写可复现、可调试的坐标级交互代码。
读完本文你将掌握:
device.input对象上五个方法的完整参数(x/y浮点坐标、steps步数、AndroidKey按键枚举)与取值约定;- “每步 5 毫秒”的
steps时序模型,以及如何据此估算手势时长; tap/swipe/drag/press/type从 JS 客户端到 Android 驱动的内部传输路径;AndroidKey支持的完整按键范围,以及type在底层被拆解为按键事件的机制。
什么是 AndroidInput:从 AndroidDevice 获取的输入句柄
AndroidInput 是 Playwright Android 支持中的坐标级输入抽象,自 v1.9 引入,当前文档标记仅支持 js 语言(见文档头部的 langs: js 标记)。它不是独立入口,而是挂在 AndroidDevice 上的 input 属性上:通过 playwright.device() 获取设备句柄后,用 device.input 访问该对象,所有方法均为 async。
从源码结构看,android.ts 中 AndroidInput 是一个轻量封装类:构造函数仅持有对 AndroidDevice 的引用,五个方法全部转发到设备通道(this._device._channel)上的 inputType、inputPress、inputTap、inputSwipe、inputDrag 命令,并统一使用 kNoTimeout(不施加客户端超时,交由驱动侧完成):
export class AndroidInput implements api.AndroidInput {
private _device: AndroidDevice;
constructor(device: AndroidDevice) {
this._device = device;
}
async type(text: string) {
await this._device._channel.inputType({ text }, kNoTimeout);
}
async press(key: api.AndroidKey) {
await this._device._channel.inputPress({ key }, kNoTimeout);
}
async tap(point: types.Point) {
await this._device._channel.inputTap({ point }, kNoTimeout);
}
async swipe(from: types.Point, segments: types.Point[], steps: number) {
await this._device._channel.inputSwipe({ segments: [from, ...segments], steps }, kNoTimeout);
}
async drag(from: types.Point, to: types.Point, steps: number) {
await this._device._channel.inputDrag({ from, to, steps }, kNoTimeout);
}
}
值得注意的实现细节:swipe 在客户端就把起始点 from 拼进 segments 数组头部(segments: [from, ...segments]),服务端收到的是一条完整的折线路径。坐标统一为屏幕坐标系的 { x, y } 浮点数。
AndroidInput.tap:单点点击
await device.input.tap({ x, y });
| 参数 | 类型 | 说明 |
|---|---|---|
point |
Object |
目标点,含 x <[float]>、y <[float]> |
tap 在指定坐标执行一次点按。客户端实现只有一行:inputTap({ point })(见 android.ts)。
典型用途是配合 device.screenshot() 或元素查询结果获取的坐标,点击画布、图片区域等不适合用 device.query() + element.tap() 的元素(例如游戏地图、自定义绘制的控件)。
AndroidInput.drag:两点之间的拖拽
await device.input.drag({ x: 200, y: 600 }, { x: 500, y: 600 }, 100);
| 参数 | 类型 | 说明 |
|---|---|---|
from |
Object |
拖拽起点,x <[float]>、y <[float]> |
to |
Object |
拖拽终点,x <[float]>、y <[float]> |
steps |
<[int]> | 拖拽步数。每步耗时 5 毫秒 |
drag 在 from 与 to 两点之间执行一次直线拖拽,语义等价于“按下 → 沿线移动 → 抬起”。客户端直接透传 { from, to, steps }(见 android.ts),与 android.yml 中 inputDrag 的参数定义一一对应:from: Point、to: Point、steps: int。
steps 的时长换算:每步 5ms,因此 steps 直接决定拖拽时长——steps: 100 即 0.5 秒,steps: 400 即 2 秒。步数过少(如 1)会接近瞬移,可能被目标应用忽略(部分列表/滑块控件依赖速度判定手势)。
AndroidInput.swipe:沿折线路径滑动
await device.input.swipe(
{ x: 200, y: 800 }, // from:起点
[ { x: 300, y: 400 }, { x: 300, y: 100 } ], // segments:起点之后的路径点
100 // steps:每段 100 步 = 0.5 秒
);
| 参数 | 类型 | 说明 |
|---|---|---|
from |
Object |
滑动起点,x <[float]>、y <[float]> |
segments |
Array<Object> |
跟随 from 的路径点数组,每项含 x <[float]>、y <[float]> |
steps |
<[int]> | 每段(segment)的步数。每步 5ms,100 步即每段 0.5 秒 |
与 drag 的两点直线不同,swipe 支持任意多段折线:手势依次经过 from → segments[0] → segments[1] → …,因此可以模拟“先向上再向左”这类复合滑动轨迹。
从 客户端源码 看,swipe 的协议消息把 from 合并进 segments 首部发送;而 协议规范 中 inputSwipe 接收的正是合并后的 segments: array<Point> 与 steps: int。服务端分发器 androidDispatcher.ts 不做坐标换算,直接把参数转发给 Android 驱动进程执行。
总时长估算:segments 有 n 项时共 n 段,每段 steps × 5ms。例如上例 2 段 × 100 步 = 1 秒完整滑动手势。
AndroidInput.press:模拟物理按键
await device.input.press('Back');
await device.input.press('Home');
await device.input.press('VolumeUp');
| 参数 | 类型 | 说明 |
|---|---|---|
key |
[AndroidKey] | 要按下的按键名 |
press 触发一次 Android 按键事件。AndroidKey 是 types.d.ts 中定义的字符串联合类型,覆盖范围包括:
- 导航与功能键:
Home、Back、Call、EndCall、Power、Camera、Clear、Sym、Explorer、Envelop等; - 数字与拨号键:
0–9、Star/*、Pound/#、DialUp/DialDown/DialLeft/DialRight/DialCenter; - 音量键:
VolumeUp、VolumeDown; - 字母与标点:
A–Z、Comma/,、Period/.,以及Space/、Enter/\n、Tab/\t等带字面量别名; - 修饰与编辑键:
AltLeft、AltRight、ShiftLeft、ShiftRight、Del、Grave、Minus、Equals、LeftBracket等(定义在types.d.ts的AndroidKey联合类型中,可整体查阅)。
底层键码映射:服务端分发器 中的 inputPress 会把 key 名通过 keyMap 映射为 Android 原生 keyCode 再下发,即 Playwright 负责“按键名 → 键码”的翻译,你无需记忆 Android 键码数值。
AndroidInput.type:向焦点控件输入文本
// 先聚焦输入框(通常用 query + tap),再输入
const input = await device.query('res/input');
await (await input.tap()); // 元素级 tap 获得焦点
await device.input.type('Playwright');
| 参数 | 类型 | 说明 |
|---|---|---|
text |
<[string]> | 要输入的文本,输入到当前聚焦的控件中 |
type 的语义是“输入到当前获得焦点的 widget”——它本身不负责聚焦,调用前需要确保目标输入框已获得焦点(如先 element.tap())。
一个值得注意的实现细节:从 androidDispatcher.ts 的源码结构看,inputType 处理器在将文本拆分为字符对应的键码后,以 Promise.all 并行的方式把每个字符作为 inputPress(携带 keyCode)逐一下发给驱动进程。也就是说,type 在协议层被拆解为一连串按键事件,这也是它对中文等多字节字符或需要 IME 组合的输入场景存在局限性的原因——它走的是按键路径而非剪贴板粘贴。
五个方法的完整调用链:从 JS 到 Android 驱动
综合上述源码,AndroidInput 的完整链路为:
- 客户端:AndroidInput 五个方法将参数打包为
inputTap/inputSwipe/inputDrag/inputPress/inputType通道命令; - 协议层:android.yml 定义了这五条命令的参数契约(
Point、array<Point>、int、string),channels.d.ts 由协议生成,保证客户端与服务端类型一致; - 服务端分发:AndroidDeviceDispatcher 接收命令,
inputPress做键名到键码的映射,inputTap/inputSwipe/inputDrag直接转发; - 驱动层:命令最终由打包在设备上的 Android 测试驱动执行,见 InstrumentedTest.java。该驱动以 instrumentation 形式运行在设备/模拟器上,保证触摸事件由系统直接注入。
仓库中的 tests/android 目录包含 Android 相关测试(如 device.spec.ts、webview.spec.ts),展示了设备连接与页面交互的整体测试组织方式,可作为编写 device.input 用例时的参考骨架。
编写坐标级交互代码的实践建议
- 先量坐标,再写手势:
tap/drag/swipe均为屏幕绝对坐标,与设备分辨率强相关。建议先用device.screenshot()标定坐标,或在playwright.config中按分辨率分组(tests/android/playwright.config.ts 展示了 Android 测试的独立配置方式)。 - 用
steps控制节奏而非速度:每步固定 5ms,没有单独的 speed 参数。需要“慢拖拽”时加大steps;swipe的steps是每段的步数,路径多段时总时长 = 段数 × steps × 5ms。 - 优先用元素 API,坐标 API 兜底:能用
device.query()拿到的元素,优先用element.tap()/element.info();AndroidInput用于元素树覆盖不到的画布、地图、滑块等场景,这正是文档将其定位为坐标级输入的原因。 type需要焦点前置:它输入到“当前聚焦控件”,调用前务必先聚焦;对复杂文本可评估按键拆解路径的局限。
适用范围与前置条件
- 能力自 v1.9 引入,文档标注当前仅
js语言可用; - 需要先通过
playwright.device()(配合 Android 设备连接 测试中的设备启动方式,如 AVD 模拟器)建立AndroidDevice会话,input方法才能调用; - 所有方法均为
async,且内部不设客户端超时(kNoTimeout),长时间手势不会因客户端超时被打断。
参考文件
- API 文档:docs/src/mobile-api/class-androidinput.md,同目录下的 mobile-api 系列文档覆盖 Android 设备/网页视图等对象
- 客户端实现:packages/playwright-core/src/client/android.ts
- 协议规范:packages/protocol/spec/android.yml
- 服务端分发:packages/playwright-core/src/server/dispatchers/androidDispatcher.ts
- 类型定义:packages/playwright-core/types/types.d.ts
- Android 测试:tests/android
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00