使用resourceid定位控件
UISelector提供的定位的方式很多,可以是類名,文本,資源id,索引值等,但是索引、文本很容易隨版本變化,類名重復(fù)程度又太高,而資源id通常是不會變的,使用多種條件混合有時效果更好。
執(zhí)行控件方法前判斷是否存在
if (mDevice.hasObject(By.clazz(TextView.class).res(downloadRes))){
UiObject downloag = mDevice.findObject(new UiSelector().className(TextView.class).resourceId(downloadRes));
downloag.clickAndWaitForNewWindow(mOutTime/2);
if (mDevice.hasObject(By.text(lostApk))){
UiObject confirmBtn = mDevice.findObject(new UiSelector().resourceId(dialogbtnRes));
confirmBtn.click();
}
waitAndInstall();
}
多使用clickAndWaitForNewWindow
對于有跳轉(zhuǎn)的click,使用clickAndWaitForNewWindow會比直接點擊更有效,這個方法等待新窗口的出現(xiàn)后才返回,降低了由于卡頓而導(dǎo)致跳轉(zhuǎn)慢最終找不到控件的概率
查找控件失敗的可能原因
有的控件設(shè)置了屬性NAF=true,這個屬性大概就是no access flag之類的,表示不能被自動化工具識別到,這種控件我一般用起父控件的坐標(biāo)去點擊。
資料:https://stuff.mit.edu/afs/sipb/project/android/docs/tools/testing/testing_ui.html
用一些重試機制使腳本更穩(wěn)定
對于自動化,最關(guān)注的不是功能點擊的實現(xiàn),而是腳本的穩(wěn)定性,兼容性,為了寫把一個click寫好,很可能要額外寫10幾行代碼,下面寫的是如何寫一個兼容性強的apk安裝腳本
protected void waitAndInstall() throws UiObjectNotFoundException{
if(mDevice.hasObject(By.clazz(Button.class).text("下一步"))){
UiObject btn = mDevice.findObject(new UiSelector().text("下一步").className(Button.class));
btn.click();
waitAndInstall();//循環(huán)查找下一步
}else if(mDevice.hasObject(By.clazz(Button.class).text("安裝"))){
UiObject btn = mDevice.findObject(new UiSelector().text("安裝").className(Button.class));
btn.click();
btn = mDevice.findObject(new UiSelector().text("確定").className(TextView.class));
btn.waitForExists(30000);
btn.click();
}else{
mDevice.pressBack();//進入到這個流程通常時點擊下載或安裝時彈出了《是否需要root自動安裝》《推薦其他應(yīng)用》的彈窗。這類彈窗沒有規(guī)律
UiObject btn = mDevice.findObject(new UiSelector().text("下一步").className(Button.class));
btn.waitForExists(mOutTime/2);
btn.click();
waitAndInstall();
}
}
使用UIWatcher對異常情況處理,增強穩(wěn)定性
腳本運行時的異常彈窗,谷歌當(dāng)然也會預(yù)料到,所以在UiAutomator里提供了UiWatcher這個接口,希望腳本編寫者能夠在異常時進行一些處理。
UiWatcher的使用簡單,首先它是一個接口,其次它只有一個方法需要實現(xiàn),下面是其接口定義。
public interface UiWatcher {
/**
* Custom handler that is automatically called when the testing framework is unable to
* find a match using the {@link UiSelector}
*
* When the framework is in the process of matching a {@link UiSelector} and it
* is unable to match any widget based on the specified criteria in the selector,
* the framework will perform retries for a predetermined time, waiting for the display
* to update and show the desired widget. While the framework is in this state, it will call
* registered watchers' checkForCondition(). This gives the registered watchers a chance
* to take a look at the display and see if there is a recognized condition that can be
* handled and in doing so allowing the current test to continue.
*
* An example usage would be to look for dialogs popped due to other background
* processes requesting user attention and have nothing to do with the application
* currently under test.
*
* @return true to indicate a matched condition or false for nothing was matched
* @since API Level 16
*/
public boolean checkForCondition();
}
從接口的注釋可以看到,當(dāng)我們注冊了watcher時,如果通過selector沒有找到我們想要的Ui元素,就會調(diào)用watcher。具體使用方法如下,首先實現(xiàn)這個接口,在我的安裝自動化中,安裝完apk后經(jīng)常有些app彈窗問是否要刪除安裝包,影響腳本后續(xù)的點擊。所以我寫了這個watcher,當(dāng)觸發(fā)時,如果UI中找到了類似這個彈窗,那么我點擊系統(tǒng)back按鍵取消這個彈窗,使我的腳本繼續(xù)執(zhí)行。
public class MyWatcher implements UiWatcher {
private UiDevice mDevice;
public MyWatcher(UiDevice device){
mDevice = device;
}
@Override
public boolean checkForCondition() {
if(mDevice.hasObject(By.text("刪除安裝包"))){
mDevice.pressBack();
return true;
}
return false;
}
}
完成定以后,在腳本的setUp里注冊自己的watcher,當(dāng)控件查找失敗時就會自動調(diào)用watcher了。
myWatcher = new MyWatcher(mDevice);
mDevice.registerWatcher("testwatcher",myWatcher);
下面我們看下watcher是如何增強腳本穩(wěn)定性的。以下是UiObject中查找控件的方法,可以看到當(dāng)查找控件失敗時,就會調(diào)用device的runWatcher方法啟動所有注冊的watcher,然后如果沒有超時就會再次尋找。所以如果我們在watcher里把彈窗處理掉,那么下次查找就會成功了。
protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout) {
AccessibilityNodeInfo node = null;
long startMills = SystemClock.uptimeMillis();
long currentMills = 0;
while (currentMills <= timeout) {
node = getQueryController().findAccessibilityNodeInfo(mUiSelector);
if (node != null) {
break;
} else {
// does nothing if we're reentering another runWatchers()
mDevice.runWatchers();
}
currentMills = SystemClock.uptimeMillis() - startMills;
if(timeout > 0) {
SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
}
}
return node;
}
實際用的過程中,不管是調(diào)用device的findObject還是hasObject,如果查找失敗都會調(diào)用到watcher,所以watcher里一定要根據(jù)實際狀態(tài)進行處理,切不可統(tǒng)一做處理。
takeScreenShot失???
這兩天寫自動化時有時會截圖失敗,會提示device or resource is busy,后來發(fā)現(xiàn)是RootExplorer打開著沒有完全退出,只是退到了后臺,應(yīng)該是RE打開時把文件系統(tǒng)重洗掛載了導(dǎo)致無法寫入截圖文件。
同時在實踐也發(fā)現(xiàn)takescreenshot函數(shù)的重載版,設(shè)置圖片質(zhì)量和縮放時貌似是無效的,如果圖片有傳輸要求還是自己寫代碼壓縮吧