查找元素

如何阅读界面转储,选择持续有效的选择器,处理重复元素、列表、图标、网页和没有元素的界面,以及排查匹配不到的选择器。

本页为译文。如与英文版不一致,以英文版为准。 English

Droidline 脚本的大部分工作,是告诉它要操作哪一个元素。本指南介绍如何像 Droidline 那样查看界面,以及如何选择在应用更新或手机语言改变之后仍然有效的选择器。

先看再写

在手机上打开要操作的界面,然后保存 Droidline 看到的内容:

droidline dump screen.json
droidline screenshot screen.png
d.dump("screen.json")
d.screenshot("screen.png")
await d.dump("screen.json");
await d.screenshot("screen.png");

在编辑器中打开 screen.json,与截图并排放置,搜索你在手机上看到的文字。每个匹配项都是一个元素;它周围的字段就是你可以使用的选项:

{
  "text": "Keep me signed in",
  "id": "dev.droidline.demo:id/auto_login",
  "desc": "",
  "class": "android.widget.CheckBox",
  "bounds": [60, 860, 1020, 960],
  "clickable": true,
  "checkable": true,
  "checked": false
}

bounds 是以像素为单位的 [left, top, right, bottom]。如果不确定文件中的哪个元素对应屏幕上的哪个东西,就把 bounds 和截图对照:上面这个元素横向从 60 到 1020 像素,纵向从 860 到 960 像素。

挑选选择器

选择器由一个字段和一个值组成。按以下顺序挑选:

  1. id,如果元素有的话。 ID 由应用的开发者设定,不会随手机语言变化,大多数在应用更新后也不变。只写 :id/ 之后的部分:("id", "auto_login")。
  2. text,用于按钮和标签。 在代码中一目了然,但它是显示出来的文字,所以会随语言变化,有时也会随更新变化。如果文字中有会变化的部分,例如数量,就使用 textContains:("textContains", "unread") 能匹配“3 unread”。
  3. desc,用于图标。 图标按钮通常不显示文字,但带有供屏幕阅读器使用的描述,例如“Search”或“More options”。
  4. class 加 nth,作为最后的手段。 “第三个复选框”这种写法,布局一变就会失效。
d.touch("id", "auto_login")
d.touch("text", "Log in")
d.touch("textContains", "unread")
d.touch("desc", "More options")
d.touch("class", "android.widget.CheckBox", nth=2)
await d.touch("id", "auto_login");
await d.touch("text", "Log in");
await d.touch("textContains", "unread");
await d.touch("desc", "More options");
await d.touch("class", "android.widget.CheckBox", { nth: 2 });
droidline touch id auto_login
droidline touch text "Log in"
droidline touch textContains unread
droidline touch desc "More options"
droidline touch class android.widget.CheckBox --nth 2

无法直接点击的文字

文字常常是一个更大的可点击行中的标签,而标签本身不可点击。你不需要去找那一行:touch 会发现标签不接受点击,转而点击离它最近的可点击父元素。发生这种情况时,回复中会有 "via": "parent"。

如果标签位于你要操作的控件旁边,例如设置行末尾的开关,点击标签所在的行通常就能切换开关。如果不行,就直接选中开关本身,通常用 class 和 nth。

多个元素匹配

有多个元素匹配时,命令作用于按屏幕顺序的第一个。用 nth 选择其他元素(从 0 开始计数),用 count 查看一共有几个:

print(d.count("text", "Delete"))          # 3
d.touch("text", "Delete", nth=1)          # 第二个
console.log(await d.count("text", "Delete"));   // 3
await d.touch("text", "Delete", { nth: 1 });    // 第二个
droidline count text Delete
droidline touch text Delete --nth 1

列表中更靠下的元素

dump 只包含屏幕上显示的内容,某些列表会多包含一点。更靠下的元素此时还不存在,所以 touch 找不到它。scroll_to 会一直滑动,直到它出现,然后你再操作它:

d.scroll_to("text", "Developer options")
d.touch("text", "Developer options")

d.scroll_to("text", "Airplane mode", direction="up")   # 向上查找
await d.scrollTo("text", "Developer options");
await d.touch("text", "Developer options");

await d.scrollTo("text", "Airplane mode", { direction: "up" });   // 向上查找
droidline scroll_to text "Developer options"
droidline touch text "Developer options"

滑动 20 次(max_swipes)后仍未找到,它就会放弃,并返回 NOT_FOUND。

弹窗、键盘和状态栏

默认情况下,dump 显示的是前台应用。弹出的对话框属于这个应用,会正常显示。要同时看到键盘、状态栏或由 Android 自己绘制的权限对话框等系统窗口,请要求获取所有窗口:

d.dump("all.json", all_windows=True)
await d.dump("all.json", { all_windows: true });
droidline dump all.json --all_windows

对于可能出现也可能不出现的弹窗,用条件检查,不要等待它们:

if d.exists("text", "Allow"):
    d.touch("text", "Allow")
if (await d.exists("text", "Allow")) await d.touch("text", "Allow");
[ "$(droidline exists text Allow)" = "true" ] && droidline touch text Allow

网页和 WebView

Chrome 中的页面和应用内的 WebView 通常也会把链接、按钮和表单字段作为元素提供出来,可见文字在 text 中,有时表单字段的标签在 desc 中。像对待其他界面一样对待它们:先 dump。如果网页在 dump 中没有任何有用的内容,说明它是在 canvas 上绘制的;请按下一节的说明使用坐标。

没有元素的界面

游戏、视频播放器、地图和一些自行绘制界面的应用完全不提供元素:dump 中只有一个大方框。在这些界面上,使用坐标和颜色:

d.tap(540, 1650)                         # 点击一个点,单位为屏幕像素
print(d.color(540, 1650))                # "#FF6B21":该点的颜色
d.swipe(540, 1600, 540, 400, 300)        # 在 300 毫秒内从一个点拖到另一个点
await d.tap(540, 1650);
console.log(await d.color(540, 1650));
await d.swipe(540, 1600, 540, 400, 300);
droidline tap 540 1650
droidline color 540 1650
droidline swipe 540 1600 540 400 300

从原始尺寸的截图中获取坐标:图片中的像素位置就是手机的屏幕像素。color 让你在点击之前确认按钮已经出现,例如等到某个像素变成预期的颜色。

选择器匹配不到时

touch 在超时后以 NOT_FOUND 失败,并告诉你当时屏幕上显示的是什么:

NOT_FOUND: Could not find text 'Log in' within 10s. Current screen: com.example / .MainActivity

按顺序检查以下几点:

  1. 是你预期的界面吗? 消息中写明了当时显示的应用和界面。弹窗、广告或很慢的网络可能把你带到了别处。
  2. 文字完全正确吗? 匹配要求完全一致,包括大小写、空格和标点。从 dump 中复制值,不要手动输入。如果其中一部分会变化,使用 textContains。
  3. 它已经在屏幕上了吗? 如果在屏幕下方还没显示出来,使用 scroll_to。如果还在加载,传入更长的 timeout。
  4. 它在另一个窗口中吗? 使用 all_windows=True 进行 dump。
  5. 这个界面有元素吗? 如果 dump 中只有一个大方框,就使用坐标。