您好,登錄后才能下訂單哦!
使用SDK自帶的NotePad應(yīng)用作為實踐目標應(yīng)用,目的是對MonkeyDevice擁有的成員方法做一個初步的了解。
以下是官方列出的方法的Overview。
Return Type | Methods | Comment |
void | broadcastIntent (string uri, string action, string data, string mimetype, iterable categories dictionary extras, component component, iterable flags) Broadcasts an Intent to this device, as if the Intent were coming from an application. |
|
void | drag (tuple start, tuple end, float duration, integer steps) Simulates a drag gesture (touch, hold, and move) on this device's screen. |
|
object | getProperty (string key) Given the name of a system environment variable, returns its value for this device. The available variable names are listed in the detailed description of this method. |
|
object | getSystemProperty (string key) . The API equivalent of adb shell getprop <key>. This is provided for use by platform developers. |
|
void | installPackage (string path) Installs the Android application or test package contained in packageFile onto this device. If the application or test package is already installed, it is replaced. | Obsolete,返回值是Boolean |
dictionary | instrument (string className, dictionary args) Runs the specified component under Android instrumentation, and returns the results in a dictionary whose exact format is dictated by the component being run. The component must already be present on this device. |
|
void | press (string name, dictionary type) Sends the key event specified by type to the key specified by keycode. |
|
void | reboot (string into) Reboots this device into the bootloader specified by bootloadType. |
|
void | removePackage (string package) Deletes the specified package from this device, including its data and cache. | Obsolete,返回值是Boolean |
object | shell (string cmd) Executes an adb shell command and returns the result, if any. |
|
void | startActivity (string uri, string action, string data, string mimetype, iterable categories dictionary extras, component component, flags) Starts an Activity on this device by sending an Intent constructed from the supplied arguments. |
|
MonkeyImage | takeSnapshot() Captures the entire screen buffer of this device, yielding a MonkeyImage object containing a screen capture of the current display. |
|
void | touch (integer x, integer y, integer type) Sends a touch event specified by type to the screen location specified by x and y. |
|
void | type (string message) Sends the characters contained in message to this device, as if they had been typed on the device's keyboard. This is equivalent to callingpress() for each keycode in message using the key event type DOWN_AND_UP. |
|
void | wake () Wakes the screen of this device. |
|
其實官方這個表是沒有及時更新的,我現(xiàn)在手頭上用到的MonkeyRunner是當前最新的,里面就擁有好幾個官網(wǎng)沒有列出來的API,我懷疑是不是自從UIAutomator在03年出來后,google就不打算再繼續(xù)維護MonkeyRunner了?如果有朋友知道事實的話,還麻煩告知。
以下是我整理出來的源碼多出來的可用公共API列表
Return Type | Methods | Comment |
HierarchyViewer | getHierarchyViewer(PyObject args[], String kws[]) 獲取一個HierarchyViewer對象 | 請查看《MonkenRunner通過HierarchyViewer定位控件的方法和建議》 |
PyList | getPropertyList(PyObject args[], String kws[]) | 取得所有的property屬性鍵值 |
PyList | getViewIdList(PyObject args[], String kws[]) | Failed |
MonkeyView | getViewById(PyObject args[], String kws[]) | Failed |
MonkeyView | getViewByAccessibilityIds(PyObject args[], String kws[]) | Failed |
MonkeyView | getRootView(PyObject args[], String kws[]) | Failed |
PyList | getViewsByText(PyObject args[], String kws[]) | Failed |
但可惜的是在本人嘗試以上多出來的API的時候,發(fā)現(xiàn)除了最上面兩個可用之外,其他的都不可用并拋出錯誤。且網(wǎng)上資料少的可憐,別人碰到同樣的問題也找不到解決辦法。所以本人懷疑這些“隱藏”API是不是并沒有完善,或者說google不準備完善,所以才沒有列出到官網(wǎng)上面去。本人用的SDK tools和Platform tools已經(jīng)是當前最新的23.0.2和20.
一個臺灣網(wǎng)友碰到的問題描述:http://imsardine.simplbug.com/note/monkeyrunner/api/hierarchy-viewer.html
(string uri, string action,string data, string mimetype, iterable categories dictionary extras, componentcomponent, iterable flags)
本人理解的此方法的本意是想廣播一個Intent給我們的AndroidDevice,目標應(yīng)用接收到該Intent做相應(yīng)的處理,比如打開一個Activity等。但在我的多次嘗試下并沒有成功!
targetDevice.broadcastIntent(action='android.intent.action.INSERT', mimetype='vnd.android.cursor.dir/contact', extras = {'name':'user1501488', 'phone':'123-15489'}
如果使用同樣的參數(shù),使用下面的startActivity是沒有問題的。
targetDevice.startActivity(action='android.intent.action.INSERT', mimetype='vnd.android.cursor.dir/contact', extras = {'name':'user1501488', 'phone':'123-15489'})
google了半天網(wǎng)上根本找不到這個方法的使用例子,倒是stackOverFlow上有人建議用Shell來達到同樣的效果。
targetDevice.shell("am start -a android.intent.action.INSERT -t vnd.android.cursor.dir/contact -e name 'Donald Duck' -e phone 555-1234").
所以可見這個方法并沒有多少人在用,原因應(yīng)該是它完全可以用上面介紹的兩個方法替代。
(string uri, string action, string data, string mimetype, iterable categories dictionary extras, component component, flags)
targetDevice.startActivity(action='android.intent.action.VIEW', mimetype='vnd.android.cursor.dir/vnd.google.note')
targetDevice.startActivity(component="com.example.android.notepad/com.example.android.notepad.NotesList")
targetDevice.startActivity(action='android.intent.action.INSERT', mimetype='vnd.android.cursor.dir/contact', extras = {'name':'user1501488', 'phone':'123-15489'})
這個方法的目的是按住一個控件然后把她拖動到其他位置
viewer = targetDevice.getHierarchyViewer() note = viewer.findViewById('id/text1') point = viewer.getAbsoluteCenterOfView(note) startX = point.x startY = point.y targetDevice.drag((startX,startY),(startX,startY),1) targetDevice.press('KEYCODE_BACK', MonkeyDevice.DOWN_AND_UP)
以上示例是通過drag的方法來模擬LongPress,只要把參數(shù)中的起始坐標和目標坐標都設(shè)置成同樣一個值,然后時常設(shè)置成一個有效的值就好了。
通過環(huán)境變量的key來獲得其對應(yīng)的值。以下是官方提供的可用化境變量列表
Property Group | Property | Description | Notes |
build | board | Code name for the device's system board | See Build |
brand | The carrier or provider for which the OS is customized. |
| |
device | The device design name. |
| |
fingerprint | A unique identifier for the currently-running build. |
| |
host |
|
| |
ID | A changelist number or label. |
| |
model | The end-user-visible name for the device. |
| |
product | The overall product name. |
| |
tags | Comma-separated tags that describe the build, such as "unsigned" and "debug". |
| |
type | The build type, such as "user" or "eng". |
| |
user |
|
| |
CPU_ABI | The name of the native code instruction set, in the form CPU type plus ABI convention. |
| |
manufacturer | The product/hardware manufacturer. |
| |
version.incremental | The internal code used by the source control system to represent this version of the software. |
| |
version.release | The user-visible name of this version of the software. |
| |
version.sdk | The user-visible SDK version associated with this version of the OS. |
| |
version.codename | The current development codename, or "REL" if this version of the software has been released. |
| |
display | width | The device's display width in pixels. | SeeDisplayMetricsfor details. |
height | The device's display height in pixels. |
| |
density | The logical density of the display. This is a factor that scales DIP (Density-Independent Pixel) units to the device's resolution. DIP is adjusted so that 1 DIP is equivalent to one pixel on a 160 pixel-per-inch display. For example, on a 160-dpi screen, density = 1.0, while on a 120-dpi screen, density = .75. The value does not exactly follow the real screen size, but is adjusted to conform to large changes in the display DPI. See density for more details. |
| |
am.current | package | The Android package name of the currently running package. | The am.currentkeys return information about the currently-running Activity. |
action | The current activity's action. This has the same format as the name attribute of the action element in a package manifest. |
| |
comp.class | The class name of the component that started the current Activity. See comp.package for more details. |
| |
comp.package | The package name of the component that started the current Activity. A component is specified by a package name and the name of class that the package contains. |
| |
data | The data (if any) contained in the Intent that started the current Activity. |
| |
categories | The categories specified by the Intent that started the current Activity. |
| |
clock | realtime | The number of milliseconds since the device rebooted, including deep-sleep time. | SeeSystemClock for more information. |
displayWidth =targetDevice.getProperty ('display.width') printdisplayWidth.encode('utf-8') displayHight =targetDevice.getProperty('display.width') printdisplayHight.encode('utf-8')
以上示例的目的是獲得目標設(shè)備的長和高,當我們使用坐標點來操作控件的時候,調(diào)試的時候在一臺機器上通過了,但是如果換了另外一個屏幕大小不一樣的機器的話就會失敗,因為控件的坐標點位置可能就變了。這個時候我們就需要用到示例中的連個屬性來動態(tài)計算控件在不同屏幕大小的設(shè)備上面的坐標點了。
這里需要注意參數(shù)應(yīng)該填寫的格式是以上列表中前兩列的組合PropertyGroup.Property
displayWidth = targetDevice.getSystemProperty ('service.adb.tcp.port') print displayWidth.encode('utf-8')
根據(jù)官網(wǎng)的描述,這個函數(shù)和getProperty函數(shù)應(yīng)該有同樣的功能(Synonym for getProperty().),使用的屬性表也如上面的屬性列表一樣。但是按照我的實踐并非如此,不過它確實如官方描述的等同于命令“adb shell getprop <key>.”倒是真的。
以上的例子是獲取adb這個服務(wù)所打開的TCP端口,等同于如下的shell命令:“adb shell getprop service.adb.tcp.port“
如果我嘗試使用下面的方法去獲得設(shè)備的長度,返回的結(jié)果其實會是None
displayWidth = targetDevice.getSystemProperty ('display.width') print displayWidth.encode('utf-8')
if True == targetDevice.installPackage('D:\\Projects\\Workspace\\PythonMonkeyRunnerDemo\\apps\\MPortal.apk'): print "Installationfinished successfully" else: print "Failedto install the apk"
這里有兩點需要注意的:
注意這里碰到一個路徑問題,如果我的路徑寫成:
'D:\Projects\Workspace\PythonMonkeyRunnerDemo\apps\MPortal.apk'
那么會出現(xiàn)下圖這樣的錯誤:
如果在“apps”前面加多個轉(zhuǎn)義符就沒有問題:
'D:\Projects\Workspace\PythonMonkeyRunnerDemo\\apps\MPortal.apk'
所以這里MonkeyRunner處理路徑字串應(yīng)該是存在Bug的,但是時間問題就先不去研究,建議所有路徑的話“\“都加上轉(zhuǎn)義符就好了
'D:\\Projects\\Workspace\\PythonMonkeyRunnerDemo\\apps\MPortal.apk'
if True == targetDevice.removePackage('com.majcit.portal'): print "Succeed toremove the package" else: print "Failedto remove teh package"
· 如上面的installPackageAPI一樣,官網(wǎng)描述的該API是沒有返回值的,而最新的是返回boolean值
只指定第一個參數(shù)(將會跑所有指定Component下的所有case+ NotePad項目本身的使用了Instrumentation的用例,具體意思請看下一節(jié)的分析)
dict = targetDevice.instrument('com.example.android.notepad.tryout/android.test.InstrumentationTestRunner') print dict
指定只跑其中的一個Case:
dict = dict = targetDevice.instrument('com.example.android.notepad.tryout/android.test.InstrumentationTestRunner', {'class':'com.example.android.notepad.tryout.TCCreateNote'}) print dict
這里首先需要看下官方的描述
Runs the specifiedcomponent under Android instrumentation, and returns the results in adictionary whose exact format is dictated by the component being run. Thecomponent must already be present on this device
這里重點是第一句“Runs the specified component under Android instrumentation“,剩下的是說返回值根據(jù)模塊不一樣而有所差別。那么第一句翻譯過來就是”運行使用了Anroid Instrumentation的指定模塊“。那么究竟什么是使用了Android Instrumentation的指定模塊呢?其實如果你有用過Robotium的話應(yīng)該知道,腳本的每個case都是從Instrumentation相關(guān)的類中繼承下來的。
以下是本人之前寫的一個Robotium的測試case(具體請查看本人之前的一篇《Robotium創(chuàng)建一個Note的實例》,可以看到它是繼承于“ActivityInstrumentationTestCase2”的,其實它就滿足了剛才的描述“使用了Anroid Instrumentation的指定模塊“
package com.example.android.notepad.tryout; import com.robotium.solo.Solo; import android.test.ActivityInstrumentationTestCase2; import android.app.Activity; @SuppressWarnings("rawtypes") public class TCCreateNote extends ActivityInstrumentationTestCase2{ private static Solo solo = null; public Activity activity; private static final int NUMBER_TOTAL_CASES = 2; private static int run = 0; private static Class<?> launchActivityClass; //對應(yīng)re-sign.jar生成出來的信息框里的兩個值 private static String mainActiviy = "com.example.android.notepad.NotesList"; private static String packageName = "com.example.android.notepad"; static { try { launchActivityClass = Class.forName(mainActiviy); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") public TCCreateNote() { super(packageName, launchActivityClass); } @Override public void setUp() throws Exception { //setUp() is run before a test case is started. //This is where the solo object is created. super.setUp(); //The variable solo has to be static, since every time after a case's finished, this class TCCreateNote would be re-instantiated // which would lead to soto to re-instantiated to be null if it's not set as static if(solo == null) { TCCreateNote.solo = new Solo(getInstrumentation(), getActivity()); } } @Override public void tearDown() throws Exception { //Check whether it's the last case executed. run += countTestCases(); if(run >= NUMBER_TOTAL_CASES) { solo.finishOpenedActivities(); } } public void testAddNoteCNTitle() throws Exception { solo.clickOnMenuItem("Add note"); solo.enterText(0, "中文標簽筆記"); solo.clickOnMenuItem("Save"); solo.clickInList(0); solo.clearEditText(0); solo.enterText(0, "Text 1"); solo.clickOnMenuItem("Save"); solo.assertCurrentActivity("Expected NotesList Activity", "NotesList"); solo.clickLongOnText("中文標簽筆記"); solo.clickOnText("Delete"); } public void testAddNoteEngTitle() throws Exception { solo.clickOnMenuItem("Add note"); solo.enterText(0, "English Title Note"); solo.clickOnMenuItem("Save"); solo.clickInList(0); solo.clearEditText(0); solo.enterText(0, "Text 1"); solo.clickOnMenuItem("Save"); solo.assertCurrentActivity("Expected NotesList Activity", "NotesList"); solo.clickLongOnText("English Title Note"); solo.clickOnText("Delete"); } }
那么我們先在目標機器上執(zhí)行命令“pm list instrumentation”看是否能列出這個“Component”:
這其中哪一個是我們想要的呢?這要看我們的Robotium測試項目中的AndroidManifest.xml的定義了,根據(jù)下圖的packageName再對照上圖的輸出我們就定位到我們想要的Component了:
如果我們調(diào)用這個方法的時候只是填寫了第一個參數(shù)的話,上圖處在”com.example.andriod.notepad.tryout”這個Component下的所有三個方法都會執(zhí)行。但在我的測試中,發(fā)現(xiàn)除了跑這個Case之外,它還跑多了一些步驟,就是額外的多創(chuàng)建了一個Note1。我查了半天,發(fā)現(xiàn)原來我之前除了編寫了基于ActivityInstrumentationTestCase2的Robotium的這個項目之外,還在NoetePad這個項目上直接用InstrumentationTestCase寫了一個基于Instrumentation的測試用例,做得事情就是增加一個Note1,具體請看本人另外一篇blog《SDK Instrumentation創(chuàng)建一個Note的實例》
其實調(diào)用這個方法相當于在shell腳本上執(zhí)行如下的命令:
am instrument -w -r -e class com.example.android.notepad.tryout.TCCreateNote com.example.android.notepad.tryout/android.test.InstrumentationTestRunner
最后提一下的是,我們的腳本在執(zhí)行示例的時候大概5秒左右就會失敗,但事實上在設(shè)備端所有的指定component的測試用例已經(jīng)在執(zhí)行的了。要解決這個問題需要重新編譯源碼,這里就免了,具體請查看:http://stackoverflow.com/questions/4264057/android-cts-is-showing-shellcommandunresponsiveexception-on-emulator
這里嘗試做一個簡單的總結(jié)
targetDevice.press('KEYCODE_BACK',MonkeyDevice.DOWN_AND_UP)
這個方法的目的是發(fā)送一個指定的Key事件到Android設(shè)備以觸發(fā)相應(yīng)的動作,比如示例中發(fā)送“KEYCODE_BACK”這個key到設(shè)備端去觸發(fā)”返回“鍵的按下和升起(也就是點擊)的動作。
具體Key的Code請查看:http://developer.android.com/reference/android/view/KeyEvent.html
第二個參數(shù)是指導該Key應(yīng)該如何Behave,比如先按下去,休眠一秒再彈起來(其實就相當于一個長按的動作)。具體支持behavior在本MonkeyDevice這個類的成員變量里有定義好:
Type | Constants | Notes | Comment |
int(官網(wǎng)是string,下同) | DOWN | Use this with the type argument of press() or touch() to send a DOWN event. |
|
int | UP | Use this with the type argument of press() or touch() to send an UP event. |
|
int | DOWN_AND_UP | Use this with the type argument of press() or touch() to send a DOWN event immediately followed by an UP event. |
|
int | MOVE | TBD | 官網(wǎng)沒有列出來 |
viewer = targetDevice.getHierarchyViewer() note = viewer.findViewById('id/text1') point = viewer.getAbsoluteCenterOfView(note) startX = point.x startY = point.y targetDevice.touch(startX,startY,MonkeyDevice.DOWN_AND_UP)
這個和上面的press方法在結(jié)果上有點類似,但是本方法是接受坐標進行點擊的,而上面方法接受的是keycode。
第二個參數(shù)同上。
viewer =targetDevice.getHierarchyViewer()
note = viewer.findViewById('id/text1')
point = viewer.getAbsoluteCenterOfView(note)
startX = point.x
startY = point.y
targetDevice.touch(startX,startY,MonkeyDevice.DOWN_AND_UP)
MonkeyRunner.sleep(3)
targetDevice.type('NewContent')
viewer = targetDevice.getHierarchyViewer() note = viewer.findViewById('id/text1') point = viewer.getAbsoluteCenterOfView(note) startX = point.x startY = point.y targetDevice.touch(startX,startY,MonkeyDevice.DOWN_AND_UP) MonkeyRunner.sleep(3) targetDevice.type('NewContent')
指定一串字符串進行模擬鍵盤輸入,等同于調(diào)用press方法按照一個一個的keycode用MonkeyDevice的DOWN_AND_UP進行輸入。
嘗試時發(fā)現(xiàn)中文不支持,鑒于項目不需要用到這種手機鍵盤,所以節(jié)省時間暫不研究。
targetDevice.wake()
注意這里只是喚醒屏保,如果手機已經(jīng)鎖屏的話你是不能喚醒的。所以嘗試這個API的時候要注意把鎖屏功能先關(guān)閉掉。
#Connect to the target targetDevice targetDevice = MonkeyRunner.waitForConnection() easy_device = EasyMonkeyDevice(targetDevice) #touch a button by id would need this targetDevice.startActivity(component="com.example.android.notepad/com.example.android.notepad.NotesList") #invoke the menu options MonkeyRunner.sleep(6) #targetDevice.press('KEYCODE_MENU', MonkeyDevice.DOWN_AND_UP); ''' public ViewNode findViewById(String id) * @param id id for the view. * @return view with the specified ID, or {@code null} if no view found. ''' #MonkeyRunner.alert("Continue?", "help", "Ok?") pic = targetDevice.takeSnapshot() pic = pic.getSubImage((0,38,480,762)) newPic = targetDevice.takeSnapshot() newPic = newPic.getSubImage((0,38,480,762)) print (newPic.sameAs(pic,1.0)) newPic.writeToFile('./shot1.png','png')
res = targetDevice.shell('ls /data/local/tmp|grep note') print res
這個命令等同于你直接在命令行上“adb shell $command”
其他參數(shù)沒有用到所以也就沒有嘗試,僅僅嘗試了不帶參數(shù)的情況,結(jié)果就是直接reboot目標設(shè)備了
targetDevice.reboot()這里有點需要提下的是,如果你是在MonkeyRunner命令行下執(zhí)行這條命令的話,就算目標機器重啟,整個MonkeyRunner的環(huán)境還是依然有效的。也就是說你如果繼續(xù)打進一條"targetDevice.reboot()",你的設(shè)備就會再重啟一次。
作者 | 自主博客 | 微信 | CSDN |
天地會珠海分舵 | http://techgogogo.com | 服務(wù)號:TechGoGoGo 掃描碼:
| 向AI問一下細節(jié) 免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。 猜你喜歡最新資訊相關(guān)推薦相關(guān)標簽AI
助 手 |