1. 程式人生 > 其它 >使用facebook-wda進行iOS APP自動化測試

使用facebook-wda進行iOS APP自動化測試

facebook-wda 是一個基於Python的測試庫,通過HTTP協議與WebDriverAgent通訊,本文介紹如何使用 facebook-wda 進行iOS APP自動化測試。

目錄

環境準備

使用 facebook-wda 之前需要滿足如下條件:

  1. 手機安裝 WebDriverAgent應用

    • 可以使用xcodebuild啟動WDA(需要MAC電腦)
    • 也可以使用tidevice啟動,它可以在Linux 、Windows和MAC上使用
  2. 電腦安裝facebook-wda:

pip3 install -U facebook-wda

在windows上搭建iOS自動化測試環境可參考:Windows上實現iOS APP自動化測試:tidevice + WebDriverAgent + facebook-wda / appium

本文使用tidevice啟動WDA:

$ tidevice list
List of apple devices attached
00008101-000255021E08001E iPhone12
$ 
$ tidevice -u [裝置 udid] wdaproxy -B [wda 的 bundle Id] --port 8100

可以使用weditor檢視UI元素,注意使用它之前要啟動WDA。

pip3 install -U weditor # 安裝

命令視窗輸入weditor,會自動開啟一個瀏覽器,選擇iOS。

環境準備好後就可以使用facebook-wda進行iOS APP自動化測試了。

初始化

全域性配置

import wda

wda.DEBUG = False # default False
wda.HTTP_TIMEOUT = 180.0 # default 180 seconds
wda.DEVICE_WAIT_TIMEOUT = 180.0

DEBUG設定為 True 時會顯示HTTP請求和響應資訊

>>> import wda
>>> wda.DEBUG = True
>>> wda.HTTP_TIMEOUT = 180.0
>>> wda.DEVICE_WAIT_TIMEOUT = 180.0
>>> c = wda.Client('http://localhost:8100')
>>> c.app_current()
Shell$ curl -X GET -d '' 'http://localhost:8100/wda/activeAppInfo'
Return (206ms): {
  "value" : {
    "processArguments" : {
      "env" : {

      },
      "args" : [

      ]
    },
    "name" : "",
    "pid" : 228,
    "bundleId" : "com.apple.Preferences"
  },
  "sessionId" : null
}
{'processArguments': {'env': {}, 'args': []}, 'name': '', 'pid': 228, 'bundleId': 'com.apple.Preferences'}
>>>
>>> wda.DEBUG = False
>>> c.app_current()
{'processArguments': {'env': {}, 'args': []}, 'name': '', 'pid': 228, 'bundleId': 'com.apple.Preferences'}
>>>

建立一個客戶端

import wda
c = wda.Client('http://localhost:8100')
c = wda.Client() # 讀取環境變數DEVICE_URL,如果沒有使用預設地址:http://localhost:8100

也可以使用USBClient連線裝置:

c = wda.USBClient() # 僅連線一個裝置可以不傳引數
c = wda.USBClient("00008101-000255021E08001E", port=8100) # 指定裝置 udid 和WDA 埠號
c = wda.Client("http+usbmux://{udid}:8100".format(udid="00008101-000255021E08001E")) # 通過DEVICE_URL訪問
c = wda.USBClient("00008101-000255021E08001E", port=8100, wda_bundle_id="com.facebook.WebDriverAgent.test2.xctrunner") # 1.2.0 引入 wda_bundle_id 引數

注:初始化連線裝置時不需要事先使用tidevice命令啟動WDA,wda.Client()會自動啟動WDA應用。

裝置操作

等待WDA服務正常

c.wait_ready(timeout=120, noprint=True) # 預設120s,安靜的等待,無進度輸出

鎖屏

>>> c.locked()
False
>>> c.lock()
{'value': None, 'sessionId': '4D60BDFB-5D18-4EFB-8C40-97E4826B9AAB', 'status': 0}
>>> c.unlock()
{'value': None, 'sessionId': '4D60BDFB-5D18-4EFB-8C40-97E4826B9AAB', 'status': 0}

回到手機主頁面

c.home()
c.press("home")

增大降低音量

c.press("volumeUp")
c.press_duration("volumeUp", 1) # 長按1s
c.press("volumeDown")
c.press_duration("volumeDown", 1) 

開啟、停止App

開啟APP

s = c.session('com.apple.Health') # 開啟app
s = c.session('com.apple.Health', alert_action="accept") 
s.close() # 關閉

c.session().app_activate("com.apple.Health") # 開啟app

停止APP:

c.session().app_terminate("com.apple.Health") # 關閉app

獲取app狀態:

>>> c.session().app_state("com.apple.Health") # 返回app狀態
{'value': 1, 'sessionId': 'BEB6A59E-254A-428A-AB53-F52A957ABE1F', 'status': 0}

  • 1:表示已關閉
  • 2:表示後臺執行
  • 4:表示正在執行

獲取裝置應用資訊

檢視裝置狀態資訊

>>> c.status()
{'message': 'WebDriverAgent is ready to accept commands', 'state': 'success', 'os': {'testmanagerdVersion': 28, 'name': 'iOS', 'sdkVersion': '14.5', 'version': '14.6'}, 'ios': {'ip': '192.168.5.159'}, 'ready': True, 'build': {'time': 'Jul 27 2021 20:50:24', 'productBundleIdentifier': 'com.facebook.WebDriverAgentRunner'}, 'sessionId': '4D60BDFB-5D18-4EFB-8C40-97E4826B9AAB'}

獲取應用資訊

當前應用資訊

>>> c.app_current()
{'processArguments': {'env': {}, 'args': []}, 'name': '', 'pid': 228, 'bundleId': 'com.apple.Preferences'}

獲取裝置資訊

裝置資訊

>>> c.device_info()
{'timeZone': 'GMT+0800', 'currentLocale': 'zh_CN', 'model': 'iPhone', 'uuid': '1DF5DFF9-93B2-4B87-8249-DB33ADB6A330', 'userInterfaceIdiom': 0, 'userInterfaceStyle': 'light', 'name': 'iPhone12', 'isSimulator': False}
>>>

電量資訊

>>> c.battery_info()
{'level': 0.9800000190734863, 'state': 2}

解析度:

>>> c.window_size()
Size(width=375, height=812)

UI元素定位

基本選擇器

通過屬性值定位:
id

s(id='URL')

className

s(className="XCUIElementTypeCell")

name

s(name='螢幕使用時間')
s(nameContains='螢幕')
s(nameMatches="螢幕.*")

value

s(value="螢幕使用時間")

label

s(label="螢幕使用時間")
s(labelContains="螢幕")

也可以組合多個屬性,可以組合的屬性包括:className、name、label、visible、enabled。

s(className="XCUIElementTypeCell",name="螢幕使用時間").click()

子元素

# 子元素定位
s(className='XCUIElementTypeTable').child(name='通知').exists

多個例項

返回所有匹配到的元素

$ s(nameContains='模式').find_elements()
[<wda.Element(id="15000000-0000-0000-0901-000000000000")>, <wda.Element(id="1B000000-0000-0000-0901-000000000000")>, <wda.Element(id="5E000000-0000-0000-0901-000000000000")>]

使用index來選擇匹配到的多個元素

s(nameContains='模式', index=2).click() # 點選匹配到的第3個元素
# 或者
s(nameContains='模式')[2].click()

XPath定位

s(xpath='//*[@name="螢幕使用時間"]')
# 或者
s.xpath('//*[@name="螢幕使用時間"]')

更多xpath語法可參考Web自動化測試:xpath & CSS Selector定位

Predicate定位

Predicate定位是iOS原生支援的定位方式,定位速度比較快,它可以通過使用多個匹配條件來準確定位某一個或某一組元素。

s(predicate='name BEGINSWITH "螢幕"').click() # 點選螢幕使用時間

更詳細的Predicate語法及定位示例可參考:iOS APP自動化:predicate定位

classChain定位

classChain是Predicate和Xpath定位的結合,搜尋效率比XPath更高。

s(classChain='**/XCUIElementTypeTable/*[`name == "通知"`]').click() # 點選【通知】

更詳細的使用方法參考:iOS APP自動化:class chain定位方法

獲取元素資訊

檢查元素是否存在

s(name="螢幕使用時間").exists # Return True or False

讀取UI元素的屬性資訊

ele = s(name="螢幕使用時間").get(timeout=3.0) # 如果10s內沒有找到,會丟擲WDAElementNotFoundError錯誤
ele = s(name="螢幕使用時間").wait(timeout=3.0)

# 獲取元素屬性
ele.className # XCUIElementTypeCell
ele.name # XCUIElementTypeCell
ele.visible # True 
ele.value 
ele.label # 螢幕使用時間
ele.text # 螢幕使用時間
ele.enabled # True 
ele.displayed # True 
ele.accessible

x, y, w, h = ele.bounds # Rect(x=0, y=744, width=375, height=46)

元素操作方法

點選

點選UI元素

s(name="螢幕使用時間").get(timeout=3.0).tap()
s(name="螢幕使用時間").tap()
s(name="設定").tap_hold(2.0) # 長按

s(name="螢幕使用時間").click()

s(text='螢幕使用時間').click_exists() # return True or False
s(text='螢幕使用時間').click_exists(timeout=5.0)

點選畫素座標

s = c.session('com.apple.Preferences')
s.tap(490, 1885) # 通過畫素座標點選

s.click(490, 1885) 
s.click(0.426, 0.716)

s.double_tap(490, 1885)  # 雙擊

文字輸入

文字值輸入與清除

ele = s(text='搜尋').get()

ele.set_text("NFC") # 輸入文字
ele.clear_text() # 清除文字
ele.set_text("\b\b\b\n") # 刪除3個字元
ele.set_text("NFC\n") # 輸入文字並確認

等待wait

設定隱式等待:

s.implicitly_wait(5) # 5s

設定超時時間

s.set_timeout(10.0)

等待元素

s(name="螢幕使用時間").wait(timeout=3.0) # 等待元素出現
s(name="螢幕使用時間").wait_gone(timeout=3.0) # 等待元素消失

Alert操作

對Alert彈框進行處理

>>> print(s.alert.exists)
True
>>> print(s.alert.text)
移除“設定”?
“從主螢幕移除”會將App保留在App資源庫中。

s.alert.accept() # 確認
s.alert.dismiss() # 取消
s.alert.wait(5) # 等待出現

s.alert.buttons() # 返回選項,例如:['取消', '從主螢幕移除']

s.alert.click("取消") # 點選取消
s.alert.click(["取消", "cancel"]) # 點擊出現的列表中的某個選項

也可以監控到Alert出現後進行操作:

with c.alert.watch_and_click(['好', '確定']):
	s(label="Settings").click() # 

滑動swipe

根據畫素座標滑動

c.swipe(fx, fy, tx, ty, duration=0.5) # 從(fx, fy)滑到(tx, ty),座標值可以是迅速值或者百分比,duration單位秒
c.swipe_left() # 向左滑動
c.swipe_right()
c.swipe_up()
c.swipe_down()

截圖

s.screenshot().save("test.png")
from PIL import Image
s.screenshot().transpose(Image.ROTATE_90).save("correct.png") # 橫屏截圖

裝置截圖

c.screenshot().save("screen.png")
c.press_duration("snapshot", 0.1) 

pytest + facebook-wda例項

測試步驟:

  1. 開啟【設定】
  2. 點選搜尋
  3. 輸入“NFC”
  4. 關閉NFC
  5. 斷言NFC是否關閉

Python程式碼:

#!/usr/bin/python3
# -*-coding:utf-8-*-

import wda
import time
wda.DEBUG = False # default False
wda.HTTP_TIMEOUT = 180.0 # default 180 seconds
wda.DEVICE_WAIT_TIMEOUT = 180.0

class TestDemo():
    def setup(self):
        self.c = wda.USBClient("00008101-000255021E08001E", port=8100)
        self.c.wait_ready(timeout=300)
        self.c.implicitly_wait(30.0)

    def teardown(self):
        self.c.session().app_terminate("com.apple.Preferences")

    def test_NFC(self):
        self.c.session('com.apple.Preferences')
        self.c.swipe_down()

        self.c(name="搜尋").set_text("NFC")  # 搜尋 NFC
        self.c(name="NFC").click()  # 點選NFC
        time.sleep(1)

        ele = self.c(xpath='//Switch').wait(timeout=3.0)
        sw = ele.value
        if sw == '1':
            ele.click()
            self.c.alert.wait(3)
            self.c.alert.click("關閉")
            time.sleep(1)
            sw = ele.value

        assert sw == '0'
--THE END--