Robotium Robotium 处理跨进程的问题

xuxu · February 27, 2015 · Last by securitytest replied at September 26, 2017 · 2179 hits
本帖已被设为精华帖!

先贴上年前的两个帖子:
@qinggchu http://testerhome.com/topics/1826
@yangchengtest http://testerhome.com/topics/1876

Adb-For-Test
https://github.com/gb112211/Adb-For-Test
用在robotium中去处理跨进程的时候还有坑没填上,主要就是权限问题,这里可以看下@qinggchu的这篇博客http://blog.csdn.net/qingchunjun/article/details/43343735
robotium中使用uiautomator命令获取uidump.xml的时候是需要root权限才能获取到的,所以在跨进程时,要通过解析uidump.xml去定位元素位置的时候,就需要使用root过的真机才行,关键需要使用su命令。

参考@qinggchu修改的例子,年后回来就花了点时间修改了下Adb-For-Test,Adb-For-Robotium,简单测试了下,基本上可以满足需求,例子中有一个执行robotium脚本时录制的视频。

主要修改的地方就是获取uidump.xml,原谅我去解析的时候先是用的cat命令

// 获取设备当前界面的控件信息,并解析uidump.xml文件
private void uidump() {

ShellUtils.suShell("uiautomator dump /data/local/tmp/uidump.xml");
sleep(2000);
ShellUtils.suShell("chmod 777 /data/local/tmp/uidump.xml");
sleep(2000);

try {
xml = ShellUtils.StringTOInputStream(ShellUtils
.getShellOut(ShellUtils
.shell("cat /data/local/tmp/uidump.xml")));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

dumps = new UiDumpService().getDumps(xml);
}
//需要root权限执行命令时使用该方法
public static void suShell(String cmd) {
Process ps = null;
DataOutputStream os;

try {
ps = Runtime.getRuntime().exec("su");
os = new DataOutputStream(ps.getOutputStream());

os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
os.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public static InputStream StringTOInputStream(String in) throws Exception {
ByteArrayInputStream is = new ByteArrayInputStream(in.getBytes("ISO-8859-1"));
return is;
}

Adb-For-Robotium(https://github.com/gb112211/adb-For-Robotium.git

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 17 条回复 时间 点赞

我提供一个思路. adb调用uiautomator是不需要root的. adb支持wifi模式, 预先把adb服务设置为wifi模式, 并开放一个端口. 这样android自己就可以通过adb客户端连接自己的adb服务.

这样就可以绕过root了.

@xuxu 感谢侠帅哥,前面通过这个文档学了不少东西。
个人补充一下:ANDROID 4.4以后的版本写文件有权限的问题,保存文件只能放在/SDCARD/ANDROID/DATA/APP的目录下。下次发布的时候,文件路径拉出来给个变量吧~

xuxu #3 · February 27, 2015 作者

@seveniruby 哈哈 还真没想过,又开阔了思路~回头试下

#3楼 @xuxu 我之前试过在android上用adb连接android自己,可以执行adb shell. 有点无限递归自己的味道.

但是这样的操作还是不稳定啊

xuxu #6 · February 27, 2015 作者

@kasi 稳定还是比较稳定的,就是速度比较慢。

xuxu,果断膜拜

通过uiautomator脚本封装一个server端,robotium和它通信,来实现跨进程的操作。
uiautomator通过反射获取AccessibilityNodeInfo 信息,递归获取控件树信息;
这样效率比uiautomator dump快好多倍,只需要几毫秒的时间;

xuxu #9 · March 02, 2015 作者

@marker 求分享下!!

//获取window_dump.xml
public String getWindowInfo() {
try {
Object bridge = ReflectionUtils.invokeMethod(this.getUiDevice(),
"getAutomatorBridge", new Class[] {}, new Object[] {});

if (bridge == null) {
return "AutomatorBridge is Null";
}

Object queryController = ReflectionUtils.invokeMethod(bridge,
"getQueryController", new Class[] {}, new Object[] {});

if (queryController == null) {
return "queryController is Null.";
}

Object rootNode = ReflectionUtils
.invokeMethod(queryController, "getAccessibilityRootNode",
new Class[] {}, new Object[] {});

AccessibilityNodeInfo root = (AccessibilityNodeInfo) rootNode;

if (root != null) {
return Utils.getWindowInfo(root, this.getUiDevice()
.getDisplayWidth(), this.getUiDevice()
.getDisplayHeight());
} else {
return "Root Node is Null";
}
} catch (Exception e) {
e.printStackTrace();
Log.e(tag, "Error", e);
return e.getMessage();
}
}

//根据root获取控件数信息
public static String getWindowInfo(AccessibilityNodeInfo root, int screenW, int screenH) {
if (root == null) {
return "";
}
final long startTime = SystemClock.uptimeMillis();
try {
XmlSerializer serializer = Xml.newSerializer();
StringWriter stringWriter = new StringWriter();
serializer.setOutput(stringWriter);
serializer.startDocument("UTF-8", true);
serializer.startTag("", "hierarchy");
dumpNodeRec(root, serializer, 0, screenW, screenH);
serializer.endTag("", "hierarchy");
serializer.endDocument();
return stringWriter.toString();

} catch (IOException e) {
Log.e("UITestin", "failed to dump window to file", e);
}
final long endTime = SystemClock.uptimeMillis();
Log.w("UITestin", "Fetch time: " + (endTime - startTime) + "ms");
return "";
}

//参考uiautomator部分源码
private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer,
int index, int screenW, int screenH) throws IOException {
serializer.startTag("", "node");
if (!nafExcludedClass(node) && !nafCheck(node))
serializer.attribute("", "NAF", Boolean.toString(true));
serializer.attribute("", "index", Integer.toString(index));
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
serializer.attribute("", "password", Boolean.toString(node.isPassword()));
serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
serializer.attribute("", "bounds",
AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, screenW, screenH).toShortString());
int count = node.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
if (child.isVisibleToUser()) {
dumpNodeRec(child, serializer, i, screenW, screenH);
child.recycle();
} else {
// Log.i(tag, String.format("Skipping invisible child: %s", child.toString()));
}
} else {
// Log.i(tag, String.format("Null child %d/%d, parent: %s",
// i, count, node.toString()));
}
}
serializer.endTag("", "node");
}

//反射相关方法
public static Object invokeMethod(Object object, String methodName,
Class<?>[] parameterTypes, Object[] parameters) {

// 根据 对象、方法名和对应的方法参数 通过反射 调用上面的方法获取 Method 对象

Method method = getDeclaredMethod(object, methodName, parameterTypes);

// 如果Method为空,则肯定没有结果,直接返回null即可。
if (method == null){
return null;
}

// 抑制Java对方法进行检查,主要是针对私有方法而言
method.setAccessible(true);

try {
if (null != method) {
// 调用object 的 method 所代表的方法,其方法的参数是 parameters
return method.invoke(object, parameters);
}

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

return null;

}

public static Method getDeclaredMethod(Object object, String methodName,
Class<?>... parameterTypes) {

Method method = null;

for (Class<?> clazz = object.getClass(); clazz != Object.class; clazz = clazz
.getSuperclass()) {

try {

method = clazz.getDeclaredMethod(methodName, parameterTypes);
return method;

} catch (Exception e) {

// 这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。

// 如果这里的异常打印或者往外抛,则就不会执行clazz =
// clazz.getSuperclass(),最后就不会进入到父类中了

}
}

return null;

}
xuxu #11 · March 02, 2015 作者

@marker 谢谢!回头试下看看!~

感谢 分享
目前我就是遇到真机测试的时候遇到这个问题

已经解决 感谢楼主

#8楼 @marker 这个复杂么

#14楼 @young
不复杂,上面已经提供获取window dump的源码;
server端还需要自己封装,可以参考AppiumBootstrap 源码

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up