Pyhton3+AirTest+[004]+小程序UI自动化之常用的高级方法

  1. 元素定位

    1. 建议尽量使用 text 定位元素
# 定位一个元素
poco(text='选择门店')
# 如果text匹配多个元素,获取多个元素
ele_list=list(poco(text='选择门店').wait(5))
# 模糊定位,支持正则
poco(textMatches="'^门店.*$'")
  1. 如果不能使用text定位,常用局部定位
# 子元素
poco(name='com.tencent.mm:id/nb').child(text='选择门店')
# 后代
poco(name='com.tencent.mm:id/nb').offspring(text='选择门店')
# 父
poco(name='com.tencent.mm:id/nb').parent()
# 所有子元素
poco(name='com.tencent.mm:id/nb').children()
# 兄弟元素
poco(name='com.tencent.mm:id/nb').sibling(text='选择门店')
# 同样resourceid的元素列表
list(poco(name='com.tencent.mm:id/nb'))
  1. 元素定位

    1. 通过相对坐标,控制点击的具体位置。左上角 (0, 0),右下角 (1, 1),横坐标为 x,纵坐标为 y
po = poco(text='main_node')
# 点击节点的中心点位置, 默认点击中心位置
po.focus('center').click()
# 点击节点的靠近左上角位置
po.focus([0.1, 0.1]).click()
# 点击节点的右下角位置
po.focus([1, 1]).click()
  1. 等待元素的出现或者消失

实际写用例时,有一些扫描或缓冲场景,需要等待元素出现或消失,才能进行下一步操作

# 当使用wait_for_appearance或wait_for_disappearance时,建议处理PocoTargetTimeout,并截图,以方便在报告中查看出错时的页面情况
try:
poco(name='com.tencent.mm:id/nb').wait_for_appearance(timeout=10)
poco(name='com.tencent.mm:id/nb').wait_for_disappearance(timeout=10)
except PocoTargetTimeout:
snapshot(msg="元素出现或未出现")
  1. 滑动和拖动
# 拖动
poco('star').drag_to(poco('shell'))
# 滑动
poco('Scroll View').swipe([0, -0.1]) # 滑动指定坐标
poco('Scroll View').swipe('up') # 向上滑动
poco('Scroll View').swipe('down') # 向下滑动
 
# 向量滑动
x, y = poco('Scroll View').get_position()
end = [x, y - 0.1]
dir = [0, -0.1]
poco.swipe([x, y], end) # 从A点滑动到B点
poco.swipe([x, y], direction=dir) # 从点A向给定方向和长度进行滑动
  1. 获取元素信息
poco(name='com.tencent.mm:id/nb').attr("checkable")
poco(name='com.tencent.mm:id/nb').get_position()
poco(name='com.tencent.mm:id/nb').get_text()
  1. 连续滑动与自定义滑动操作
from airtest.core.api import *

 # 获取当前手机设备
android = device() 
# 手指按照顺序依次滑过3个坐标,可以用于九宫格解锁
android.minitouch.swipe_along([(100, 100), (200, 200), (300, 300)])



# 自定义操作
# 实现两个手指同时点击的操作
from airtest.core.android.minitouch import *

multitouch_event = [
    DownEvent((100, 100), 0),  # 手指1按下(100, 100)
    DownEvent((200, 200), 1),  # 手指2按下(200, 200)
    SleepEvent(1),
    UpEvent(0), UpEvent(1)]  # 2个手指分别抬起

device().minitouch.perform(multitouch_event)


# 三只滑动操作
from poco.utils.track import *

tracks = [
    MotionTrack().start([0.5, 0,5]).move([0.5, 0.6]).hold(1).
    MotionTrack().start([0.5, 0,5]).move([0.5, 0.6]).hold(1).
    MotionTrack().start([0.5, 0,5]).move([0.5, 0.6]).hold(1)
]
poco.apply_motion_tracks(tracks)

# 手势操作
# 点击ui1保持1秒,拖动到ui2并保持1秒,然后抬起
ui1.start_gesture().hold(1).to(ui2).hold(1).up()
  1. 点击元素偏移位置
# 点击, focus为偏移值,sleep_interval为点击后的间隔时间
poco(text="选择门店").click(focus=(0.1, 0.1), sleep_interval=5)
  1. 隐性等待元素
# 隐形等待元素出现,元素出现后,wait()方法结束
poco(text="选择元素").wait(timeout=5)
  1. 长按操作
# 长按操作
poco(text="选择门店").long_click(duration=2.0)
  1. 两指挤压收缩操作
# 在给定的范围和持续时间下,在UI上两指挤压收缩操作
poco.pinch(direction='in', percent=0.6, duration=2.0, dead_zone=0.1)
  1. 根据UI滑动
# 根据UI的给定高度或宽度,滑动距离的百分比
# 从底部上滑5秒
poco.scroll(direction='vertical', percent=1, duration=5)
# 从顶部下滑5秒
poco.scroll(direction='vertical', percent=-1, duration=5)
  1. 高级拓展

    1. 滚动查找元素 (poco_swipe_to),滚动查找元素,当找到元素后,滑动元素到页面中间
# 滚动查找元素
def poco_swipe_to(text=None, textMatches=None, poco=None):
    find_ele = False
    find_element = None
    if poco is None:
        raise Exception("poco is None")
    if text or textMatches:
        swipe_time = 0
        snapshot(msg="开始滚动查找目标元素")
        if text:
            find_element = poco(text=text)
        elif textMatches:
            find_element = poco(textMatches=textMatches)
        while True:
            snapshot(msg="找到目标元素结果: " + str(find_element.exists()))
            if find_element.exists():
                # 将元素滚动到屏幕中间
                position1 = find_element.get_position()
                x, y = position1
                if y < 0.5:
                    # 元素在上半页面,向下滑动到中间
                    poco.swipe([0.5, 0.5], [0.5, 0.5+(0.5-y)], duration=2.0)
                else:
                    poco.swipe([0.5, 0.5], [0.5, 0.5-(y-0.5)], duration=2.0)
                snapshot(msg="滑动元素到页面中间: " + str(text) + str(textMatches) )
                find_ele = True
                break
            elif swipe_time < 30:
                poco.swipe([0.5, 0.8], [0.5, 0.4], duration=2.0)
                # poco.swipe((50, 800), (50, 200), duration=500)
                swipe_time = swipe_time + 1
            else:
                break
    return find_ele
  1. 观察者函数 (watcher)
  • 说明:利用子进程对页面元素进行监控,发元素后,自动操作。
  • 适用场景:多用于不可预测的弹窗或元素
  • 用法:watcher(text=None, textMatches=None, timeout=10, poco=None)
def loop_watcher(find_element, timeout):
    """
    循环查找函数:每隔一秒,循环查找元素是否存在. 如果元素存在,click操作
    :param find_element: 要查找元素,需要是poco对象
    :param timeout: 超时时间,单位:秒
    :return:
    """
    start_time = time.time()
    while True:
        # find_element.invalidate()
        if find_element.exists():
            find_element.click()
            print("观察者:发现元素")
            break
        elif (time.time() - start_time) < timeout:
            print("--------------------观察者:等待1秒")
            time.sleep(1)
        else:
            print("观察者:超时未发现")
            break

def watcher(text=None, textMatches=None, timeout=10, poco=None):
    """
    观察者函数: 根据text或textMatches定位元素,用子进程的方式循环查找元素,直到超时或找到元素
    :param text: 元素的text
    :param textMatches: 元素的textMatches,正则表达式
    :param timeout: 超时时间
    :param poco: poco实例
    :return:
    """
    print("观察者:启动")
    # 目标元素
    find_element = None
    if poco is None:
        raise Exception("poco is None")
    if text or textMatches:
        if text:
            find_element = poco(text=text)
        elif textMatches:
            find_element = poco(textMatches=textMatches)

    # 定义子线程: 循环查找目标元素
    from multiprocessing import Process
    p = Process(target=loop_watcher, args=(find_element, timeout,))
    p.start()
  1. 等待任一元素出现wait_for_any()
poco.wait_for_any(),等待到任一元素出现,返回UIObjectProxy。
check_list = [poco(text="选择门店"), poco(text = '请输入门店/门店地址')]
poco.wait_for_any(check_list, timeout=20)
  1. 等待所有元素出现
poco.wait_for_all(),等待所有元素出现。
check_list = [poco(text="选择门店"), poco(text = '请输入门店/门店地址')]
poco.wait_for_all(check_list, timeout=20)
  1. 用 swipe_along() 画个圈圈
swipe_along 接口可以 实现连续划过一系列坐标 ,因此我们可以使用这个接口实现一些连续滑动的操作,比如手机屏幕的 滑动解锁 等。
from airtest.core.api import *
from airtest.core.android.rotation import XYTransformer
 
auto_setup(__file__)
 
# 获取当前手机设备
driver = device()
# 手指按照顺序依次滑过多个坐标
driver.swipe_along([[919, 418],[111, 564],[1014, 824],[711, 638],[915, 415]])
  1. 双指缩放操作
# 获取当前手机设备
driver = device()
# 向内捏合
driver.pinch(in_or_out='in', center=None, percent=0.5)
sleep(1.0)
 
# 向外捏合
driver.pinch(in_or_out='out', center=None, percent=0.2)
sleep(1.0)
 
driver.pinch(in_or_out='out', center=None, percent=0.2)
sleep(1.0)

  7. 其他操作

    7.1 所有UI相关的操作都默认以UI的 anchorPoint 为操作点,如果想自定义一个点那么可以使用 focus 方法。调用此方法将返回 新的 设置了默认 焦点 的UI,重复调用则以最后一次所调用的为准。focus 所使用的是局部坐标系,因此同样是UI包围盒的左上角为原点,x轴向右,y轴向下,并且包围盒长宽均为单位1。很显然中心点就是 [0.5, 0.5] 。下面的例子会展示一些常用的用法

poco('元素定位').focus('center').click()  # click the center

    7.2 将 focusdrag_to 结合使用还能产生卷动(scroll)的效果,下面例子展示了如何将一个列表向上卷动半页

item = poco(type='元素定位')
item.focus([0.5, 0.8]).drag_to(item.focus([0.5, 0.2]))

    7.3 在给定时间内等待一个UI出现并返回这个UI,如果已经存在画面中了那就直接返回这个UI。 如果超时了还没有出现,同样也会返回,但是调用这个UI的操作时会报错。类似的操作还有wait_for_appearance

poco('元素定位').wait(5).click()  # wait 5 seconds at most,click once the object appears
poco('元素定位').wait(5).exists()  # wait 5 seconds at most,return Exists or Not Exists

    7.4 点击click

poco.click([0.5, 0.5])  # click the center of screen
poco.long_click([0.5, 0.5], duration=3)

    7.5 截屏幕并以base64编码返回。截图的格式(png, jpg, …)由对应的sdk实现决定,大多数情况下是png。详见 ScreenInterface.getScreen

from base64 import b64decode
# 注意:在poco的某些引擎实现中不支持快照。
b64img, fmt = poco.snapshot(width=720)
open('screen.{}'.format(fmt), 'wb').write(b64decode(b64img))

    7.6 最简单的操作就是点击(click),也可以长按(long click),按多久都行

# coding=utf-8

from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

poco('元素定位').click()
poco('元素定位').click()
poco('元素定位').long_click()
poco('元素定位').long_click(duration=5)

  

    7.7 poco里的坐标的取值范围是相对于屏幕的,屏幕的宽和高都为单位1,因此也叫百分比坐标。当你需要和某个UI控件附近的UI控件交互或者要点击某个按钮的边缘而不是中间时,那可以用 局部定位

总的来说,和UI控件交互最终都是和坐标交互,例如点击一个按钮实际上就是点击某个坐标。 局部定位 就可以基于某个UI的左上角进行偏移,然后可以实现点击到这个UI控件内的各个坐标甚至UI外面的其他坐标。

import time
from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

image = poco('元素定位').child(type='Image')
image.focus('center').long_click()
time.sleep(0.3)
image.focus([0.1, 0.1]).long_click()
time.sleep(0.3)
image.focus([0.9, 0.9]).long_click()
time.sleep(0.3)
image.focus([0.5, 0.9]).long_click()
time.sleep(0.3)

    7.8 选中的UI外单击。通过它的名字标签点击一些模型是非常有用的

# coding=utf-8

from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

item = poco(text='元素text').focus([0.5, -3])
item.long_click()

    7.9 如何使用拖动来滚动列表

# coding=utf-8

import time
from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

listView = poco('元素定位')
listView.focus([0.5, 0.8]).drag_to(listView.focus([0.5, 0.2]))
time.sleep(1)

    7.10 下面例子展示了怎么样在商城界面中购买当前屏幕的所有商品

# coding=utf-8

poco = Poco(...)

bought_items = set()
for item in poco('元素定位').child('元素定位').offspring('元素定位'):
 
    item_name = item.get_text()

    if item_name not in bought_items:
        item.click()
        bought_items.add(item_name)

    7.11 一些复杂的测试用例中,不可能只是不断地主动控制或者读取属性。通过被动地获取UI状态改变的事件,这样有助于写出不混乱的测试脚本。Poco提供了简单的轮询机制去同时轮询1个或多个UI控件,所谓轮询就是依次判断UI是否存在。

下面例子展示的是最简单的UI同步

# coding=utf-8

from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

# start and waiting for switching scene
start_btn = poco('元素定位')
start_btn.click()
start_btn.wait_for_disappearance()

# waiting for the scene ready then click
exit_btn = poco('元素定位')
exit_btn.wait_for_appearance()
exit_btn.click()

    7.12 下面例子展示轮询UI时等待 任意一个 UI出现就往下走

# coding=utf-8

from poco.drivers.unity3d import UnityPoco
from poco.exceptions import PocoTargetTimeout

poco = UnityPoco()

bomb_count = 0
while True:
    blue_fish = poco('元素定位').child('元素定位')
    yellow_fish = poco('fish_emitter').child('yellow')
    bomb = poco('元素定位').child('元素定位')
    fish = poco.wait_for_any([元素1, 元素2, 元素3])
    if fish is bomb:
        # 跳过炸弹,数到3退出
        bomb_count += 1
        if bomb_count > 3:
            return
    else:
        # 否则点击鱼收集。
        fish.click()
    time.sleep(2.5)

    7.13 下面例子展示轮询UI时等待 所有 UI出现才往下走

# coding=utf-8

import time
from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()

poco(text='元素定位').click()

blue_fish = poco('元素定位').child('111')
yellow_fish = poco('元素定位').child('111')
shark = poco('fish_area').child('black')

poco.wait_for_all([111, 222, 333])
poco('444').click()
time.sleep(2.5)

    7.14 介绍一种加快UI操作速度的一种方法(即冻结UI),只是对于复杂的选择和UI遍历有效,如果只是简单的按名字选择请不要用这种方法,因为一点效果都没有冻结UI其实就是将当前界面的层次结构包括所有UI的属性信息抓取并存到内存里,在跟UI交互时就直接从内存里读取UI属性,而不用在发送rpc请求到game/app里去操作UI。好处就是一次抓取(消耗几百毫秒),可以使用多次,读取UI属性几乎不消耗时间,同时坏处就是,你需要手动处理UI同步,如果抓取了层次结构后,某个UI控件位置发生了变化,此时如果仍然点击这个UI的话,就会点击到原来的位置上,而不是最新的位置,这很容易导致奇怪的测试结果

下面两个例子分别展示使用了冻结UI和不使用冻结UI的效果区别

import time
from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()
with poco.freeze() as frozen_poco:
    t0 = time.time()
    for item in frozen_poco('1111').offspring(type='2222'):
        print item.get_text()
    t1 = time.time()
    print t1 - t0  # 大约6 ~ 8秒

不冻结

import time
from poco.drivers.unity3d import UnityPoco

poco = UnityPoco()
t0 = time.time()
for item in poco('1111').offspring(type='2222'):
    print item.get_text()
t1 = time.time()
print t1 - t0  # 约50 ~ 60 s