UI自動化編寫規範(基於AirTest)

 UI自動化常用的那些方法和操作,做UI的基本都是通用的。

一、前言

說起UI自動化測試,尤其是移動端UI自動化,測試框架和技術層出不窮。經過多框架對比後,最終選擇了AirTest。

Airtest主要有以下優勢:

(1)UI自動化基於Airtest和PocoUI進行,該框架是網易開源框架,專業團隊開發維護,比較穩定。

(2)Airtest基於圖像識別算法,對圖片進行模版匹配和特徵點匹配,以此計算點擊座標,進行操作;

(3)PocoUI基於minitouch進行操作,可通過text/resouceid/name等進行元素定位。

(4)支持多平臺:安卓、IOS、Unity、小程序、H5

二、常用方法及推薦用法

1、前置用例引用

可以將一些通用的操作寫在一個.air腳本中,然後在其他腳本中import它。

  1. from airtest.core.api import using
  2. using("replace_install.air") # 需要使用相對路徑,不然找不到.air
  3. import replace_install

2、引用自定義的公共類或方法

在編寫自動化的過程中,有一些自定義的方法或類,需要在.air下的.py中引用時候,需要將項目路徑添加到環境變量中。

  1. import sys
  2. import os
  3. # 必填,將項目目錄添加到系統環境變量中
  4. sys.path.append(‘項目路徑’)

 

3、元素定位方式

(1)建議儘量使用text定位元素

poco(text='立即清理')

(2)如果不能直接定位,建議使用局部佈局

# 子元素
poco(text='main_node').child(text='list_item')
# 後代
poco(text='main_node').offspring(text='name')
# 父
poco(text='main_node').parent()
# 所有子元素
poco(text='main_node').children()
# 兄弟元素
poco(text='main_node').sibling(text='name')

備註:

  1. 因爲text變化的概率相對較小,所以建議使用text,且腳本易讀;
  2. resourceid不建議,因爲release版會混淆,除非公司對resourceid進行了統一設計和規劃,且release版本不混淆。

4、元素操作

(1)點擊元素的某一點

通過相對座標,控制點擊的具體位置。左上角(0, 0),右下角(1, 1),橫座標爲x,縱座標爲y。

node = poco(text='main_node')
# 點擊節點的中心點位置, 默認點擊中心位置
node.focus('center').click()
# 點擊節點的靠近左上角位置
node.focus([0.1, 0.1]).click()
# 點擊節點的右下角位置
node.focus([1, 1]).click()

(2)等待元素出現或消失

實際寫用例時,有一些掃描或緩衝場景,需要等待元素出現或消失,才能進行下一步操作。

# 當使用wait_for_appearance或wait_for_disappearance時,建議處理PocoTargetTimeout,並截圖,以方便在報告中查看出錯時的頁面情況
try:
    poco(text='main_node').wait_for_appearance(timeout=10)
    poco(text='main_node').wait_for_disappearance(timeout=10)
except PocoTargetTimeout:
    snapshot(msg="元素出現或未出現")

(3)滑動和拖動

# 拖動
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向給定方向和長度進行滑動

 

 

(4)獲取元素信息

"""
attribute name, it can be one of the following or any other customized type implemented by SDK
                - visible: whether or not it is visible to user
                - text: string value of the UI element
                - type: the type name of UI element from remote runtime
                - pos: the position of the UI element
                - size: the percentage size [width, height] in range of 0~1 according to the screen
                - name: the name of UI element
                - ...: other sdk implemented attributes
"""
poco(text="text_content").attr("checkable")
poco(text="text_content").get_position()
poco(text="text_content").get_text()

....

(5)連續滑動與自定義滑動操作

from airtest.core.api import *
dev = device()  # 獲取當前手機設備
# 手指按照順序依次滑過3個座標,可以用於九宮格解鎖
dev.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()

(6)點擊元素偏移位置

# 點擊, focus爲偏移值,sleep_interval爲點擊後的間隔時間
poco(text="立即清理").click(focus=(0.1, 0.1), sleep_interval=5)

(7)隱性等待元素

# 隱形等待元素出現,元素出現後,wait()方法結束
poco(text="立即清理").wait(timeout=5)

(8)判斷元素是否存在

# 判斷元素是否存在,存在返回True
poco(text="立即清理").exists()

(9)UI狀態清除

# 在poco裏選擇出來的ui都是代理對象,在執行同一個用例裏,一個ui控件選出來後能持續多長時間有效這個是要看android那回收ui資源的策略的,每個廠商的差異比較大.
# 對於cocos2d-x引擎的poco,由於使用的是快照模式,獲取到UI狀態後如果UI狀態確實發生了改變,需要調用ui.invalidate()進行重新獲取。
ui = poco(text="立即清理")
ui.click()
ui.invalidate()
ui.click()

(10)long click

# 長按操作
poco(text="立即清理").long_click(duration=2.0)

(11)兩指擠壓收縮操作

# 在給定的範圍和持續時間下,在UI上兩指擠壓收縮操作
poco.pinch(direction='in', percent=0.6, duration=2.0, dead_zone=0.1)

(12)根據UI滑動

# 根據UI的給定高度或寬度,滑動距離的百分比
# 從底部上滑5秒
poco.scroll(direction='vertical', percent=1, duration=5)
# 從頂部下滑5秒
poco.scroll(direction='vertical', percent=-1, duration=5)

三、常見異常

(1)InvalidOprationException

這個異常特指無效的操作,或者不起作用的操作

try:
    poco.click([1.1, 1.1])  # click outside screen
except InvalidOperationException:
    snapshot(msg="出現異常")

(2)PocoNoSuchNodeException

如果從一個不存在的UI空間讀取屬性或操作,就會出現該異常。

node = poco("not existed node")
try:
    node.click()
except PocoNoSuchNodeException:
    snapshot(msg="出現異常")


try:
    node.attr('text')
except PocoNoSuchNodeException:
    snapshot(msg="出現異常")

(3)PocoTargetTimeout

這個異常只會在你主動等待UI出現或消失時拋出,和 PocoNoSuchNodeException 不一樣,當你的操作速度太快,界面來不及跟着變化的話,你只會遇到 PocoNoSuchNodeException 而不是 PocoTargetTimeout ,其實就是在那個UI還沒有出現的時候就想要進行操作。

try:
    poco(text="demo").wait_for_appearance(timeout=10)
except PocoTargetTimeout:
    snapshot(msg="出現異常")

(4)PocoTargetRemovedException

如果操作速度遠遠慢於UI變化的速度,很可能會出現這個異常。當且僅當訪問或操作一個剛纔存在現在不在的UI元素時,纔會出現,並且一般不會出現。

try:
    poco(text="demo").click()
except PocoNoSuchNodeException:
    snapshot(msg="出現異常")

 

四、拓展用法

(1)滾動查找元素(poco_swipe_to)

滾動查找元素,當找到元素後,滑動元素到頁面中間。

用法:poco_swipe_to(text=None, textMatches=None, poco=None)

# 滾動查找元素
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

 (2)觀察者函數(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()

(3)等待任一元素出現(poco.wait_for_any)

poco.wait_for_any(),等待到任一元素出現,返回UIObjectProxy。

check_list = [poco(text="可清理"), poco(text = '手機很乾淨')]

poco.wait_for_any(check_list, timeout=20)

 

 (4)等待所有元素(poco.wait_for_all) 

poco.wait_for_all(),等待所有元素出現。

check_list = [poco(text="可清理"), poco(text = '手機很乾淨')]
poco.wait_for_all(check_list, timeout=20)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章