Pyhton3+AirTest+[004]+小程序UI自动化之常用的高级方法
-
元素定位
- 建议尽量使用 text 定位元素
# 定位一个元素 poco(text='选择门店') # 如果text匹配多个元素,获取多个元素 ele_list=list(poco(text='选择门店').wait(5)) # 模糊定位,支持正则 poco(textMatches="'^门店.*$'")
- 如果不能使用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'))
-
元素定位
- 通过相对坐标,控制点击的具体位置。左上角 (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()
- 等待元素的出现或者消失
# 当使用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="元素出现或未出现")
- 滑动和拖动
# 拖动 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向给定方向和长度进行滑动
- 获取元素信息
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()
- 连续滑动与自定义滑动操作
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()
- 点击元素偏移位置
# 点击, focus为偏移值,sleep_interval为点击后的间隔时间 poco(text="选择门店").click(focus=(0.1, 0.1), sleep_interval=5)
- 隐性等待元素
# 隐形等待元素出现,元素出现后,wait()方法结束 poco(text="选择元素").wait(timeout=5)
- 长按操作
# 长按操作 poco(text="选择门店").long_click(duration=2.0)
- 两指挤压收缩操作
# 在给定的范围和持续时间下,在UI上两指挤压收缩操作 poco.pinch(direction='in', percent=0.6, duration=2.0, dead_zone=0.1)
- 根据UI滑动
# 根据UI的给定高度或宽度,滑动距离的百分比 # 从底部上滑5秒 poco.scroll(direction='vertical', percent=1, duration=5) # 从顶部下滑5秒 poco.scroll(direction='vertical', percent=-1, duration=5)
-
高级拓展
- 滚动查找元素 (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
- 观察者函数 (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()
- 等待任一元素出现wait_for_any()
poco.wait_for_any(),等待到任一元素出现,返回UIObjectProxy。 check_list = [poco(text="选择门店"), poco(text = '请输入门店/门店地址')] poco.wait_for_any(check_list, timeout=20)
- 等待所有元素出现
poco.wait_for_all(),等待所有元素出现。 check_list = [poco(text="选择门店"), poco(text = '请输入门店/门店地址')] poco.wait_for_all(check_list, timeout=20)
- 用 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]])
- 双指缩放操作
# 获取当前手机设备 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 将 focus
和 drag_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