界面进阶操作

本章节为您介绍更加深层的自动化接口,您可以通过这些接口来完成各种细致的操作,本章的内容较多,如果您是第一次接触,我们建议您耐心的看完每个节点。

小技巧

编写自动化代码时,您可以直接在远程桌面的右侧终端输入命令 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 代表点击该元素中心点,您也可以点击他的左上角或者右下角(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)

输出或者返回的值是一个区域信息(Bound),您后面会发现这是某个截图接口也会用到的参数,是的,您可以将这个参数提交给截图接口来实现给这个元素单独截图,不过我们早已为您封装好了。

您可能也想要获取到元素的宽度和高度用来计算某些偏移,比如计算其他相对元素的偏移,您可以。

>>> 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-righttop-rightbottom-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 进行的,但我们输入 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
方向指示 说明
Direction.DIR_UP 向上滑动
Direction.DIR_LEFT 向左滑动
Direction.DIR_DOWN 向下滑动
Direction.DIR_RIGHT 向右滑动

快速滑动

快速滑动类似人快速滑动屏幕的行为,此操作会快速的滑动屏幕,适合模拟快速浏览类的操作。以下示例从上往下滑动屏幕,示例中选择器为空,您仍然需要根据时间情况选择是否填写选择器。

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="购物"))

#########
# 查找同级或者子级元素
#########
# 有时候会有一些重复元素或者无明显特征的元素,很难去定位
# 这时你可以通过查找子级/同级元素的方法来缩小查找范围
# 子级元素,举例为:一个聊天登录框,里面的输入框即为登录框的子级元素
# 同级元素,举例为:聊天输入框里面的用户名和密码框为同级原始(正常情况下)
form = d(resourceId="login_form")
form.child(index=1)
# 这将获取到 login_form 下 index 为 0 的元素
form.child(index=1).sibling()
# 你也这样来找与 login_form 同级的找回密码按钮
#(其实已经可以通过字符串判断了,就不需要这样做了,这里只是演示)
form.sibling(textContains="找回密码")
# 它们本身就是一个element,你可以对其做任何 element 的操作


# 其他,一直向下/左右上滑,直到滑动到底
# 因为并不是一定可以滑动到底或者检测到滑动到底
# 所以 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)

#########
# scroll: 比较机械性的滑动
#########
step = 60
max_swipes = 32
# 从上往下滑动 step 步
d().scroll_from_top_to_bottom(step)
# 从下往上滑动 step 步
d().scroll_from_bottom_to_top(step)
# 从左往右滑动 step 步
d().scroll_from_left_to_right(step)
# 从右往左滑动 step 步
d().scroll_from_right_to_left(step)

# 其他,一直向下/左右上滑,直到滑动到底
# 同上文 fling 描述
d().scroll_from_top_to_bottom_to_end(max_swipes, step)
d().scroll_from_bottom_to_top_to_end(max_swipes, step)
d().scroll_from_left_to_right_to_end(max_swipes, step)
d().scroll_from_right_to_left_to_end(max_swipes, step)