@doctorq 或者你查一下 grunt 里面的源码吧,看看那个 task 具体做了什么。
reset.sh 的 log 在用了--verbose
后还是挺清晰的。
解决以后分享一下哈。我这个只记录了 mac 的,没有试过在 windows 下配置。
@jinjun0620 这么神奇?今晚回去研究研究。这是一个很有趣的问题。因为根据上面的分析应该无论哪个平台都找不到的(bootstrap 是在 android 上运行的)。
@doctorq 执行成功的标志是最后一行是
---- reset.sh completed successfully ----
用 appium 的 bootstrap debug 过了, 确实 appium 1.3.5 在 android 平台下的 findElementByXPath 不支持中文。
appium 用 xpath 定位的原理是先用 dump 把当前界面所有元素保存到一个 xml 文件里面,然后再读取 dump 出来的 xml 文件,最后用 xpath 去查那个 xml 文件。(xpath 本来设计就是用来做 xml 的元素定位的)
不能支持中文(严格地说是不支持所有非 ascii 字符)的原因是这个 dump 用的是 bootstrap 里面自己的 dump,而不是 uiautomator 的,这个 dump 会把所有不支持的字符替换成?
,所以你用包含中文的 xpath 去找会找不到。
借个地方贴一下相关源码方便后面研究:
通过 xpath 查找元素的函数:
io.appium.android.bootstrap.utils.XMLHierarchy.java
public static ArrayList<ClassInstancePair> getClassInstancePairs(String xpathExpression)
throws ElementNotFoundException, InvalidSelectorException, ParserConfigurationException {
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression exp = null;
try {
exp = xpath.compile(xpathExpression);
} catch (XPathExpressionException e) {
throw new InvalidSelectorException(e.getMessage());
}
Node formattedXmlRoot;
formattedXmlRoot = getFormattedXMLDoc();
return getClassInstancePairs(exp, formattedXmlRoot);
}
public static ArrayList<ClassInstancePair> getClassInstancePairs(XPathExpression xpathExpression, Node root) throws ElementNotFoundException {
NodeList nodes;
try {
nodes = (NodeList) xpathExpression.evaluate(root, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
e.printStackTrace();
throw new ElementNotFoundException("XMLWindowHierarchy could not be parsed: " + e.getMessage());
}
ArrayList<ClassInstancePair> pairs = new ArrayList<ClassInstancePair>();
for (int i = 0; i < nodes.getLength(); i++) {
if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
try {
pairs.add(getPairFromNode(nodes.item(i)));
} catch (PairCreationException e) { }
}
}
return pairs;
}
替换字符的函数:
io.appium.uiautomator.core.AccessibilityNodeInfoDumper.java
private static String stripInvalidXMLChars(CharSequence cs) {
StringBuilder ret = new StringBuilder();
char ch;
for (int i = 0; i < cs.length(); i++) {
ch = cs.charAt(i);
// code below from Html#withinStyle, this is a temporary workaround because XML
// serializer does not support surrogates
if (ch >= 0xD800 && ch <= 0xDFFF) {
if (ch < 0xDC00 && i + 1 < cs.length()) {
char d = cs.charAt(i + 1);
if (d >= 0xDC00 && d <= 0xDFFF) {
i++;
ret.append("?");
}
}
} else if (ch > 0x7E || ch < ' ') {
ret.append("?");
} else {
ret.append(ch);
}
}
return ret.toString();
}
其他方法(如 findElementByAccessibilityId)能找到包含中文的元素的原因是它直接使用 uiautomator 的对应方法。而 uiautomator api 并没有根据 xpath 查找元素的方法,所以 appium 是自己另外实现的。
这里替换掉字符的原因应该是基于安全性考虑( evaluate 是一个权限很高的函数,不对参数进行过滤的话很危险)。
@doctorq 我们那个项目第一个任务可以考虑修复这个问题,让 appium 支持通过含有非 ascii 字符的 xpath 来查找元素。
我的意思是你的 Java 文件应该使用 utf-8 编码。这样中文的编码才和 app 对应元素一致(android testview 内容默认使用 utf-8 编码)。
不过我也没使用 Java 的 client 试验过。明天试一下。
你用的什么编码?是 utf-8 吗?
要支持中文应该要 utf-8 的吧。
可以肯定 appium 的 xpath 定位应该是支持中文的。
@doctorq 对,其实就是一种流行的 http api 编写风格。因为没有严格的规定,所以连规范都说不上。
曾经尝试写一个系统的 REST api,查了不少资料。当时天真地以为 REST api+html5 可以一套代码通杀 Web+Mobile,结果被 Android 的 html5 性能打败了……
@doctorq ok,我更新一下文章
REST 是指 RESTful 的 api 吗。个人理解就是 http 的 GET、POST、PUT 和 DELETE 对应资源的 CRUD(增删改查),把所有的对象(例如论坛里的用户、用户组、帖子、专区)都设计成资源。然后就能像写 CRUD 那样使用 api 了。
话说 appium 使用 Http REST 方式是因为遵循了webDriver 的规范吧?
你的代码是直接复制粘贴上来的吗?第一个代码的 xpath 有错,@text=‘中文'
第一个引号是中文引号。
学习了!这样的原理剖析不仅学会了原理,还知道了应该如何正确使用这个功能。
能附上 Appium 端的错误信息吗?
@cosyman 所以我注明了 需要能连接国外网络。确实 chromedriver 没代理下不下来。你有调整过 reset.sh 来让它下载 chromedriver 的节点改为国内镜像吗?有的话麻烦在这里说一下,我更新到帖子内容里。谢谢。
@lihuazhang 现在还没做到这一步。我后面跑一下测试后再加上。看官方文档的话 unittest 只需要一个命令就可以跑了,不过里面有没有坑现在还不清楚……
@lihuazhang 好的,明白了。第一组双星号前面必须有一个空格。谢谢指导。
@lihuazhang 我这边确实有问题。用输入法输的:粗体。复制页面底部示例的:粗体。我用的浏览器:Safari version 8.0.2 (10600.2.5)
可以确定我没用全角或者加了空格。全角的写法是××粗体××,在这个字体下还是很明显的。
markdown 是浏览器 js 处理还是发到服务器才处理的?如果是浏览器 js 处理可能是我网速问题导致 Js 加载不完整,或者是 js 的浏览器兼容性问题,所以粗体没有被正确转换把。
后面我再深入看看。
现在可以了。粗体。昨天确实不行,修复了就好。谢谢!
@monkey 论坛的 markdown 粗体是不是有问题?怎么粗体不会转成粗体的?
能把解决方法同时附到主内容里面吗?这样方便后面的人快速参考。
data:text/html,chromewebdata
应该是 chromedriver 启动浏览器的默认 url。至于加载不成功为啥会返回这个我只找到 selenium 有个相关 issue。官方标记为 fixed 但后面有人说在部分浏览器中还存在。
传送门(请科学上网):
Issue 4301: getCurrentUrl should return the current URL on a 404 page
建议换个方式来验证页面是否加载成功吧。
PS: 话说神马是 H5 自动化
?该不会是 html5 自动化
吧?
@doctorq 谢谢支持!后面我会放到我自己的 blog 里面的,也会发个总结帖说明一下,还会发些分享帖说明如何进行 remote debug 来查看 app 使用 android api 时内部具体是怎么做的。现在在这里记录的只是原始资料,所以也只是跟帖而已。