UiAutomator uiautomatorviewer 功能扩展实践

雪怪 · 2016年04月16日 · 最后由 就不告诉你 回复于 2016年12月06日 · 5491 次阅读
本帖已被设为精华帖!

参考

打造专属 uiautomatorviewer
由 uiautomator 二次开发得到的启发以及完善
uiautomatorviewer 二次开发之自动生成控件定位符

成果

  • 文件目录 ```shell │
    ├─com │ └─android │ └─uiautomator │ │ OpenDialog.java │ │ UiAutomatorModel.java │ │ UiAutomatorViewer.java //展示界面相关,主要修改文件之一 │ │
    │ ├─actions │ │ ClearTextAction.java //新增清除操作按钮 │ │ ExpandAllAction.java │ │ ImageHelper.java │ │ OpenFilesAction.java │ │ ScreenshotAction.java │ │
    │ └─tree │ AttributePair.java │ BasicTreeNode.java //节点操作相关,修改文件之一 │ BasicTreeNodeContentProvider.java │ RootWindowNode.java │ UiHierarchyXmlLoader.java //处理 xml 文件,处理控件展示 │ UiNode.java //处理控件相关属性,修改文件之一 │
    └─images //图标文件夹 clear.png expandall.png open-folder.png screenshot.png
> PS  源码是@xuxu 分享的,对比了其他几位前辈的,貌似缺了几个文件和图标,但没什么太大影响
另外给大伙贴个uiautomatorviewer的源码:
https://android.googlesource.com/platform/frameworks/testing/+/aecdc4a/uiautomator/utils/uiautomatorviewer/src/com/android/uiautomator

需要导入的依赖包在eclipse/plugins
    org.eclipse.core.commands_XXXXXX.jar
    org.eclipse.equinox.common_XXXXXXX.jar
    org.eclipse.jface_XXXXXXXX.jar
    org.eclipse.swt.win32.win32.x86_64_XXXXXX.jar

### 功能点
- 新增清除操作按钮
![](/photo/2016/bcb90d382f627cddd40574ba5c1512b5.png)
- Activity & xpth属性
![](/photo/2016/f541e7493d679a2facf1b646fe96b618.png)
- 右键菜单
 - Click
 - Sendkey(根据输入值,自动生成Sendkey脚本语言)
 - findElementBy (输入id或者class,返回符合要求的控件坐标。。。本来想标红的,无奈一直不成功)
 - 鼠标点击事件
 - sleep(根据输入值,自动生成sleep脚本语言)
 - 输入字符到输入框(仍有bug,adb无法输入中文 && 手机输入法影响)
![](/photo/2016/e84aebec2a3c14cad07090c86c936d1b.png)
![](/photo/2016/1e2c27da6a3ffab03db2143632e0c89d.png)
![](/photo/2016/b2bd71fc4d53739a6d55b0359d962178.png)
![](/photo/2016/9526352ff5fe283cc18b0064a19f1220.png)

- 扩展展示界面(显示内容过多时,点击清除操作按钮后清除显示的内容)
![](/photo/2016/0029f4f4c53e88b878e5ec24d9603531.png)

### 其他
这些扩展功能,参考里面的前辈基本都已经说过了,我在做的时候花的时间也主要是SWT上,这也只是我之前从未接触过Java图形编程的原因,查文档花的时间比较多,所以也就不赘述了。
所以讲下 [打造专属 uiautomatorviewer](https://testerhome.com/topics/4614) 中,@zengjunzhou 前辈提到的
> 控件的id或text或content-desc或class相同,能不能区分选中的控件是第几个?

- 简单思路:
 - 获取父节点和选中的节点
 - 获取父节点下一级子节点
 - 遍历子节点,对选中的节点左侧的子节点进行递归遍历,把结果并写入List

下面是我代码实现部分
#### 获取子节点
```java
/**
     * 返回需要的节点数组
     * @param parentNode 父节点
     * @param selectedNode 当前选中的节点
     * @return
     */
    public static List<BasicTreeNode> AllChildNodes(BasicTreeNode parentNode, BasicTreeNode selectedNode){
        //某个父节点下的所有子节点
        List<BasicTreeNode> childNodes = new ArrayList<BasicTreeNode>();
        Recursion(parentNode, selectedNode, childNodes);

        return childNodes;
    }

    /**
     * 简单的一个递归获取需要的子节点
     * @param currentNode 父节点
     * @param selectedNode 当前选中的节点
     * @param childList 存储的子节点数组
     */
    public static void Recursion(BasicTreeNode currentNode, BasicTreeNode selectedNode, List<BasicTreeNode> childList){
        if (currentNode.hasChild()) {
            for (BasicTreeNode temp : currentNode.getChildren()) {
                if (!temp.equals(selectedNode)) {
                    childList.add(temp);
                    if (temp.hasChild()) {
                        Recursion(temp, selectedNode, childList);
                    }
                    else {
                        continue;
                    }
                }
                else {
                    break;
                }
            }
        }
    }

调用

//class
                    MenuItem classInputItem = new MenuItem(inputChridItem, SWT.PUSH);
                    classInputItem.setText("class");
                    classInputItem.addSelectionListener(new SelectionAdapter() {
                        @Override
                        public void widgetSelected(SelectionEvent e){
                            UiNode sele = (UiNode) UiAutomatorModel.getModel().getSelectedNode();
                            String classSele = sele.getAttribute("class");
                            InputDialog dialog = new InputDialog(getShell(), "请输入文本", "请输入", null, null);
                            String value = "";
                            if (dialog.open() == InputDialog.OK) {
                                value = dialog.getValue();
//                              System.out.println(value);
                                String ursClass = "driver.findElementById(\"" + classSele + "\").sendKeys(\"" + value + "\");";
                                System.out.println(ursClass);
                            }
                            int num = 0;
                            boolean flag = true;
                            BasicTreeNode parent = UiAutomatorModel.getModel().getSelectedNode().getParent();

                            List<BasicTreeNode> childNodes = BasicTreeNode.AllChildNodes(parent, UiAutomatorModel.getModel().getSelectedNode());

                            for (int i = 0; i < childNodes.size(); i++) {
                                BasicTreeNode temp = childNodes.get(i);
                                UiNode tempUinode = (UiNode) temp;

                                if (tempUinode.getAttribute("class").equals(classSele)) {
                                    num++;
                                    flag = false;
                                }

                            }

                            if (flag) {
                                String ursClass = "driver.findElementByClassName(\"" + classSele + "\").sendKeys(\"" + value + "\");" + "\n";
                                System.out.println(ursClass);
                                show += ursClass;
                                text.setText(show);
                            }
                            else {
                                String ursClasses = "driver.findElementsByClassName(\"" + classSele + "\").get(" + num + "\").sendKeys(\"" + value + "\");" + "\n";
                                System.out.println(ursClasses);
                                show += ursClasses;
                                text.setText(show);
                            }
                        }
            });
共收到 36 条回复 时间 点赞

想了下,从父节点开始遍历好像还不够的样子。。还是得从根节点开始遍历
把 parentNode 改成 mRootNode 吧

#1 楼 @snowmaster 代码的部分实现可以贴代码,别用图片吧。

雪怪 #38 · 2016年04月16日 Author

#2 楼 @lihuazhang 好的,我就是觉得复制代码用 md 后格式总有点奇怪,所以才贴的图

#3 楼 @snowmaster 你估计是没指定语言吧.所以默认没有各种颜色.

#1 楼 @snowmaster 不用那么复杂吧,可以看看我写的帖子

—— 来自 TesterHome 官方 安卓客户端

#4 楼 @seveniruby 嗯,指定 java 后好些了,也可能只是我看惯了代码区用黑色背景色所以有点不习惯

#5 楼 @taki 想起来了,之前也看过你那篇帖子,UIAutomatorview 简单封装 生成脚本 (初版),不过你的帖子在 Appium 板块,我又一时没想起来了,就只翻了 Uiautomator 板块的帖子 orz。。

支持 学习

9楼 已删除

webview 支持吗

安卓 app,H5 做的注册页面,用户名和验证码文本框在 uiautomatorviewer 里面只显示 index 是 1,xpath 相同,resouce-id,text 什么的又没有显示,有什么办法可以定位么?!!!!!!!

38楼 已删除
雪怪 #13 · 2016年04月20日 Author

#10 楼 @hjhjhghghg 刚刚看了下,可以查看 webview
@duffypace 右侧树状图点卡看看

#13 楼 @snowmaster 请问怎么查看 webview 里面的内容呢?

雪怪 #15 · 2016年04月20日 Author

#14 楼 微信内置浏览器那种暂时没办法 @hjhjhghghg

#15 楼 @snowmaster 这个应该是浏览器吧?chrome?

我的问题在这里,麻烦大神帮分析下,https://testerhome.com/topics/4686

雪怪 #16 · 2016年04月21日 Author

#16 楼 @hjhjhghghg 嗯,只是举个例子

看个文章都那么麻烦了。。。

能否共享一下?

#13 楼 @snowmaster uiautomatorviewer 支持 webview???之前试过网页都只显示一整块啊,不知道大神做了什么修改可以支持 webview 的,可否说说思路呢

#15 楼 @snowmaster 我看你贴出来的那个解析 webview 的图,那你的.xml 文件是怎么 dump 出来的呢,uiautomator dupm 不出来网页的元素吧

雪怪 #21 · 2016年05月06日 Author

因为辞职有段时间,所以我最近忙着找工作也就没怎么仔细看。。


@niuniudd @hjhjhghghg
在 webview 的界面第一次 dump 是图一,看不到 webview 的内容
第二次再 dump 一次是图二,此时就能看到 webview 的内容。。
@lyoung 图三,第一次 dump 的 xml 下确实是没有 webview 的内容的,第二次 dump 后就有了 webview 的内容。。不清楚 dump 具体是什么机制
@snowboy 社区有人分享了一份,基本都类似的,我这边 github 传不上

#23 楼 @snowmaster 我记得之前有人在论坛里说过这个问题,一定要第二次 dump 才能出来 webview 里面的内容。。看来真得花时间去看看底层实现了

雪怪 #25 · 2016年05月06日 Author

#24 楼 @hjhjhghghg 还有印象那贴的标题吗?不知道是否已经解决了这个疑问

#25 楼 @snowmaster 刚才找了一圈没找到,晚上我回去再找找。。

#23 楼 @snowmaster 那对于 webview 在 dump 的时候用的是 uiautomator 的命令吗,还是要有其他的?

雪怪 #26 · 2016年05月06日 Author

#27 楼 @lyoung 你直接执行 adb shell uiautomator dump 生成 xml 就是有 webview 内容的

雪怪 #29 · 2016年05月06日 Author

@lihuazhang @chenhengjie123 打扰下 2 位前辈,能不能帮忙分析下 #23 里面的提到的 dump2 次后,第二次产生的 xml 文件才包含 webview 下的对象的原因是什么?
ScreenshotAction 方法里面在执行procRunner = getAdbRunner(serial,"shell", "/system/bin/uiautomator", "dump", "/sdcard/uidump.xml");之前也有procRunner = getAdbRunner(serial,"shell", "rm", "/sdcard/uidump.xml");操作,我自己折腾了一晚上也搞不懂为什么第二次 dump 的 xml 会含有第一次 dump 的 xml 所没有的 webview 下的内容,但是手动执行 adb shell uiautomator dump 生成的 xml 却又是直接包含 webview 对象的

#29 楼 @snowmaster 我看楼主用的是魅族的手机,我也拿魅族手机按照你的方法试了一下,果然能 dump 出 webview 的元素,但是华为的还像是不行的,不知道是不是手机里的工具有事么不一样的。。。

我试了下三星 note4 不管几次都是没有 webview 内容的- -,不知道是否魅族改过这里的源码了

雪怪 #32 · 2016年05月09日 Author

#30 楼 @lyoung @niuniudd 手上只有一台魅族所以不大清楚其他手机的情况,Android 版本 5.1,如果真是只有魅族才可以的话,那就不知道该怎么处理了。。

#31 楼 @niuniudd 如果手机安装有一些辅助功能,比如说 talkback 或者 switch access,只要是打开这些服务,dump 就可以解析 webview 的内容,不知道是个啥原理,应该是启用了什么服务。。。还在研究中
#32 楼 @snowmaster

请问一下,怎么获取当前页的 activity

雪怪 #32 · 2016年05月10日 Author

#34 楼 @itboyst 直接调用 adb shell dumpsys activity | grep mFocusedActivity,处理下返回值就可以了

#34 楼 @itboyst adb shell dumpsys window -w | grep name | grep /也可以

—— 来自 TesterHome 官方 安卓客户端

37楼 已删除
Erichthionius [该话题已被删除] 中提及了此贴 08月26日 11:44

你好我有个疑问,我按照你的这个走了一遍,发现个问题,最后得到的这个 num 值是相同 id 或 class 的控件个数,但是要怎么判断所选节点是这几个相同控件中的第几个啊?

#39 楼 @star8 循环调用当前 node 的 getPreviousSibling() 方法,判断类型和标签是不是和当前 node 相同,若相同则计数 +1,直到循环结束

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册