京东质量社区 UIAutomatorview 简单封装 与 Appium Boostrap 结合

taki for 京东 · 2016年04月01日 · 最后由 回复于 2017年06月21日 · 2041 次阅读
本帖已被设为精华帖!

前几天稍微改装了一下 UIAutoatorview 生成脚本,刚发完帖子发现论坛已经有人发出来了,闭门造车不是个好习惯哈,我做这个东西的初衷呢?就是是这段时间学习 APPIUM 觉得写脚本调式费劲,就是想能确定元素是否真的可以定位到,可以执行,最终的一切都是为了支持 Appium 的执行,目前程序完成的比较粗糙,健壮性也不太好,主要是为了给不是特别深入的朋友提供些思路和参考


我这里面的检查执行呢?底层调用时通过 Appium 的 Bootstrap 实现,这样才能保持和 Appium 环境一致,如果用坐标点确定的话,生成的脚本放到 Appium 里面也不一定是有效的,执行环境变了


具体实现,大家看过 Bootstrap 的会知道,Bootstrap 其实是发布了一个 Socket 服务端,并且是长连接,所以当 Bootstrap 运行起来我们可以通过 Socket 去连接发送请求

/**
     * @Descrition:执行初始化连接
    * */
public void init() {
        try {
            String host = "127.0.0.1";
            int port = 4724;
            InetSocketAddress remote = new InetSocketAddress(host, port);
            sc = SocketChannel.open();
            sc.connect(remote);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/**
     * @Descrition:发送请求
    * */
public void sendMsg(){
        boolean run = true;
      while (run) {


                String sendMsg = QueueMsg.getSendMsg();          //这里简单弄了个队列信息
                if (null!=sendMsg&&!sendMsg.equals("")) {
                    printMsg(sendMsg, sendMsg);
                    InputStream inputStream = new ByteArrayInputStream(sendMsg.getBytes());
                    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                    String str = br.readLine();
                    printMsg(sendMsg, "读入一行数据,开始发送...");
                    w_bBuf = ByteBuffer.wrap(str.getBytes("UTF-8"));
                    //将缓冲区中数据写入通道
                    sc.write(w_bBuf);
                    printMsg(sendMsg, "数据发送成功...");
                    w_bBuf.clear();
                    printMsg(sendMsg, "接收服务器端响应消息...");
                    try {
                        Thread.currentThread();
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    r_bBuf.clear();
                    //将字节序列从此通道中读入给定的缓冲区r_bBuf
                    sc.read(r_bBuf);
                    r_bBuf.flip();
                    String resultMsg = Charset.forName("UTF-8").decode(r_bBuf).toString();
                        QueueMsg.setResultMsg(resultMsg);
                }
      }
}

我们看 UIAutomatorView 这个工具去 Dump 控件文件会发现它是执行的 UIAutoamtor,Boostrap 也是执行的 UIAutoamtor,那么我们启动 Boostrap 之后,线程是阻塞的,所以在用 UIAutomatorView 去 Dump 是无法 Dump 的,所以我们需要对 Boostrap 进行改造一下,通过 Boostrap 去 Dump 控件文件

首先在 Boostrap 中的 AndroidCommandExecutor.java 类要加入一行命令

map.put("dump", new Dump());

然后写一个 Dump 类

public class Dump extends CommandHandler {
    private static final File dumpFolder = new File(Environment.getDataDirectory(), "local/tmp");
    private static final String dumpFileName = "myuidump.xml";
    @Override
    public AndroidCommandResult execute(AndroidCommand command) {
        File file = new File(dumpFolder, dumpFileName);
        try {
            QueryController queryController = UiAutomatorBridge.getInstance().getQueryController();
            AccessibilityNodeInfo accessibilityRootNode = queryController.getAccessibilityRootNode();
            String msg = "success";
            try {
                AccessibilityNodeInfoDumper.dumpWindowToFile(accessibilityRootNode, file);
            } catch (Exception e) {
                msg = e.toString();
            }
            return getSuccessResult(msg);
        } catch (final Exception e) {
            Logger.debug("Dump Exception: " + e);
            return getErrorResult(e.getMessage());
        }
    }
}

完成之后重新打 Bootstrap 放到设备里

UIAutomator 里面的 UiAutomatorHelper 类的 getUiHierarchyFile 方法要改动一下,通过 Bootstrap 去 Dump 控件文件

monitor.subTask("Taking UI XML snapshot...");
//原来的Dunp方法

 //try {
//            device.executeShellCommand(
//                    command,
//                    new CollectingOutputReceiver(commandCompleteLatch),
//                    XML_CAPTURE_TIMEOUT_SEC * 1000);
//            commandCompleteLatch.await(XML_CAPTURE_TIMEOUT_SEC, TimeUnit.SECONDS);
//
//            monitor.subTask("Pull UI XML snapshot from device...");
//            device.getSyncService().pullFile(UIDUMP_DEVICE_PATH,
//                    dst.getAbsolutePath(), SyncService.getNullProgressMonitor());
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }


//修改后的Dunp方法
      try {
            QueueMsg.setSendMsg("{\"cmd\":\"action\",\"action\":\"dump\",\"params\":{}} ");
            monitor.subTask("Pull UI XML snapshot from device...");
            String value = QueueMsg.getResultMsg();
            BootstrapCmdDomain bootstrapCmdDomain = JSON.parseObject(value, BootstrapCmdDomain.class);
            int status = bootstrapCmdDomain.getStatus();
            if (0 == status) {
                device.getSyncService().pullFile(MyUIDUMP_DEVICE_PATH,
                        dst.getAbsolutePath(), SyncService.getNullProgressMonitor());
            } else {
                throw new Exception(bootstrapCmdDomain.getValue());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

{\"cmd\":\"action\",\"action\":\"dump\",\"params\":{}} 这写个命令可以在 Appium 的日志里获取,大家看我的命令多打了一个空格,因为 Bootstrap 读取消息的时候少读了一个字节,会报出转 JSON 异常,所以这里面一定要多加一个空格。

剩下就是封装一些点击事件,输入文本脚本生成的事情了


完成后的结果,我们可以手动把 APP 启动起来,然后打开 UIAtuomator,弄一个输入的事件


选择一个元素,鼠标右键


确定后设备里面的输入被执行了,右侧的文本框打印出了脚本,这里的打印信息,只有事件被正确执行才能生成脚本,生成的脚本规则是自己定义的,所以这里按照自己的需求写自己的算法



点击事件,我们点击下【搜索按钮】

点击【搜索按钮】后的结果


UI 界面什么的都比较粗糙,其实就把它做一个辅助工具,为 Appium 服务,所有功能 OK 就可以了,当然大家可以自己加一些拖拽、JS 执行、长按等事件的操作

共收到 16 条回复 时间 点赞

没看明白这样封装的目的是啥?楼主能否解释下?才学自动化不久

taki [该话题已被删除] 中提及了此贴 07月15日 16:45
taki #4 · 2016年06月06日 Author

#14 楼 @alwans 那不是多绕了一层 使用多麻烦

#12 楼 @taki 为什么不能直接把数据给 appium 的服务端呢?这样省去了自己解析成 bootstrap 执行的指令

#12 楼 @taki 感谢,我看到了。。。

taki #7 · 2016年06月06日 Author

#11 楼 @alwans 抓取 appium 日志 你观察一下

你好,请问下具体实现方式是否就是直接发送指令给 bootstrap 监听的 4724 端口执行,但是这样做是不是就是绕开了 appium 的服务端?比如:Webelement e = driver.findElementById(xxx),e.click(); 你需要自己手动把他解析成 bootstrap 能执行的指令,那这个怎么实现呢?

taki #10 · 2016年04月14日 Author

#9 楼 @adfghzhang public class QueueMsg {
public static List sendList = new ArrayList();
public static List resultList = new ArrayList();

public static void setSendMsg(String sendMsg) {
sendList.add(sendMsg);
}
public synchronized static String getResultMsg() {
String resultMsg = null;
for (int i = 0; i < 20; i++) {
if (resultList.size() > 0) {
resultMsg = new String(resultList.get(0));
resultList.remove(0);
break;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException ii) {
ii.printStackTrace();
}
}
}
return resultMsg;
}
public static void setResultMsg(String resultMsg) {
resultList.add(resultMsg);
}
public synchronized static String getSendMsg() {
String sendMsg = "";
if (sendList.size() > 0) {
sendMsg = sendList.get(0);
sendList.remove(0);
sendFlag = 0;
}
return sendMsg ;
}
}

#8 楼 @taki 方便公布一下该类的代码么

taki #8 · 2016年04月14日 Author

#7 楼 @adfghzhang 自己定义的

@taki QueueMsg 是您自定义的类?另外 init() 和 sendMsg() 方法是用来测试 bootstrap 如何工作的么?能联系一下我邮箱私聊问点问题么?

taki #13 · 2016年04月13日 Author

#5 楼 @adfghzhang 我没咋调试,打完放在 /data/local/tmp 下面

请指点一下 bootstrap 项目如何调试,打包后的 bootstrap 放到设置的什么位置?

#3 楼 @taki 还是失败了 私聊下我吧

taki #16 · 2016年04月01日 Author

#2 楼 @lanxiangtechnical 核心类已经写上去了,你参考自己弄弄才能有进步

先谢谢大神,说好的工程文件呢?麻烦给个链接

taki #1 · 2016年04月01日 Author

dump 用 UiDevice.getInstance().dumpWindowHierarchy(); 自这个也可以

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