您的位置:首页 > 移动开发

Robotium中webview源码分析

2017-04-06 18:36 288 查看
记得好久以前分析了robotium对native控件的支持,最近在研究webview的测试,所以继续看下robotium对webview部分的支持。那么我们稍微复习一下上次分析到结论:

1.利用decoderView获取控件树的方式完成对控件的查找

2.最后调用的instrumentation的inject注入event事件完成对控件的操作

3.利用junit的assert类或者hamcreast框架进行结果判断。

上一篇详情可以查看:Robotium整体源码浅析

(写的没这次好,毕竟水平是越来越高的是不,看有没有时间重新分析下 :) )

照着这个思路,今天一起来学习下robotium中webview的部分

源码地址:robotiumgit主页

概览

打开工程目录,涉及到的web相关的内容就是下图表示的这几个



RobotiumWeb.js:

这个js文件是要注入到webview里面的,定义了一堆方法,用来查找元素和操作元素

RobotiumWebClient.java:

继承WebChromeClient类,主要是为了重写onJsPrompt方法,把查找的元素信息通过回调给到robotium,然后进行封装

WebElement.java:

把查找到的元素信息封装成实体类,这个就是那个类了

WebElementCreator.java:

看名字就知道是创建webelement的,把解析过来的element信息string,包装成WebElement对象

WebUtils

工具类,入口类,包含执行查找,js注入的函数,文本注入等,需要重点关注的入口

最后,提上和webview相关的api



webview支持的原理离不开上面这个api了,这几个api排除重载的方法后,可以分成下面一下几个:

- 查找所有或者当前屏幕的的webElement对象,返回列表

- 查找一个webElement对象,返回这个element对象

- 注入文字

- 点击Element

- 等待element元素出现

查找

查找全部元素

首先是solo.getCurrentWebElements,这个方法和getWebElements一致,只是是否可见作为一个形参传入,所以直接看solo.getWebElements这个方法。

public ArrayList<WebElement> getWebElements(boolean onlySufficientlyVisible){
boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");

return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
}


那么跟踪这个方法,最终调用的是executeJavaScriptFunction这个方法,executeJavaScriptFunction就是执行js注入的方法:

private boolean executeJavaScriptFunction(final String function) {
List<WebView> webViews = viewFetcher.getCurrentViews(WebView.class, true);
final WebView webView = viewFetcher.getFreshestView((ArrayList<WebView>) webViews);

if(webView == null) {
return false;
}

final String javaScript = setWebFrame(prepareForStartOfJavascriptExecution(webViews));

inst.runOnMainSync(new Runnable() {
public void run() {
if(webView != null){
webView.loadUrl("javascript:" + javaScript + function);
}
}
});
return true;
}


那么上面可以看到主要做了有下面3个事情:

通过getCurrentViews查找所有WebView.class对象,并通过绘制时间(View.getDrawingTime())判断哪个才是最近使用的webview,作为被测的webview对象

初始化WebView对象,设置允许加载js脚本,并把WebChromeClient设置为robotiumWebCLient,并读入RobotiumWeb.js

然后通过webview.loadUrl方式注入js脚本,执行查找操作,结果通过robotiumWebCLient重写的onJsPromtp方法读取出查找的内容

所以,重点就变成这个注入的js脚本是什么,直接打开RobotiumWeb.js



那么可以看到这里定义了一堆方法,看这些名称,看起来功能实现不就是和一开始的api相呼应么,看到这里也就明白了,执行js注入查找,本质上是通过dom里面的api查找的,我们打开allWebElements这个js函数看下:

function allWebElements() {
for (var key in document.all){
try{
promptElement(document.all[key]);
}catch(ignored){}
}
finished();
}


嗯,到这里终于明确了:

document.all方法获取html所有元素

promptElement方法把取到的元素解析,用;,分割合并成一个string,最后调用prompt方法发送出来(后续使用onJsprompt回调方式取到内容),这个方法的截取部分实现如下:

prompt(id + ';,' + text + ';,' + name + ";," + className + ";," + tagName + ";," + rect.left + ';,' + rect.top + ';,' + rect.width + ';,' + rect.height + ';,' + attributes);


最终,调用finish()方法,finish方法也是发送prompt消息,标记为完成

function finished(){
prompt('robotium-finished');
}


好了,有了上面的分析,我们很明确下一部就是要把prompt发出来的消息接收并封装成WebElement对象

在executeJavaScriptFunction方法中我们有一步是初始化WebChromeView,这一步使用的是robotium自定义的robotiumWebCLient对象,里面就有重写的onJsPrompt方法。

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {

if(message != null && (message.contains(";,") || message.contains("robotium-finished"))){

if(message.equals("robotium-finished")){
webElementCreator.setFinished(true);
}
else{
webElementCreator.createWebElementAndAddInList(message, view);
}
r.confirm();
return true;
}
else {
if(originalWebChromeClient != null) {
return originalWebChromeClient.onJsPrompt(view, url, message, defaultValue, r);
}
return true;
}

}


这个方法最后调用了WebElementCreator.createWebElementAndAddInList方法:

拿到prompt的message,也就是包含Element信息的string,并解析

封装成WebElement对象

最后,好像好剩下一个方法:getCurrentWebElements

这个方法和getWebElements相比,在返回element列表过程中多加了一步过滤,做的工作是把Element的x,y坐标和webview坐标做比较,把坐标在webview之外的过滤掉,也就是返回的是当前展示的内容啦。

好的,查找核心的东西就这么多了,然后上面是查找全部或者一组,那么查找单个也是同理的,在js文件中封装id、xpath、cssSelector等方法,这些方式刚好都是调用document相关的api,写web端的同学相信会非常熟悉这些定位方式。

点击

robotium对webview的点击支持两种方式,一种和native一样,点击坐标点;另一种则是js脚本的方式。

坐标点:

在RobotiumWeb.js的promptElement方法中,会调用:

”’

var rect = element.getBoundingClientRect();

”’

这就可以得到元素的x,y坐标啦,然后就可以调用solo.clickOnScreen方法点击了

js脚本:

”’

function clickElement(element){

var e = document.createEvent(‘MouseEvents’);

e.initMouseEvent(‘click’, true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);

element.dispatchEvent(e);

}

”’

需要注意的是,solo其实并没有提供入口给我们使用第二种方法点击,如果需要使用js方式点击,需要在初始化的时候配置config对象,默认都是会去查找然后转成坐标点进行操作的。

文本 && 其他 相关



这是solo提供关于text的api,所以提供了两种方式注入文本

enterTextInWebElement

typeTextInWebElement

有啥区别呢,对于enterxxx来说:



是调用类似setText的方法设置的,在robotiumWeb.js可以看到是直接设置By定位到的元素的value设置的,而typeTextInWebElement方式则是:

”’

public void typeTextInWebElement(By by, String text, int match){

if(config.commandLogging){

Log.d(config.commandLoggingTag, “typeTextInWebElement(“+by+”, \”“+text+”\”, “+match+”)”);

}

clicker.clickOnWebElement(by, match, true, false);
dialogUtils.hideSoftKeyboard(null, true, true);
instrumentation.sendStringSync(text);
}


”’

点击–隐藏弹出的输入法–调用instrumentation.sendStringSync方法,这个方法是拆分组个字符,然后输入,也就是会看到输入轨迹。

最后一个清除text,用的是enterTextIntoWebElement方法,就是把value设置为“”,咱们理解成setText(“”)就行了。

还有最后一个api没说,waitForWebElement

public WebElement waitForWebElement(final By by, int minimumNumberOfMatches, int timeout, boolean scroll){
final long endTime = SystemClock.uptimeMillis() + timeout;

while (true) {

final boolean timedOut = SystemClock.uptimeMillis() > endTime;

if (timedOut){
searcher.logMatchesFound(by.getValue());
return null;
}
sleeper.sleep();

WebElement webElementToReturn = searcher.searchForWebElement(by, minimumNumberOfMatches);

if(webElementToReturn != null)
return webElementToReturn;

if(scroll) {
scroller.scrollDown();
}
}
}


这个等待元素出现的比较简单,循环查找并等待超时,自带向下滚动操作,超时就返回null,否则返回找到的元素,当然,这个可以做成断言的,assertthat(xxx,Matcher.isNotNull())用来判断界面元素的展示与否。

总结

好了,分析得差不多了,最后来总结一下。

robotium对webview是支持的,原理是通过获取最近绘制的webview.class作为当前测试的webview,并通过js注入的方式获取webview里面的Element信息,通过prompt回调的方式通讯,获取到元素信息后之后通过onJSPrompt回调取得元素信息,并封装成WebElement对象,最后取得element对象的坐标点调用clickOncreen方法实现点击,当然也可以通过配置config对象让robotium通过dispatchEvent方式点击,不过不是很推荐。其他的类似文本输入可以通过js注入然后domcument的相关api设置value值方式,也可以调用instarumentation的sendKey方式输入文本。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: