developer.android.com/training/te…java
Android 平臺全部自動化測試框架的底層實現都依賴官方提供的 UI Automator 測試框架,適用於跨系統和已安裝應用程序的跨應用程序功能UI測試。主要功能包括三部分:node
PC 端 GUI 工具,掃描和分析 Android 設備上當前顯示的 UI 組件。展現 UI 佈局層次結構,查看設備上當前對用戶可見的 UI 組件的屬性。從名稱能夠看出,它是 UI Automator 的只讀功能部分,即只能查看 UI 組件的樹形結構和屬性,不能操做控制 UI 組件。python
uiautomatorviewer
位於 <android-sdk>/tools/bin
目錄。
啓動入口是一個bash文件,實際調用 <android-sdk>/tools/lib
目錄的 uiautomatorviewer-26.0.0-dev.jar
。
GUI 基於 Eclipse + SWT 實現,使用 Gradle 構建。
系列工具源碼在 https://android.googlesource.com/platform/tools/swt/
。 依賴 https://android.googlesource.com/platform/tools/base/
。
活躍分支: mirror-goog-studio-master-dev
。
該倉庫還包含如下工具。android
其內部實現基於 adb shell uiautomator dump
。從源碼倉庫提交記錄看,主要功能開發的活躍時間是 2014-2015,2016以後已經不多更新維護。那個年代的 Android 開發主要使用 Eclipse , 因此基於 SWT 實現多平臺 PC GUI ,在當時合理。git
該工具實際使用運行不穩定,極易報錯。github
Error while obtaining UI hierarchy XML file: com.android.ddmlib.SyncException: Remote object doesn't exist!
shell
錯誤緣由一般是:express
分析源碼可知,錯誤都源於 Android Framework uiautomator
。編程
developer.android.com/studio/test…bash
官方提供的另一個工具,封裝 uiautomator API,供 Python 腳本調用,也可注入 java 擴展插件。
相比 uiautomatorviewer
和 uiautomator
命令行工具,可編程擴展性更佳。
MonkeyRunner 使用了比較冷門的 Jython 實現。
monkeyrunner -plugin <plugin_jar> <program_filename> <program_options>
monkeyrunner 是一個bash文件,位於 <android-sdk>/tools/bin
,啓動調用 <android-sdk>/tools/lib/monkeyrunner-26.0.0-dev.jar
。
export ANDROID_HOME="~/Library/Android/sdk"
$ANDROID_HOME/tools/bin/monkeyrunner uiparser.py
複製代碼
等同於調用 adb shell getprop <keyword>
。獲取設備系統環境變量。
不一樣廠商的設備,key可能不一樣。針對具體測試機型,可以使用 adb shell getprop
,顯示全部系統環境變量的key。
等同於調用adb shell
命令。
MonkeyRunner 基於 Jython 2.5.3 。看上去結合了Java和Python的優點,實際對於Java和Python編程都不友好。
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage
。os, sys, subprocess
等庫。MonkeyRunner 實際仍然是使用 adb shell
和其中的 uiautomator
命令獲取UI組件狀態和屬性。因此它跟 UI Automator Viewer
同樣受限於 uiautomator
自己的缺陷,致使運行不穩定。
adb
developer.android.google.cn/studio/comm…
adb shell am
developer.android.google.cn/studio/comm…
使用 Activity Manager (am) 工具發出命令以執行各類系統操做,如啓動 Activity、強行中止進程、廣播 intent、修改設備屏幕屬性及其餘操做。
adb shell pm
developer.android.google.cn/studio/comm…
使用軟件包管理器 Package Manager (pm) 工具發出命令,安裝,卸載,查詢安裝包。
adb shell uiatomator
官網相關頁面已被刪除,僅能從搜索引擎歷史快照中找到。猜想可能近期會有變動,或者官方建議再也不使用。
經過執行命令能夠查看使用方法和參數。
Usage: uiautomator <subcommand> [options]
Available subcommands:
help: displays help message
runtest: executes UI automation tests
runtest <class spec> [options]
<class spec>: <JARS> < -c <CLASSES> | -e class <CLASSES> >
<JARS>: a list of jar files containing test classes and dependencies. If
the path is relative, it's assumed to be under /data/local/tmp. Use absolute path if the file is elsewhere. Multiple files can be specified, separated by space. <CLASSES>: a list of test class names to run, separated by comma. To a single method, use TestClass#testMethod format. The -e or -c option may be repeated. This option is not required and if not provided then all the tests in provided jars will be run automatically. options: --nohup: trap SIG_HUP, so test won't terminate even if parent process
is terminated, e.g. USB is disconnected.
-e debug [true|false]: wait for debugger to connect before starting.
-e runner [CLASS]: use specified test runner class instead. If
unspecified, framework default runner will be used.
-e <NAME> <VALUE>: other name-value pairs to be passed to test classes.
May be repeated.
-e outputFormat simple | -s: enabled less verbose JUnit style output.
dump: creates an XML dump of current UI hierarchy
dump [--verbose][file]
[--compressed]: dumps compressed layout information.
[file]: the location where the dumped XML should be stored, default is
/sdcard/window_dump.xml
events: prints out accessibility events until terminated
複製代碼
運行耗時長,失敗率高,頻繁報錯。
ERROR: could not get idle state.
一般表示當前UI處於動態渲染刷新期間,例如正在播放視頻,動畫。在10秒超時時間內仍未進入靜態。由於此時 UI 樹的節點對象快速變化中,不能穩定獲取。
PC端工具源碼位於倉庫 android.googlesource.com/platform/fr… master
分支。
最新更新於 2014.11.14。以後活躍分支變動爲 android-support-test
分支。uiautomator
源碼被移除,改爲 android.support.test library, expresso
等工具的源碼工程。
手機端框架源碼位於倉庫 android.googlesource.com/platform/fr… master
分支。
uiAutomation.waitForIdle(1000, 1000 * 10);
是報錯的關鍵代碼,即單次超時等待1秒,最長超時等待10秒。超時拋出異常。
DumpCommand.java
// It appears that the bridge needs time to be ready. Making calls to the
// bridge immediately after connecting seems to cause exceptions. So let's also
// do a wait for idle in case the app is busy.
try {
UiAutomation uiAutomation = automationWrapper.getUiAutomation();
uiAutomation.waitForIdle(1000, 1000 * 10);
AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow();
if (info == null) {
System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
return;
}
Display display =
DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
int rotation = display.getRotation();
Point size = new Point();
display.getSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y);
} catch (TimeoutException re) {
System.err.println("ERROR: could not get idle state.");
return;
} finally {
automationWrapper.disconnect();
}
System.out.println(
String.format("UI hierchary dumped to: %s", dumpFile.getAbsolutePath()));
複製代碼
UiAutomation.java
/** * Waits for the accessibility event stream to become idle, which is not to * have received an accessibility event within <code>idleTimeoutMillis</code>. * The total time spent to wait for an idle accessibility event stream is bounded * by the <code>globalTimeoutMillis</code>. * * @param idleTimeoutMillis The timeout in milliseconds between two events * to consider the device idle. * @param globalTimeoutMillis The maximal global timeout in milliseconds in * which to wait for an idle state. * * @throws TimeoutException If no idle state was detected within * <code>globalTimeoutMillis.</code> */
public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis) throws TimeoutException {
synchronized (mLock) {
throwIfNotConnectedLocked();
final long startTimeMillis = SystemClock.uptimeMillis();
if (mLastEventTimeMillis <= 0) {
mLastEventTimeMillis = startTimeMillis;
}
while (true) {
final long currentTimeMillis = SystemClock.uptimeMillis();
// Did we get idle state within the global timeout?
final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
final long remainingGlobalTimeMillis =
globalTimeoutMillis - elapsedGlobalTimeMillis;
if (remainingGlobalTimeMillis <= 0) {
throw new TimeoutException("No idle state with idle timeout: "
+ idleTimeoutMillis + " within global timeout: "
+ globalTimeoutMillis);
}
// Did we get an idle state within the idle timeout?
final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
if (remainingIdleTimeMillis <= 0) {
return;
}
try {
mLock.wait(remainingIdleTimeMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
}
複製代碼
developer.android.com/studio/prof…
Android SDK 工具集的 Android Device Monitor
已廢棄。
Android Device Monitor was deprecated in Android Studio 3.1 and removed from Android Studio 3.2. The features that you could use through the Android Device Monitor have been replaced by new features. The table below helps you decide which features you should use instead of these deprecated and removed features.
官方給出的替代品 Layout Inspector
功能更強大,界面也更美觀,但目前還不成熟,相比 iOS 神器 Reveal , 仍需努力。
developer.android.com/studio/debu…
參照 MonkeyRunner 官方文檔實現的 Python Demo。
基於上述問題,我準備寫一個更智能更穩定更高效的 UI Inspecotr ,基於 AndroidX UIAutomation ,使用 Kotlin 實現。