界面进阶¶
本章节为您介绍更高级的自动化接口。您可以通过这些接口完成各种细致的操作。本章内容较多,如果您是第一次接触,我们建议您耐心地看完每个部分。
小技巧
编写自动化代码时,您可以直接在远程桌面的右侧终端输入命令 lamda,并在里面执行以下测试代码,或者自行进行元素选取、点击测试等,这样可以加快编写及验证的速度。
获取元素¶
您在基础知识或前文中可能已经对它有一些了解。您需要通过选择器查找到相关的元素才能进行操作。您应该也看到了在哪里可以获得选择器参数。接下来的介绍将围绕这个元素展开。您可以在这张图片的右侧看到“同意”这个元素的相关信息。

注意
您在左侧界面直接点击的元素可能并不是实际元素,因为它可能与其他元素大小及位置重叠。通常情况下,多个位置或大小重叠的元素会显示在右侧信息栏内,您可以上下滚动查看哪个才是真正需要的。您也可以通过在左侧选择界面按下 TAB 键的方式手动遍历所有元素。
对于以上元素,我们获取它一般是通过 text。使用 text 的条件是当前界面没有另一个元素的 text 也是“同意”,这是最简便的方法。其次您也可以选用 resourceId,不过您需要注意这里的 resourceId 代表的并不是唯一 ID,它代表的是资源 ID,而一个界面内可能包含很多个相同资源 ID 的元素。其他一些字段如 packageName、checkable 等一般不常用,但如果 text、resourceId、description 等都没有,可以尝试使用这些字段。我们可以通过如下几种方式获取到这个元素。
element = d(text="同意")
element = d(text="同意", resourceId="com.tencent.news:id/btm_first_agree")
element = d(resourceId="com.tencent.news:id/btm_first_agree")
元素点击¶
调用如下接口进行一次普通的元素点击操作,上下文中会实现手动点击“同意”的效果。
element.click()
如果您需要指定点击元素的位置,可以在调用点击接口时指定 corner 参数。例如,Corner.COR_CENTER 代表点击该元素中心点,您也可以点击它的左上角或右下角(Corner.COR_BOTTOMRIGHT)。
element.click_exists(corner=Corner.COR_TOPLEFT)
在该元素上执行长按操作,不存在则抛出异常。该接口也支持 corner,但无法指定长按时间。
element.long_click()
元素存在则点击,如果元素不存在,调用此接口不会引发异常。该接口同样支持 corner。
element.click_exists()
>>> element.click_exists()
True
是否存在¶
很多情况下,在进行进一步操作之前,需要对元素的存在性进行检查。否则后续流程可能会出现异常,甚至在错误的界面执行错误的操作。此时您可以使用如下接口进行存在性判断。
element.exists()
元素信息¶
某些情况下,您可能想要获取元素的部分信息,例如元素坐标、区域信息,或元素上的文本、描述等字符串信息。您可以通过如下接口来读取元素信息。
element.info()
对于我们上述的测试元素,输出的信息如下。
>>> info = element.info()
>>> print (info)
bounds { ... }
className: "android.widget.TextView"
clickable: true
enabled: true
focusable: true
packageName: "com.tencent.news"
resourceName: "com.tencent.news:id/btn_first_agree"
text: "\345\220\214\346\204\217"
visibleBounds { ... }
提示
您可能发现上面打印的信息中缺少某些字段,比如 description 等。这种情况通常表示该字段值为空或为 false,您仍然可以通过属性正常访问相关字段以获取其值。
可以看到这个信息有些复杂,这是 protobuf 默认的打印格式。您可以直接通过访问对应的属性来打印出其实际的值。比如想要读取元素 text 的值,您可以直接像下面这样使用。
>>> info = element.info()
>>> print (info.text)
同意
当然,里面还有一些元素区域坐标相关的信息,您也可以访问它们。比如您现在想要获取该元素对应的区域信息,您可以像以下方式打印区域信息,也可以将其保存为变量供后续操作使用。
>>> info = element.info()
>>> print (info.bounds)
输出或者返回的值是一个区域信息(Bounds),您会发现这也是某些截图接口会使用的参数。您可以将该参数传递给截图接口,实现对元素的单独截图。不过我们已为您封装了更简便的方法。
您可能也想要获取元素的宽度和高度来计算偏移量,例如计算其他元素的相对偏移。您可以使用:
>>> info = element.info()
>>> print (info.bounds.width, info.bounds.height)
484 138
或者获取该元素的中心点或角点,如左上角、右下角等。以下接口通常返回 Point 对象,您也可以从 Point 对象中获取对应的 X 和 Y 设备屏幕坐标。
>>> info = element.info()
>>> print (info.bounds.center())
x: 792
y: 1908
>>> print (info.bounds.center().x)
792
以下调用用来获取元素的角点坐标,示例获取了元素左上角的坐标,除此之外还支持获取 bottom-right、top-right、bottom-left 等共四个角的坐标。
>>> info = element.info()
>>> print (info.bounds.corner("top-left"))
x: 550
y: 1839
>>> print (info.bounds.corner("top-left").x)
550
元素遍历¶
您也可以遍历选择器选择到的所有元素。正常情况下,当前上下文中该选择器可能仅匹配到一个元素。如果您想测试遍历,请选择会匹配多个元素的选择器。您可以直接在选择器上使用 for 循环或其他方式进行遍历。
for i in element: print (i)
或者如果您知道存在多个匹配元素,想获得指定的第 N 个匹配元素,可以使用如下接口获取。
element_3rd = element.get(3)
元素计数¶
通常情况下您不会直接用到此接口。以下调用可以获取到您当前选择器匹配到的元素个数。
>>> element.count()
1
元素截图¶
我们支持元素级别的截图,可以单独截下元素的图像而无需全屏截图再行裁剪。
element.screenshot(quality=60)
您可以在截图后直接使用 getvalue() 方法获取截图的二进制数据,或者直接将其传入 PIL Image。
>>> element.screenshot(quality=60).getvalue()
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x02(ICC_PROFILE\x00\x01\x01\x00\x00\x02\x18\x00\x00\x00\x00\x02\x10\x00\x00mntrRGB XYZ \x00\x00...
或者如果您无需继续处理,也可以选择直接将截图保存到本地文件中。
>>> element.screenshot(quality=60).save("image.png")
等待元素¶
某些情况下,您可能需要判断当前页面是否加载完毕。通常可以通过判断相关元素是否已经出现来判断页面是否加载完成。以下示例会等待“同意”元素出现,最长等待时间为 10 秒。
提示
这里的等待时长是毫秒,所以 10 秒钟需要 *1000,10秒 = 10000毫秒。
element.wait_for_exists(10*1000)
>>> element.wait_for_exists(10*1000)
True
此外,我们还支持等待元素消失,即等待元素从界面消失。
element.wait_until_gone(10*1000)
>>> element.wait_until_gone(10*1000)
False
文字输入¶
文字输入是一个需要注意的地方。我们不可能往一个按钮上输入文字,因为那是按钮。现在我们重新选取一个输入框元素来介绍,这个元素的基本信息如下所示。

注意
获取输入框元素有一些需要注意的事项:请注意,在获取输入框元素时,您的输入法必须处于弹出状态,然后再查找相关元素,并且建议仔细查找,否则您获取的可能并不是真正的输入框。
提示
在自动化流程中,让输入法处于弹出状态仅仅需要您在代码中先行点击父容器中显示的输入框即可。
对于上面这个输入框,我们可以调用如下接口向其中输入“你好世界”这个字符串。您也可以输入英文或其他 Unicode 字符串,只需如下使用即可在框内输入文字。
>>> element = d(text="搜索感兴趣的内容")
>>> element.set_text("你好世界")
True
如果您想要获取该输入框当前显示的文字内容,可以这样调用。
注意
请注意,这里我们更换了选择器。初始选择器使用了 text 属性,但输入文本后,元素内容发生变化,导致原选择器不再匹配,因此我们更换了另一个选择器。选择合适的选择器很重要,但此示例仅作演示,姑且如此。
>>> element = d(className="android.widget.EditText")
>>> element.get_text()
'你好世界'
您也可以清空当前输入的内容。通常输入文字时会自动清空原有文字,但您也可以手动清空。
提示
连续使用按键接口循环按下 BACKSPACE 键也可以实现类似的效果。
>>> element = d(className="android.widget.EditText")
>>> element.clear_text_field( )
True
备注
极端情况下,有些地方无法正常使用此接口输入文字,我们正在支持。
普通滑动¶
使用如下接口进行界面的滑动操作,例如列表的上下滑动翻页。以下调用实现向上滑动,step 值越大,滑动速度越慢,比较适合精度要求较高的滑动。
注意
在简单情况下,此操作无需提供选择器参数。如果遇到无法滑动的情况,请自行设置选择器条件为适合的元素,例如具有 scrollable 属性的元素或列表的第一级容器。
d().swipe(direction=Direction.DIR_UP, step=32)
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.swipe(direction=Direction.DIR_UP, step=32)
True
快速滑动¶
快速滑动类似于人快速滑动的行为,此操作会快速地滑动屏幕,适合模拟快速浏览类的操作。以下示例从上往下滑动屏幕,示例中选择器为空,您仍然需要根据实际情况选择是否填写选择器。
d().fling_from_top_to_bottom()
从下往上的快速滑动:
d().fling_from_bottom_to_top()
从左往右的快速滑动:
d().fling_from_left_to_right()
从右往左的快速滑动:
d().fling_from_right_to_left()
注意
在简单情况下,此操作无需提供选择器参数。如果遇到无法滑动的情况,请自行设置选择器条件为适合的元素,例如具有 scrollable 属性的元素或列表的第一级容器。
>>> element = d(resourceId="com.tencent.news:id/important_list_content")
>>> element.fling_from_bottom_to_top()
True
元素拖拽¶
将元素拖动到另一元素所在位置(如将 App 图标拖入文件夹)。
element.drag_to(Selector(text="购物")) # 拖到目标元素所在位置
子级与同级元素¶
重复或无明显特征的元素,可先定位父容器,再用 child 取子元素、sibling 取同级元素缩小范围。
form = d(resourceId="login_form") # 定位父容器
form.child(index=1) # 取 index 为 1 的子元素
form.child(index=1).sibling() # 取该子元素的同级元素
form.sibling(textContains="找回密码") # 取与 form 同级的元素
快速滑动至边界¶
持续快速滑动直至无法继续;未必能检测到已到底,须指定 max_swipes。
d().fling_from_top_to_bottom_to_end(max_swipes=32) # 向下快速滑至边界
d().fling_from_bottom_to_top_to_end(max_swipes=32) # 向上
d().fling_from_left_to_right_to_end(max_swipes=32) # 向右
d().fling_from_right_to_left_to_end(max_swipes=32) # 向左
匀速滑动¶
按固定步长 step 滑动,比 swipe 更机械,适合需要稳定步进的场景。
d().scroll_from_top_to_bottom(step=60) # 向下
d().scroll_from_bottom_to_top(step=60) # 向上
d().scroll_from_left_to_right(step=60) # 向右
d().scroll_from_right_to_left(step=60) # 向左
匀速滑动至边界¶
与快速滑动至边界类似,以匀速滑动代替 fling,同样须指定 max_swipes 与 step。
d().scroll_from_top_to_bottom_to_end(max_swipes=32, step=60) # 向下滑至边界
d().scroll_from_bottom_to_top_to_end(max_swipes=32, step=60) # 向上
d().scroll_from_left_to_right_to_end(max_swipes=32, step=60) # 向右
d().scroll_from_right_to_left_to_end(max_swipes=32, step=60) # 向左