进阶界面自动化

选择器 (Selector)

界面布局检视,首先你需要打开设备的 web 远程桌面。随后,点击远程桌面右上角的眼睛图标进入,此时你将不能再滑动左侧屏幕,你可以点击屏幕上的虚线框来查看对应元素的信息,你可以将其中的部分属性作为 Selector 的参数。 再次点击眼睛图标将关闭布局检视,布局检视并不会随着页面的改变而刷新,它始终是你按下快捷键那一刻的屏幕布局,如果需要刷新布局请手动按下快捷键 CTRL+R

正常情况下,我们只会使用 resourceId, clickable, text, description 作为参数。 如果元素存在正常的 resourceId,优先使用其作为 Selector,即:Selector(resourceId="com.android.systemui:id/mobile_signal_single")。 对于无 resourceId,则会使用其 text,即:Selector(text="点击进入"),或者更模糊一点 Selector(textContains="点击") description 与 text 同理,但是 description 用的会比较少。

当然,Selector 不止可以使用一个参数,你可以做其他组合,例如 Selector(text="点击进入", clickable=True),很少直接用 Selector(),大部分情况下,使用 d() 来进行。

所有常见的匹配参数

text                    文本完全匹配
textContains            文本包含匹配
textStartsWith          文本起始匹配
className               类名匹配
description             描述完全匹配
descriptionContains     描述包含匹配
descriptionStartsWith   描述起始匹配
clickable               可以点击
longClickable           可以长按
scrollable              可滚动
resourceId              资源ID匹配

大部分情况下,你不会直接用到 Selector,但是间接使用无处不在。

元素操作

上文都是介绍了如何坐标点击这种随意性的东西,现在开始介绍如何操作固定目标元素。首先,你需要知道如何选定元素。

# 选择界面上的包含文字 被测APP 的元素
element = d(textContains="被测APP")
# 当然,你不一定要这样赋值到 element,也可直接使用 d(textContains="被测APP")

好了,现在你知道了如何获取元素了,当然,这时并没有获取到,只是代表,你想要在当前界面操作这个元素,下面开始操作。

# 我们现在假设,界面上这个 被测APP 是手机上被测APP的图标名称(图标下面的名称)。
element = d(textContains="被测APP")
# 是否存在该元素
element.exists()
# 点击该元素,不存在则会抛出异常
# Corner.COR_CENTER 代表点击该元素中心点,你可查看 COR_CENTER 定义获取其他可点击的位置
element.click(corner=Corner.COR_CENTER)

# 点击该元素,不存在不会抛出异常
element.click_exists(corner=Corner.COR_CENTER)

# 长按该元素,不存在则会抛出异常
element.long_click(corner=Corner.COR_CENTER)

# 获取元素信息
element.info()

# 获取元素的中心点坐标
element.info().bounds.center()

# 获取元素的左上点坐标
element.info().bounds.corner("top-left")

# 获取元素的高度
element.info().bounds.height

# 获取元素的宽度
element.info().bounds.width

# 获取元素个数
element.count()

# 等待元素出现,最多等待10秒
element.wait_for_exists(10*1000)

# 等待元素消失,最多等待10秒
element.wait_until_gone(10*1000)

# 获取该元素的截图(不是全屏,只是该元素)
# quality 为截图质量 1-100
element.screenshot(quality=60)

# 将此 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 的操作


############################
# 现在 element 改变了其意义,变为选择了输入框
############################

# 示例为:在一加搜索应用界面的搜索框输入 被测APP

# 注意,不要直接往看似输入框的地方输入文字,可能并无法输入
# 有些输入框需要点击一次才会进入真正的输入框,请使用此真正输入框的资源ID
element = d(resourceId="net.oneplus.launcher:id/search_all_apps")
element.set_text("被测APP")

# 获取输入的内容
element.get_text()

# 清空刚刚输入的内容
element.clear_text_field()

# 配合点击搜索,来完成一次类人的搜索操作。


# 滑动操作(列表上下滑动翻页)
# 注意,这些操作并不保证精度,下面这些方法正常情况下都并不需要选择器,
# 但是你可根据实际情况自行加入选择器

# 向上滑动, step 自行调整,越多会越慢,比较适合精度要求较高的滑动
d().swipe(direction=Direction.DIR_UP, step=32)
# 其他滑动方向:
#DIR_UP     向上滑动
#DIR_LEFT   向左滑动
#DIR_DOWN   向下滑动
#DIR_RIGHT  向右滑动

#########
# fling:甩动,即正常人滑动屏幕的行为,较快
#########
# 从上向下
d().fling_from_top_to_bottom()
# 从下往上
d().fling_from_bottom_to_top()
# 从左往右
d().fling_from_left_to_right()
# 从右往左
d().fling_from_right_to_left()

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

监视器

监视器用来监听界面变化并在满足条件时执行设定的操作(点击元素或者按键),这可能对性能或者需要人工介入时产生影响,所以请谨慎使用,默认未开启。

# 启动监视器循环
d.set_watcher_loop_enabled(True)

# 获取监视器是否已启动
d.get_watcher_loop_enabled()

# 移除系统中应用的所有 watcher,建议每次使用前都执行防止前面任务注册的未删除影响正常处理流程
d.remove_all_watchers()

# 获取系统中所有已应用的 watcher 名称列表
d.get_applied_watchers()

# 彻底移除一个 watcher
d.remove_watcher(name)

# 应用watcher到系统中(当 watcher_loop 启动,此watcher将会生效)
d.set_watcher_enabled(name, True)
# 取消应用
d.set_watcher_enabled(name, False)

# 获取此 watcher 是否应用
d.get_watcher_enabled(name)

监视系统界面出现某个元素的次数

# 做一些测试前的清理,当然,并不是每 register 一个就需要这样
# 只是为了确保测试过程不被干扰
d.remove_all_watchers()
d.set_watcher_loop_enabled(True)

# 应用监视界面出现 好的 的次数
# 第二个参数为数组,可以给多个 Selector 表示条件都满足才会记录
# 但是不建议超过三个
d.register_none_op_watcher("RecordElementAppearTimes", [Selector(textContains="好的")])
d.set_watcher_enabled("RecordElementAppearTimes", True)

# ... 做满足条件的操作

# 获取记录的次数
d.get_watcher_triggered_count("RecordElementAppearTimes")

# 重置记录的次数
d.reset_watcher_triggered_count("RecordElementAppearTimes")

# 移除
d.remove_watcher("RecordElementAppearTimes")

当界面出现匹配元素时点击某个元素

# 做一些测试前的清理,当然,并不是每 register 一个就需要这样
# 只是为了确保测试过程不被干扰
d.remove_all_watchers()
d.set_watcher_loop_enabled(True)

# 示例为,当APP启动后出现用户协议时,自动点击同意
# 第二个参数为数组,可以给多个 Selector 表示条件都满足才会点击
# 但是不建议超过三个
d.register_click_target_selector_watcher("ClickAcceptWhenShowAggrement", [Selector(textContains="用户协议")],
                                         Selector(textContains="同意", clickable=True))
d.set_watcher_enabled("ClickAcceptWhenShowAggrement", True)

# ... 做满足条件的操作

# 移除
d.remove_watcher("ClickAcceptWhenShowAggrement")

当界面出现匹配元素时点击物理按键

# 做一些测试前的清理,当然,并不是每 register 一个就需要这样
# 只是为了确保测试过程不被干扰
d.remove_all_watchers()
d.set_watcher_loop_enabled(True)

# 示例为,当界面存在 个人中心 时,按下HOME键回到启动屏幕
# 第二个参数为数组,可以给多个 Selector 表示条件都满足才会点击
# 但是不建议超过三个
d.register_press_key_watcher("PressBackWhenHomePageShows", [Selector(textContains="个人中心")], Keys.KEY_HOME)
d.set_watcher_enabled("PressBackWhenHomePageShows", True)

# ... 做满足条件的操作

# 移除
d.remove_watcher("PressBackWhenHomePageShows")