(注意:本文基于UI Automator测试框架版本为2.2.0)
前言
StaleObjectException是使用UI Automator测试框架时,在个别机型(API版本上)上常见的一个异常(经过证实:其实是所有机型),产生时机:当某个Window中的View树中已经找到的View对象(控件),你正在操作它时,突然从界面变为不可见时,如果你继续使用表示该控件的UiObject2对象,会触发1个StaleObjectException异常对象,此异常表示控件在Window中不可见。
View中个别控件可能因不可见而被“回收”的特性,官方采用抛出StaleObjectExcetion异常的方式提醒我们(表示控件从可见到不可见),这种情况必须需要处理,不然它会导致测试用例直接失败……我们怎么解决StaleObjectException异常呢?
2024.03.05,之前说的不严谨,其实在每个机型上都有可能出现该异常,它表示什么呢?请继续往下看,另外抛出一个深度思考的问题,为啥要有StaleObjectException!!
StaleObjectException什么时候出现?
1、使用UiObject2的任意API
2、任何机器、系统
3、某个window中的View树中某个控件被回收了
这3个条件同时发生时,你一定会碰见StaleObjectException
官方建议
当使用表示控件的UiObject2对象的API时,可能会触发一个StaleObjectException异常,这是因为底层的View已被销毁(多数情况是因为控件不可见),发生这种情况时,你有必要使用UiDevice的findObject(BySelector)方法再次重新获取同一个UiObject2对象(再次获取控件)
说明:官方明确给出解决方案,捕获到StaleObjectException对象时,可以尝试再次获得一个新的UiObject2对象即可……
StaleObjectException类结构介绍
public class StaleObjectException extends RuntimeException {
}
StaleObjectException类定义在UIAutomator测试框架中的androidx.test.uiautomator包中,继承RuntimeException,命名符合异常类的要求,望文知义是异常类命名的基本要求,所以类中是什么都没有定义的,只需要使用这个异常对象
StaleObjectException异常在哪被抛出?
private AccessibilityNodeInfo getAccessibilityNodeInfo() {
…………省略很多代码………………
if (!mCachedNode.refresh()) {
throw new StaleObjectException();
}
…………省略很多哦代码………………
}
我在整个UI Automator测试框架中的一番查找……发现UiObject2中的getAccessibilityNodeInfo()方法会抛出StaleObjectException对象,这意味着所有调用getAccessibilityNodeInfo()方法,只要没有捕获该异常对象,就可能抛出这个StaleObjectException对象,继续查找在UI Automator测试框架中的哪些地方调用了UiObject2的getAccessibilityNodeInfo()方法?即可获知所有可能会抛出StaleObjectException对象的位置!
getAccessibilityNodeInfo()方法被调用的位置
共计25处调用了UiObject2中的getAccessibilityNodeInfo()方法,25处代码中,只有个别的方法显式的捕获了StaleObjectException对象,然后进行了处理,而剩下的API都没有对StaleObjectException作处理。StaleObjectException异常对象会严重影响测试用例程序的稳定性!只要使用UiObject2对象,比如UiObject2常用的findObject()方法、click()方法,setText()方法等等,他们直接或者间接都会调用getAccessibilityNodeInfo()方法,这不是坑爹吗?基本上只要使用UiObject2的API就可能碰见这个异常,尼玛UiObject2也太严谨了吧?哈哈!
那么如何解决这个StaleObjectException异常?保证程序的绝对稳定性呢??
再次参考官方代码
public boolean equals(Object object) {
…………省略很多代码…………
try {
UiObject2 other = (UiObject2)object;
return getAccessibilityNodeInfo().equals(other.getAccessibilityNodeInfo());
} catch (StaleObjectException e) {
return false;
}
}
在UiObject2中的equals()方法中,使用try catch语句捕获StaleObjectException对象,然后处理方式特别粗暴,直接return false!我们借鉴一下这个思路,可以在catch代码块中捕获到StaleObjectException异常对象时,再重新通过UiDevice的findObject(BySelector)方法,获取一个新的UiObject2对象!
最终解决方案
public final boolean handleStale(BySelector bySelector) {
boolean repeat = true;
boolean result = false;
int retryCount = 5;
while (repeat) {
try {
UiObject2 temp = obtainUiObject2(bySelector);
result = performAction(temp);
repeat = false;
if (retryCount <= 0) {
break;
}
retryCount--;
} catch (StaleObjectException e) {
Log.d(TAG, "Handle StaleObjectException");
}
}
return result;
}
基本思路:当UiObject2的任意API抛出StaleObjectException异常对象时,直接捕获,接着while循环中一直尝试几次获取一个新的UiObject2对象,直到没有再出现StaleObjectException对象(说明控件可见了),再结束,这样可以完美解决使用UiObject2遇到的StaleObjectException异常
备注:建议增加一个重试的最大次数,还建议增加一个延迟时间,避免过快的重试
总结
1、StaleObjectException对象的抛出,用于提示我们即将操作的控件突然不可见了,这样可以避免业务逻辑上的误操作,所以UiObject2显然进步了。举个例子:如果使用UiObject,控件不可见的瞬间,仍然会执行点击或者滑动操作,此时的操作则会是同一个坐标内的其它控件……
2、换成UiObject2则就会更严谨,由于控件不可见,则会出现StaleObjectException,尤其是RecycleView、ListView经常会出现StaleObjectException!!!在业务逻辑的选择上,显示UiObject2更佳适用。
3、你可以把StaleObjectException理解成为UiObjectNotFoundException的高级版本,前者更严谨,除了表示找不到控件、也表示控件突然消失不见了。