首页
/ Playwright AndroidInput 详解:坐标级 tap、drag、swipe、press 与 type 输入 API 实战

Playwright AndroidInput 详解:坐标级 tap、drag、swipe、press 与 type 输入 API 实战

2026-09-06 13:33:24作者:伍霜盼Ellen

本文以 Playwright 官方 API 文档 class-androidinput.md 为蓝本,系统讲解 AndroidInput 类的五个输入方法(tapdragswipepresstype)的参数语义与执行时序,并结合 客户端实现协议规范服务端分发器 的源码,说明这些方法在 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.tsAndroidInput 是一个轻量封装类:构造函数仅持有对 AndroidDevice 的引用,五个方法全部转发到设备通道(this._device._channel)上的 inputTypeinputPressinputTapinputSwipeinputDrag 命令,并统一使用 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 毫秒

dragfromto 两点之间执行一次直线拖拽,语义等价于“按下 → 沿线移动 → 抬起”。客户端直接透传 { from, to, steps }(见 android.ts),与 android.ymlinputDrag 的参数定义一一对应:from: Pointto: Pointsteps: 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 支持任意多段折线:手势依次经过 fromsegments[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 按键事件。AndroidKeytypes.d.ts 中定义的字符串联合类型,覆盖范围包括:

  • 导航与功能键:HomeBackCallEndCallPowerCameraClearSymExplorerEnvelop 等;
  • 数字与拨号键:09Star/*Pound/#DialUp/DialDown/DialLeft/DialRight/DialCenter
  • 音量键:VolumeUpVolumeDown
  • 字母与标点:AZComma/,Period/.,以及 Space/ Enter/\nTab/\t 等带字面量别名;
  • 修饰与编辑键:AltLeftAltRightShiftLeftShiftRightDelGraveMinusEqualsLeftBracket 等(定义在 types.d.tsAndroidKey 联合类型中,可整体查阅)。

底层键码映射服务端分发器 中的 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 的完整链路为:

  1. 客户端AndroidInput 五个方法将参数打包为 inputTap / inputSwipe / inputDrag / inputPress / inputType 通道命令;
  2. 协议层android.yml 定义了这五条命令的参数契约(Pointarray<Point>intstring),channels.d.ts 由协议生成,保证客户端与服务端类型一致;
  3. 服务端分发AndroidDeviceDispatcher 接收命令,inputPress 做键名到键码的映射,inputTap/inputSwipe/inputDrag 直接转发;
  4. 驱动层:命令最终由打包在设备上的 Android 测试驱动执行,见 InstrumentedTest.java。该驱动以 instrumentation 形式运行在设备/模拟器上,保证触摸事件由系统直接注入。

仓库中的 tests/android 目录包含 Android 相关测试(如 device.spec.tswebview.spec.ts),展示了设备连接与页面交互的整体测试组织方式,可作为编写 device.input 用例时的参考骨架。

编写坐标级交互代码的实践建议

  • 先量坐标,再写手势tap/drag/swipe 均为屏幕绝对坐标,与设备分辨率强相关。建议先用 device.screenshot() 标定坐标,或在 playwright.config 中按分辨率分组(tests/android/playwright.config.ts 展示了 Android 测试的独立配置方式)。
  • steps 控制节奏而非速度:每步固定 5ms,没有单独的 speed 参数。需要“慢拖拽”时加大 stepsswipesteps每段的步数,路径多段时总时长 = 段数 × steps × 5ms。
  • 优先用元素 API,坐标 API 兜底:能用 device.query() 拿到的元素,优先用 element.tap() / element.info()AndroidInput 用于元素树覆盖不到的画布、地图、滑块等场景,这正是文档将其定位为坐标级输入的原因。
  • type 需要焦点前置:它输入到“当前聚焦控件”,调用前务必先聚焦;对复杂文本可评估按键拆解路径的局限。

适用范围与前置条件

  • 能力自 v1.9 引入,文档标注当前仅 js 语言可用;
  • 需要先通过 playwright.device()(配合 Android 设备连接 测试中的设备启动方式,如 AVD 模拟器)建立 AndroidDevice 会话,input 方法才能调用;
  • 所有方法均为 async,且内部不设客户端超时(kNoTimeout),长时间手势不会因客户端超时被打断。

参考文件

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