幾個添加設備參數的例子
# 方法一:在auto_setup()接口添加設備 auto_setup(__file__,devices=["Android://127.0.0.1:5037/SJE5T17B17"]) # 方法二:用connect_device()方法連接設備 dev = connect_device("Android://127.0.0.1:5037/SJE5T17B17") # 方法三:用init_device()方法連接設備 init_device(platform="Android",uuid="SJE5T17B17")
過濾非嚴重級別的log
__author__ = "Airtest" import logging logger = logging.getLogger("airtest") logger.setLevel(logging.ERROR)
循環點擊某個list元素合集
for item in range(len(freeze_poco("List").child())): item = str(item) poco("NestStarReward").offspring(item).offspring("T").click()
凍結poco,使操作更快
freeze_poco = poco.freeze() # TODO:定義dongjiepoco
freeze_poco("TheExpDlg(Clone)").offspring(item).click()
判斷元素不存在則點擊
if not poco("TeamDlg(Clone)").offspring("Categories").offspring("item1").offspring("Text").exists(): poco("CurrentCategory").click()
判斷元素存在則點擊
if poco("TeamDlg(Clone)").offspring("Categories").offspring("item1").offspring("Text").exists(): poco("CurrentCategory").click()
在腳本中重置下條case的環境
try: finally:
判斷多個控件同時存在
with poco.freeze() as freeze_poco: if freeze_poco("DetailsBtn").exists() and \ freeze_poco("CurrentExp").exists() and \ freeze_poco("Chests").child("item0").exists() and \ freeze_poco("Chests").child("item1").exists() and \ freeze_poco("Chests").child("item2").exists() and \ freeze_poco("Chests").child("item3").exists() and \ freeze_poco("Chests").child("item4").exists() and \ freeze_poco("LeftView").child("Exp").exists() and \ freeze_poco("LeftView").child("Exp500").exists() and \ freeze_poco("LeftView").child("Exp1000").exists() and \ freeze_poco("LeftView").child("Exp500").child("box").exists() and \ freeze_poco("LeftView").child("Exp1000").child("box").exists() and \ freeze_poco("DailyActivityDlg(Clone)").offspring("RightView").offspring("Go").exists()
保證該控件在可點擊范圍方法
def butpos(butpos,pos1=0.4,pos2=0.81,high=1330,low=930,lows=482): """ 把不在屏幕內部的控件滑動到屏幕內,使之可被操作 :param butpos: 控件元素 :param pos1: 希望控件所在屏幕上的最低限 :param pos2: 希望控件所在屏幕上的最上限 :param high: 固定坐標 :param low: 滑動起始或終點位置 :param lows: 滑動起始或終點位置 :return: """ for i in range(50): but = butpos.get_position() if but[1] < pos1: swipe([high, lows], [high, low], 5) elif but[1] > pos2: swipe([high, low], [high, lows], 5) else: break
獲取手機屏幕大小
def _get_screen_size(devices): '獲取手機屏幕大小' size_str = os.popen(f'adb -s {devices} shell wm size').read() if not size_str: print('請安裝 ADB 及驅動並配置環境變量') sys.exit() m = re.search(r'(\d+)x(\d+)', size_str) if m: sizeheight = "{height}".format(height=m.group(1)) sizewidth = "{width}".format(width=m.group(2)) return int(sizeheight),int(sizewidth) return "1920x1080"
把print輸出成log
_print = print def print(*args, **kwargs): _print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), *args, **kwargs)
使用函數定義pcoo
def deviceconnect(devices): """ 用於poco實例化的公用方法 :param devices: 制定設備 :return: """ dev = connect_device("android:///" + devices) poco = UnityPoco(device=dev) return poco
所有UI相關的操作都默認以UI的 anchorPoint 為操作點,如果想自定義一個點那么可以使用 focus
方法。調用此方法將返回 新的 設置了默認 焦點 的UI,重復調用則以最后一次所調用的為准。focus
所使用的是局部坐標系,因此同樣是UI包圍盒的左上角為原點,x軸向右,y軸向下,並且包圍盒長寬均為單位1。很顯然中心點就是 [0.5, 0.5]
。下面的例子會展示一些常用的用法。
poco('bg_mission').focus('center').click() # click the center
將 focus
和 drag_to
結合使用還能產生卷動(scroll)的效果,下面例子展示了如何將一個列表向上卷動半頁。
scrollView = poco(type='ScollView') scrollView.focus([0.5, 0.8]).drag_to(scrollView.focus([0.5, 0.2]))
在給定時間內等待一個UI出現並返回這個UI,如果已經存在畫面中了那就直接返回這個UI。 如果超時了還沒有出現,同樣也會返回,但是調用這個UI的操作時會報錯。類似的操作還有wait_for_appearance
poco('bg_mission').wait(5).click() # wait 5 seconds at most,click once the object appears poco('bg_mission').wait(5).exists() # wait 5 seconds at most,return Exists or Not Exists
點擊(click)
poco.click([0.5, 0.5]) # click the center of screen poco.long_click([0.5, 0.5], duration=3)
滑動(swipe)
# swipe from A to B point_a = [0.1, 0.1] center = [0.5, 0.5] poco.swipe(point_a, center) # swipe from A by given direction direction = [0.1, 0] poco.swipe(point_a, direction=direction)
截屏幕並以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))
異常處理(Exceptions)
PocoTargetTimeout(控件出現超時)
from poco.exceptions import PocoTargetTimeout try: poco('guide_panel', type='ImageView').wait_for_appearance() except PocoTargetTimeout: # bugs here as the panel not shown raise
PocoNoSuchNodeException(沒有這個控件)
from poco.exceptions import PocoNoSuchNodeException img = poco('guide_panel', type='ImageView') try: if not img.exists(): img.click() except PocoNoSuchNodeException: # If attempt to operate inexistent nodes, an exception will be thrown pass
最簡單的操作就是點擊(click),也可以長按(long click),按多久都行,下面例子展示點擊和長按各自的效果。
# coding=utf-8 from poco.drivers.unity3d import UnityPoco poco = UnityPoco() poco('btn_start').click() poco('basic').click() poco('star_single').long_click() poco('star_single').long_click(duration=5)
如果從一個不存在的UI里讀取屬性值或者操作它,會觸發異常,如果你不確定那個UI是否存在,可以調用 .exists()
來判斷。在具體的測試用例中,如果因為UI不存在出現了異常,這很有可能是game/app的bug,不要強行忽略
# coding=utf-8 from poco.drivers.unity3d import UnityPoco poco = UnityPoco() btn = poco('btn_start') btn.click() print(btn.get_text()) # => 'Start' intro = poco('introduction') print(intro.get_text()) # => 'xxxx' print(intro.attr('text')) # => 'xxxx' print(intro.attr('type')) # => 'Text' print(intro.attr('texture')) # => None. Because there is no texture on Text. print(intro.attr('foo-bar')) # => None. Because "intro" dose not have an attribute named "foo-bar". intro.click() # Perform a click on any UI objects are allowed. obj = poco('foo-bar', type='FooBar') print(obj.exists()) # => False. This UI does not exist actually invisible_obj = poco('result_panel', type='Layer') print(invisible_obj.exists()) # => False. This UI is not visible to user.
Swipe滑動
# coding=utf-8 from poco.drivers.unity3d import UnityPoco poco = UnityPoco() # swipe the list view up poco('Scroll View').swipe([0, -0.1]) poco('Scroll View').swipe('up') # the same as above, also have down/left/right poco('Scroll View').swipe('down') # perform swipe without UI selected x, y = poco('Scroll View').get_position() end = [x, y - 0.1] dir = [0, -0.1] poco.swipe([x, y], end) # drag from point A to point B poco.swipe([x, y], direction=dir) # drag from point A toward given direction and length
在UI自動化測試中,最關鍵的就是將目標UI選擇出來。一般情況下,通過名字選擇是最簡單的方式,但是在一些情況下,並不是每個UI控件都有命名,特別是通過代碼動態生成和加載的UI,一般都沒有一個有意義的名字。Poco提供了強大有效的各種選擇方式,不僅可以通過UI單一的屬性選擇,還可以通過UI之間的層次關系和位置關系來選擇出目標UI。更強大的是,以上三種模式可以進行任意串聯或者組合使用,這樣基本上能應付所有情況了。
下面的例子將展示如何在復雜層次里選出對應的UI控件
# coding=utf-8 from poco.drivers.unity3d import UnityPoco poco = UnityPoco() items = poco('main_node').child('list_item').offspring('name'): first_one = items[0] print(first_one.get_text()) # => '1/2活力葯劑' first_one.click(
poco里的坐標的取值范圍是相對於屏幕的,屏幕的寬和高都為單位1,因此也叫百分比坐標。當你需要和某個UI控件附近的UI控件交互或者要點擊某個按鈕的邊緣而不是中間時,那可以用 局部定位 。
總的來說,和UI控件交互最終都是和坐標交互,例如點擊一個按鈕實際上就是點擊某個坐標。 局部定位 就可以基於某個UI的左上角進行偏移,然后可以實現點擊到這個UI控件內的各個坐標甚至UI外面的其他坐標。
# coding=utf-8 import time from poco.drivers.unity3d import UnityPoco poco = UnityPoco() image = poco('fish').child(type='Image') image.focus('center').long_click() time.sleep(0.2) image.focus([0.1, 0.1]).long_click() time.sleep(0.2) image.focus([0.9, 0.9]).long_click() time.sleep(0.2) image.focus([0.5, 0.9]).long_click() time.sleep(0.2)
也可以在選中的UI外單擊。通過它的名字標簽點擊一些模型是非常有用的
# coding=utf-8 from poco.drivers.unity3d import UnityPoco poco = UnityPoco() balloonfish_image = poco(text='balloonfish').focus([0.5, -3]) balloonfish_image.long_click()
下面的示例表明,focus是一個不會影響原始UI的不可變方法
# coding=utf-8 from poco.drivers.unity3d import UnityPoco poco = UnityPoco() # focus is immutable fish = poco('fish').child(type='Image') fish_right_edge = fish.focus([1, 0.5]) fish.long_click() # still click the center time.sleep(0.2) fish_right_edge.long_click() # will click the right edge time.sleep(0.2)
下面的示例演示如何使用拖動來滾動列表。
# coding=utf-8 import time from poco.drivers.unity3d import UnityPoco poco = UnityPoco() listView = poco('Scroll View') listView.focus([0.5, 0.8]).drag_to(listView.focus([0.5, 0.2])) time.sleep(1)
Poco提供了非常簡單的方式來處理一系列UI交互,直接用for循環進行迭代遍歷即可。在for循環中,每次迭代的對象都是一個UI代理,所以可以像之前說的那樣,去訪問對象的屬性和進行對象操作。
下面的示例展示了如何遍歷拖動
# coding=utf-8 import time from poco.drivers.unity3d import UnityPoco poco = UnityPoco() poco('btn_start').click() poco(text='drag drop').click() time.sleep(1.5) shell = poco('shell').focus('center') for star in poco('star'): star.drag_to(shell) time.sleep(1) assert poco('scoreVal').get_text() == "100", "score correct." # 這是另一種斷言方法 poco('btn_back', type='Button').click()
下面是另一個遍歷模型所有名稱的示例
# coding=utf-8 import time from poco.drivers.unity3d import UnityPoco poco = UnityPoco() for name in poco('plays').offspring('fish').child('name'): print(name.get_text()) # pearl/shark/balloonfish
下面例子展示了怎么樣在商城界面中購買當前屏幕的所有商品。
# coding=utf-8 poco = Poco(...) bought_items = set() for item in poco('main_node').child('list_item').offspring('name'): # get its text value item_name = item.get_text() # markdown the bought item if item_name not in bought_items: item.click() bought_items.add(item_name)
這個異常特指無效的操作,或者說不起作用的操作,例如點擊到了屏幕外面,或者對一個圖片設置text屬性(輸入框才能設置text屬性)。雖然這個異常並不會對應用造成什么實質性影響,但是還是要盡可能避免,以免測試腳本里邏輯混亂或測試結果不穩定。
# coding=utf-8 from poco.drivers.unity3d import UnityPoco from poco.exceptions import InvalidOperationException poco = UnityPoco() try: poco.click([1.1, 1.1]) # click outside screen except InvalidOperationException: print('oops')
如果從一個不存在的UI控件讀取屬性或控制它,那就會出現這個異常。測試一個UI控件是否存在可以調用UI代理的 .exists()
方法。
# coding=utf-8 from poco.drivers.unity3d import UnityPoco from poco.exceptions import PocoNoSuchNodeException poco = UnityPoco() node = poco('not existed node') # select永遠不會引發任何異常 try: node.click() except PocoNoSuchNodeException: print('oops!') try: node.attr('text') except PocoNoSuchNodeException: print('oops!') print(node.exists()) # => 假的。此方法不會引發
這個異常只會在你主動等待UI出現或消失時拋出,和 PocoNoSuchNodeException
不一樣,當你的操作速度太快,界面來不及跟着變化的話,你只會遇到 PocoNoSuchNodeException
而不是 PocoTargetTimeout
,其實就是在那個UI還沒有出現的時候就想要進行操作。
下面的例子展示測試腳本如何與UI保持同步,並處理 PocoTargetTimeout
異常
# coding=utf-8 from poco.drivers.unity3d import UnityPoco from airtest.core.api import connect_device from poco.exceptions import PocoTargetTimeout poco = UnityPoco() # UI is very slow poco('btn_start').click() star = poco('star') try: star.wait_for_appearance(timeout=3) # wait until appearance within 3s except PocoTargetTimeout: print('oops!') time.sleep(1)
與上面 PocoTargetTimeout
不同,如果操作速度遠遠慢於UI變化的速度,那*很可能*會出現這個異常。這個異常僅當去訪問或操作一個剛才還在但現在不在的UI控件才會出現,並且一般情況下基本不會出現。
下面例子展示點擊一個已經不存在的UI控件的效果
# coding=utf-8 from poco.exceptions import PocoTargetRemovedException, PocoNoSuchNodeException # 這個異常僅會在一些poco-sdk實現中,所以更可靠的做法是必要的情況下顯示地去調用.exists()
去判斷UI是否存在。
poco = Poco(...) start = poco('start') print(start.exists()) # => True. start.click() print(start.exists()) # => False try: start.click() except PocoTargetRemovedException: print('oops!') # IMPORTANT NOTE: # `start2` is different from `start` ! # `start` is tracking the UI at initial and it knows itself was removed but `start2` # does not know anything before. start2 = poco('start') try: start2.click() except PocoNoSuchNodeException: print('oops!')
這個異常僅會在一些poco-sdk實現中,所以更可靠的做法是必要的情況下顯示地去調用 .exists()
去判斷UI是否存在。
在poco.drivers.std。StdPoco,這個異常從未被提出!
# coding=utf-8 from poco.drivers.unity3d import UnityPoco from airtest.core.api import connect_device poco = UnityPoco() # no PocoTargetRemovedException case start = poco('start') print(start.exists()) # => True. start.click() print(start.exists()) # => False # 重要提示:在Unity3d軟件中,此操作將點擊與之前相同的坐標 # 不管發生什么 start.click()
一些復雜的測試用例中,不可能只是不斷地主動控制或者讀取屬性。通過被動地獲取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') start_btn.click() start_btn.wait_for_disappearance() # waiting for the scene ready then click exit_btn = poco('exit') exit_btn.wait_for_appearance() exit_btn.click()
下面例子展示輪詢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('fish_emitter').child('blue') yellow_fish = poco('fish_emitter').child('yellow') bomb = poco('fish_emitter').child('bomb') fish = poco.wait_for_any([blue_fish, yellow_fish, bomb]) if fish is bomb: # 跳過炸彈,數到3退出 bomb_count += 1 if bomb_count > 3: return else: # 否則點擊魚收集。 fish.click() time.sleep(2.5)
下面例子展示輪詢UI時等待 所有 UI出現才往下走
# coding=utf-8 import time from poco.drivers.unity3d import UnityPoco poco = UnityPoco() poco(text='wait UI 2').click() blue_fish = poco('fish_area').child('blue') yellow_fish = poco('fish_area').child('yellow') shark = poco('fish_area').child('black') poco.wait_for_all([blue_fish, yellow_fish, shark]) poco('btn_back').click() time.sleep(2.5)
介紹一種加快UI操作速度的一種方法(即凍結UI),只是對於復雜的選擇和UI遍歷有效,如果只是簡單的按名字選擇請不要用這種方法,因為一點效果都沒有凍結UI其實就是將當前界面的層次結構包括所有UI的屬性信息抓取並存到內存里,在跟UI交互時就直接從內存里讀取UI屬性,而不用在發送rpc請求到game/app里去操作UI。好處就是一次抓取(消耗幾百毫秒),可以使用多次,讀取UI屬性幾乎不消耗時間,同時壞處就是,你需要手動處理UI同步,如果抓取了層次結構后,某個UI控件位置發生了變化,此時如果仍然點擊這個UI的話,就會點擊到原來的位置上,而不是最新的位置,這很容易導致奇怪的測試結果
下面兩個例子分別展示使用了凍結UI和不使用凍結UI的效果區別
Freezing 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('Scroll View').offspring(type='Text'): print item.get_text() t1 = time.time() print t1 - t0 # 大約6 ~ 8秒
No Freezing UI
import time from poco.drivers.unity3d import UnityPoco poco = UnityPoco() t0 = time.time() for item in poco('Scroll View').offspring(type='Text'): print item.get_text() t1 = time.time() print t1 - t0 # 約50 ~ 60 s