1. 程式人生 > >史上最全的MonkeyRunner自動化測試從入門到精通(10)

史上最全的MonkeyRunner自動化測試從入門到精通(10)

三、MonkeyRunner複雜的功能開始學習
(1)獲取APK檔案中ID的兩種方式
Monkeyrunner的環境已經搭建完成,現在對Monkeyrunner做一個簡介。
Monkeyrunner工具提供了一套API讓使用者/測試人員來呼叫,呼叫這些api可以控制一個Android裝置或模擬器,而不需要了解對應的原始碼。
有了Monkeyrunner,我們可以編寫python指令碼來控制apk包的安裝和解除安裝、啟動app、向app傳送各種動作事件、擷取圖片並儲存。
除此之外,MonkeyRunner是Google提供的一個基於座標點的Android黑盒自動化測試工具。所以,要使用Monkeyrunner進行自動化測試,首先,要了解Monkeyrunner中獲取座標點的方式。

這裡寫圖片描述

本文中,我們主要介紹兩種獲取座標點的方式。一種是通過MonkeyRecorder獲取座標;另一種是通過HierarchyViewer工具獲取控制元件ID。

一、控制元件座標獲取
1.Pointer location獲取座標
先說一個比較簡單的獲取座標的方式,是通過模擬器中的設定-開發者選項,找到“指標位置”的選項,勾選上。如下圖所示。

這裡寫圖片描述

勾選後,模擬器的最頂部則顯示座標,比如點選模擬器上的任一應用,最頂部顯示X、Y的值即該應用的座標;同理,如果想要獲取任一應用中的任一位置的座標,也可用此方法。

這裡寫圖片描述

  1. MonkeyRecorder獲取座標
    下面就MonkeyRecorder獲取座標的方式,進行演示。MonkeyRecorder是一個比較好用的獲取座標的工具,它是用來獲取真機或模擬器上座標的工具,當我們點選真機或模擬器上的空間時,就能顯示真機或模擬器上的點選點的座標。

(1)MonkeyRecorder的啟動
首先安卓手機連線上電腦,並保證以下兩個條件成立:
a.終端USB調成開發者模式
b.電腦安裝手機驅動
手機連線成功後,開啟cmd視窗,輸入adb devices檢視已連線真機或模擬器裝置的名稱,我們這裡仍以模擬器為代表。
之後,在cmd視窗,輸入monkeyrunner後,啟動Monkeyrunner。做以下操作:匯入MonkeyRecorder包、連線模擬器裝置、以MonkeyRecorder方式啟動模擬器,並依次輸入
如下命令:

from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice

from com
.android.monkeyrunner.recorder import MonkeyRecorder as recorder device=MonkeyRunner.waitForConnection() recorder.start(device)

到此,MonkeyRecorder正式啟動。截圖如下。

這裡寫圖片描述

2)MonkeyRecorder的使用
我們這裡只是使用MonkeyRecorder來記錄座標,獲取座標的方式很簡單。比如qq的登入介面,點選“登入”按鈕,右側就會顯示該按鈕的座標;同樣,點選賬號輸入框或密碼輸入框,右側同樣會顯示座標。這個座標就是我們需要獲得的座標。

這裡寫圖片描述

同時,MonkeyRecorder中的介面是同模擬器頁面保持一致的,在MonkeyRecorder中觸發任一操作,模擬器上會有相應的觸發。如果兩者沒有保持一致,則點選MonkeyRecorder右上角的Refresh Display即可重新整理頁面。

這裡寫圖片描述

3.控制元件座標之Monkeyrunner指令碼演示
我們將下面一段Monkeyrunner指令碼寫到一個test.py檔案中,然後執行test.py檔案,檢視模擬器或真機上是不是做相應的操作。

from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice 

device=MonkeyRunner.waitForConnection() 

#啟動activity(這裡啟動qq)

device.startActivity(component="com.tencent.mobileqq/.activity.SplashActivity")

#登入介面,點選賬號輸入框

device.touch(60,300,'DOWN_AND_UP') 

#輸入qq賬號

device.type('3469191693')

二、控制元件ID獲取
通過控制元件ID實現自動化指令碼的執行,就效能而言,會比控制元件座標的實現差一些;但是對於不同解析度的裝置都通用,不需要動態變換座標。控制元件ID的獲取主要是通過HierarchyViewer。下面就HierarchyViewer從開啟方式和使用兩方面進行講解。
1.HierarchyViewer的開啟方式
HierarchyViewer的開啟方式有兩種:一種是eclipse中開啟HierarchyView檢視,另外一種是命令列中執行sdk/tools/hierarchyviewer.bat。
HierarchyViewer預設只能在非加密裝置使用,例如工程機,工程平板或者模擬器。如果要在手機上使用HierarchyViewer,你需要在你的應用中新增一個開源庫View Server。連結地址:https://github.com/romainguy/ViewServer。該篇文章中有講解如何啟動真機View Server,大家如果有興趣,可參考:https://dup2.org/node/1538
方式一:連線您的真機裝置,或開啟模擬器,在eclipse中, 依次選擇Window-Open Perspective-Other,在Other中,選擇HierarchyView檢視,即可開啟。

這裡寫圖片描述

這裡寫圖片描述

方式二:連線您的真機裝置或開啟模擬器,執行cmd視窗,進入到sdk/tools目錄下,輸入命令hierarchyviewer.bat,執行hierarchyviewer。

這裡寫圖片描述

或者直接在sdk/tools目錄下,找到hierarchyviewer.bat,雙擊執行。

這裡寫圖片描述

下面講解利用HierarchyViewer獲取控制元件ID的方法。
2.HierarchyViewer獲取控制元件ID
HierarchyViewer啟動後,首先會看到的第一個視窗顯示了裝置和模擬器的列表。點選左邊的箭頭,就會展開當前裝置或模擬器的Activity物件列表。列表中顯示了裝置或模擬器上,UI當前可視的所有Activity物件。這些物件按照它們的Android元件名稱列出來。列表中的內容包含應用的Activity物件和系統的Activity物件。
當模擬器activity畫面變更後,點選refresh可以載入新的頁面佈局資訊。

這裡寫圖片描述

從列表中選擇你的activity名稱,雙擊,或點選選單欄的Load View Hierarchy按鈕,進入View Hierarchy視窗,檢視它的view層次結構;或者點選Inspect Screenshot按鈕,進入Pixel Perfect視窗,從而檢視UI的一個放大影象。我們這裡點選進入View Hierarchy視窗。
可以從下圖中看到模擬器此activity的畫面佈局資訊,左邊部分是hierarchy通過樹形結構展示的佈局形式,右下角是模擬器上當前頁面的UI佈局資訊。

這裡寫圖片描述

通過滾動滑鼠,可以放大每個樹節點;拖拽滑鼠,移動樹形結構佈局。雙擊樹節點可以展示單獨的UI部分。從下圖中,可以看到,id/btn_login即為登入按鈕的ID。依次類推,可以檢視其它控制元件ID。
注:對於列表、或者彈出框則無法直接通過點選ID操作成功,需要計算ID的座標

這裡寫圖片描述

(2)Monkeyrunner之控制元件ID不存在或重複
我們在用monkeyrunner進行Android自動化時,通過獲取座標點或控制元件ID進行一系列操作。由於使用座標點時,螢幕解析度一旦更改,則程式碼中用到座標的地方都要修改,這樣導致程式碼的複用率較低。因此,我們多采用控制元件ID操作(注:控制元件ID需要在模擬器中使用,對於絕大多數真機不適用)。
但是,某些控制元件的ID是不存在的或重複存在,那麼,遇到這種情況,我們怎樣繼續使用控制元件ID進行自動化測呢?
例如,下圖中,我想要獲取最右側紅框中的id/tv,但是,大家會發現,和它並列的也有重複的控制元件id值。現在我們就講述一下這種情況(控制元件ID不存在同樣處理)。

這裡寫圖片描述

我們從這個控制元件樹的節點角度來思考如何獲得控制元件的引用。我們可以看到在上圖hierarchy viewer中的每個控制元件所對應的框形中,右下角都有一個數字。其實這個數字就是該控制元件在同級兄弟節點中的索引值,我們知道這個索引值後,就可以根據parentView.children[index]屬性來獲取任意父節點所對應的子節點的物件引用。其中的parentView可以是樹形圖中有效ID的任意父節點(父節點要保證唯一有效),然後利用python函式的可變引數列表特性來傳入所需控制元件的索引列表即可構造出得到任意節點引用的字串,從而得到其引用。
核心程式碼如下,把如下程式碼加入自己的python指令碼中,直接呼叫該函式即可。

#定義獲取重複或不存在控制元件id,尋找子節點函式
def getChildView(parentId, *childSeq):
    hierarchyViewer = device.getHierarchyViewer()
    childView="hierarchyViewer.findViewById('" + parentId +"')"
    for index in childSeq:
        childView += ('.children[' + str(index) + ']')
    print childView
    return eval(childView) 

#獲取id的文字
def getText(view):  
    if view != None:           
        return (view.namedProperties.get('text:mText').value)

有了以上程式碼之後,我們可以獲取上圖中的id/tv,方法如下:
getChildView(‘id/province_list’,5,0,0)
其中結合上圖可知,getChildView的第一個引數即:有效且唯一的父節點
引數二、三依次為要獲取的控制元件ID的父節點的父節點
注:用到的父節點即圖中的id/province_list,有效且唯一的值。當前的父節點右下角的角標,不需要在getChildView函式中顯示。
這樣,通過以上函式,再結合Hierarchyviewer圖形,我們獲取到了重複的控制元件ID。
由於Hierarchyviewer看起來不是特別方便,這裡再推薦一款和Hierarchyviewer類似功能的工具:uiautomatorviewer(儲存在sdk\tools中,雙擊開啟即可)

這裡寫圖片描述

由上圖中,uiautomatorviewer每個控制元件前面的數字即相當於Hierarchyviewer的角標,我們同樣可以獲取到目標ID的最終有效且唯一的父節點,從而呼叫函式getChildView(‘id/province_list’,5,0,0)
獲取到了不存在或重複的控制元件ID後,我們可以通過其座標,進行點選操作。
首先,定義一個“獲取指定按鈕座標”的函式

def getBtnPoint(btn):
    print btn
    point = device.getHierarchyViewer().getAbsoluteCenterOfView(btn);
    return point

然後通過座標,實現點選操作,例如:

askView = getChildView('id/tabs',1)
askPpoint = getBtnPoint(askView)
device.touch(askPpoint.x,askPpoint.y,'DOWN_AND_UP')

到這裡,我們介紹完了處理控制元件ID不存在或重複時的方法,自己實踐一把,就會更能體會

Hierarchyviewer/uiautomatorviewer+getChildView()獲取不存在或重複控制元件ID的用法。

(3)錄製指令碼與回放指令碼

錄製指令碼,recorder.py:
01.from com.android.monkeyrunner import MonkeyRunner as mr  
02.from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder  
03.device = mr.waitForConnection()  
04.recorder.start(device)  
回放指令碼,recorder_playback.py:
01.import sys  
02.from com.android.monkeyrunner import MonkeyRunner  
03.CMD_MAP = {  
04.    'TOUCH': lambda dev, arg: dev.touch(**arg),  
05.    'DRAG': lambda dev, arg: dev.drag(**arg),  
06.    'PRESS': lambda dev, arg: dev.press(**arg),  
07.    'TYPE': lambda dev, arg: dev.type(**arg),  
08.    'WAIT': lambda dev, arg: MonkeyRunner.sleep(**arg)  
09.    }  
10.  
11.# Process a single file for the specified device.   
12.def process_file(fp, device):  
13.    for line in fp:  
14.        (cmd, rest) = line.split('|')  
15.        try:  
16.            # Parse the pydict   
17.            rest = eval(rest)  
18.        except:  
19.            print 'unable to parse options'  
20.            continue  
21.  
22.        if cmd not in CMD_MAP:  
23.            print 'unknown command: ' + cmd  
24.            continue  
25.  
26.        CMD_MAP[cmd](device, rest)  
27.  
28.  
29.def main():  
30.    file = sys.argv[1]  
31.    fp = open(file, 'r')  
32.  
33.    device = MonkeyRunner.waitForConnection()  
34.      
35.    process_file(fp, device)  
36.    fp.close();  
37.  
38.if __name__ == '__main__':  
39.    main()

錄製操作:
在cmd下輸入monkeyrunner recorder.py,將開啟下面的視窗,該視窗的功能:
1、可以自動顯示手機當前的介面
2、自動重新整理手機的最新狀態
3、點選手機介面即可對手機進行操作,同時會反應到真機,而且會在右側插入操作指令碼
4:、wait: 用來插入下一次操作的時間間隔,點選後即可設定時間,單位是秒
Press a Button:用來確定需要點選的按鈕,包括menu、home、search,以及對按鈕的press、down、up屬性
Type Something:用來輸入內容到輸入框
Fling:用來進行拖動操作,可以向上、下、左、右,以及操作的範圍
Export Actions:用來匯出指令碼
Refresh Display:用來重新整理手機介面,估計只有在斷開手機後,重新連線時才會用到

這裡寫圖片描述

5.在左側手機檢視上做的任何操作,在右側都會進行指令碼錄製
上述例項錄製的操作指令碼如下:
TOUCH|{‘x’:187,’y’:356,’type’:’downAndUp’,}
PRESS|{‘name’:’MENU’,’type’:’downAndUp’,
WAIT|{‘seconds’:5.0,}
DRAG|{‘start’:(192,128),’end’:(192,640),’duration’:1.0,’steps’:10,}

回放操作:
回放:使用命令monkeyrunner recorder_playback.py record_test.py,其中record_test.py中是我們錄製的指令碼,這裡需要使用絕對路徑,即回放指令碼和錄製操作指令碼都必須在Monkeyrunner.bat所在目錄。
PS: 錄製後的指令碼可以進行二次更改,而且每一步操作需要有時間間隔,以保證測試的正確性

(4)MonkeyRunner常用事件

這裡給大家羅列下Monkeyrunner的常用事件,基本上這些事件都能夠滿足我們日常寫用例的需求了。

#monkeyrunner匯入模組

from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage

#monkeyrunner連線裝置

device = MonkeyRunner.waitForConnection()
if not device:
print "Please connect a device to start!"
else:
print "Start "
#monkeyrunner啟動一個Activity

componentName="com.ss.android.article.news/.activity.SplashActivity"

device.startActivity(component=componentName)

#monkeyrunner按鍵

傳送指定鍵的關鍵事件:  device.press(引數1:鍵碼, 引數2:觸控事件型別)

引數1:常用鍵內容

  按下HOME鍵 device.press('KEYCODE_HOME', MonkeyDevice.DOWN_AND_UP)

  按下BACK鍵 device.press('KEYCODE_BACK', MonkeyDevice.DOWN_AND_UP)

  按下下導航鍵 device.press('KEYCODE_DPAD_DOWN', MonkeyDevice.DOWN_AND_UP)

  按下上導航鍵 device.press('KEYCODE_DPAD_UP', MonkeyDevice.DOWN_AND_UP)

  按下OK鍵 device.press('KEYCODE_DPAD_CENTER', MonkeyDevice.DOWN_AND_UP)

  按下左導航鍵 device.press('KEYCODE_DPAD_LEFT', MonkeyDevice.DOWN_AND_UP)

  按下右導航鍵 device.press('KEYCODE_DPAD_RIGHT', MonkeyDevice.DOWN_AND_UP)

  相應的按鍵對應名稱:

  menu鍵:KEYCODE_MENU

  home鍵:KEYCODE_HOME

  back鍵:KEYCODE_BACK

  search鍵:KEYCODE_SEARCH

  call鍵:KEYCODE_CALL

  end鍵:KEYCODE_ENDCALL

  上音量鍵:KEYCODE_VOLUME_UP

  下音量鍵:KEYCODE_VOLUME_DOWN

  power鍵:KEYCODE_POWER

  camera鍵:KEYCODE_CAMERA

#monkeyrunner解除安裝包

device.removePackage ('com.example.android.notepad')

print ('解除安裝成功')

#monkeyrunner安裝包

device.installPackage('ApiDemos.apk')
print ('安裝成功')

#monkeyrunner單擊控制元件

方式1:device.touch(507,72,"DOWN_AND_UP")


方式2:easy_device.touch(By.id('id/qingchu'),device.DOWN_AND_UP)

用後者需要匯入

from com.android.chimpchat.hierarchyviewer import HierarchyViewer #根據ID找到ViewNode,對viewnode的一些操作等

from com.android.monkeyrunner.easy import EasyMonkeyDevice  #提供了根據ID進行訪問方法touch、drag等

from com.android.monkeyrunner.easy import By    #根據ID返回PyObject的方法

from com.android.hierarchyviewerlib.models import ViewNode as vn #代表一個控制元件,可獲取控制元件屬性


#monkeyrunner長按控制元件

方式1:device.touch(507,72,"DOWN_AND_UP")

device.touch(507,72,MonkeyDevice.DOWN)

MonkeyRunner.sleep(1)
device.touch(507,72,MonkeyDevice.UP)
方式2:
easy_device.touch(By.id('id/qingchu'),,MonkeyDevice.DOWN)
MonkeyRunner.sleep(1)
 easy_device.touch(By.id('id/qingchu'),MonkeyDevice.UP)

用後者需要匯入

from com.android.chimpchat.hierarchyviewer import HierarchyViewer #根據ID找到ViewNode,對viewnode的一些操作等

from com.android.monkeyrunner.easy import EasyMonkeyDevice  #提供了根據ID進行訪問方法touch、drag等

from com.android.monkeyrunner.easy import By    #根據ID返回PyObject的方法

from com.android.hierarchyviewerlib.models import ViewNode as vn #代表一個控制元件,可獲取控制元件屬性

#monkeyrunner滑動螢幕

for i in range(1,70):

device.drag((250,110),(250,850),0.1,10)

MonkeyRunner.sleep(1)


#monkeyrunner延時

MonkeyRunner.sleep(3)

#monkeyrunner截圖

result = device.takeSnapshot()

result.writeToFile('C:\\Users\\Martin\\Desktop\\test.png','png')

#monkeyrunner截圖對比

result1.sameAs(result0,1.0)


#monkeyrunner區域性圖片(前兩個值是左上角左邊,後兩個值是右下角減左上角的座標。)

pic0= result0.getSubImage((4,41,400,700))

#monkeyrunner重啟裝置

device.reboot()

#monkeyrunner單擊電源鍵,熄滅螢幕

device.press('KEYCODE_POWER',MonkeyDevice.DOWN_AND_UP)

#monkeyrunner喚醒螢幕

device.wake()

#monkeyrunner輸入文字

Cotent='1234'

device.type(Cotent)