美文网首页程序员
Uiautomator如何增强脚本的稳定性

Uiautomator如何增强脚本的稳定性

作者: 白天才痴 | 来源:发表于2015-12-11 14:35 被阅读2526次

    使用resourceid定位控件

    UISelector提供的定位的方式很多,可以是类名,文本,资源id,索引值等,但是索引、文本很容易随版本变化,类名重复程度又太高,而资源id通常是不会变的,使用多种条件混合有时效果更好。

    执行控件方法前判断是否存在

    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

    对于有跳转的click,使用clickAndWaitForNewWindow会比直接点击更有效,这个方法等待新窗口的出现后才返回,降低了由于卡顿而导致跳转慢最终找不到控件的概率

    查找控件失败的可能原因

    有的控件设置了属性NAF=true,这个属性大概就是no access flag之类的,表示不能被自动化工具识别到,这种控件我一般用起父控件的坐标去点击。
    资料:https://stuff.mit.edu/afs/sipb/project/android/docs/tools/testing/testing_ui.html

    用一些重试机制使脚本更稳定

    对于自动化,最关注的不是功能点击的实现,而是脚本的稳定性,兼容性,为了写把一个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();//循环查找下一步
            }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自动安装》《推荐其他应用》的弹窗。这类弹窗没有规律
                UiObject btn = mDevice.findObject(new UiSelector().text("下一步").className(Button.class));
                btn.waitForExists(mOutTime/2);
                btn.click();
                waitAndInstall();
            }
        }
    

    使用UIWatcher对异常情况处理,增强稳定性

    脚本运行时的异常弹窗,谷歌当然也会预料到,所以在UiAutomator里提供了UiWatcher这个接口,希望脚本编写者能够在异常时进行一些处理。
    UiWatcher的使用简单,首先它是一个接口,其次它只有一个方法需要实现,下面是其接口定义。

    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();
    }
    

    从接口的注释可以看到,当我们注册了watcher时,如果通过selector没有找到我们想要的Ui元素,就会调用watcher。具体使用方法如下,首先实现这个接口,在我的安装自动化中,安装完apk后经常有些app弹窗问是否要删除安装包,影响脚本后续的点击。所以我写了这个watcher,当触发时,如果UI中找到了类似这个弹窗,那么我点击系统back按键取消这个弹窗,使我的脚本继续执行。

    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,当控件查找失败时就会自动调用watcher了。

    myWatcher = new MyWatcher(mDevice);
    mDevice.registerWatcher("testwatcher",myWatcher);
    

    下面我们看下watcher是如何增强脚本稳定性的。以下是UiObject中查找控件的方法,可以看到当查找控件失败时,就会调用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;
        }
    

    实际用的过程中,不管是调用device的findObject还是hasObject,如果查找失败都会调用到watcher,所以watcher里一定要根据实际状态进行处理,切不可统一做处理。

    takeScreenShot失败?

    这两天写自动化时有时会截图失败,会提示device or resource is busy,后来发现是RootExplorer打开着没有完全退出,只是退到了后台,应该是RE打开时把文件系统重洗挂载了导致无法写入截图文件。
    同时在实践也发现takescreenshot函数的重载版,设置图片质量和缩放时貌似是无效的,如果图片有传输要求还是自己写代码压缩吧

    即使你了解了这些技巧,就目前来看,仍不建议去做功能自动化,脚本的稳定性保证需要很多额外的代码,提升却很有限

    相关文章

      网友评论

        本文标题:Uiautomator如何增强脚本的稳定性

        本文链接:https://www.haomeiwen.com/subject/gglyhttx.html