Robotium中webview源码分析

记得好久以前分析了robotium对native控件的支持,最近在研究webview的测试,所以继续看下robotium对webview部分的支持。那么我们稍微复习一下上次分析到结论:
1.利用decoderView获取控件树的方式完成对控件的查找
2.最后调用的instrumentation的inject注入event事件完成对控件的操作
3.利用junit的assert类或者hamcreast框架进行结果判断。
上一篇详情可以查看:Robotium整体源码浅析
(写的没这次好,毕竟水平是越来越高的是不,看有没有时间重新分析下 :) )

照着这个思路,今天一起来学习下robotium中webview的部分
源码地址:robotiumgit主页

概览

打开工程目录,涉及到的web相关的内容就是下图表示的这几个
Robotium中webview源码分析_第1张图片

RobotiumWeb.js:

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

RobotiumWebClient.java:

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

WebElement.java:

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

WebElementCreator.java:

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

WebUtils

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

最后,提上和webview相关的api
Robotium中webview源码分析_第2张图片
webview支持的原理离不开上面这个api了,这几个api排除重载的方法后,可以分成下面一下几个:
- 查找所有或者当前屏幕的的webElement对象,返回列表
- 查找一个webElement对象,返回这个element对象
- 注入文字
- 点击Element
- 等待element元素出现

查找

查找全部元素

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

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

        return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
    }

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

private boolean executeJavaScriptFunction(final String function) {
        List webViews = viewFetcher.getCurrentViews(WebView.class, true);
        final WebView webView = viewFetcher.getFreshestView((ArrayList) 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
Robotium中webview源码分析_第3张图片
那么可以看到这里定义了一堆方法,看这些名称,看起来功能实现不就是和一开始的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对象,默认都是会去查找然后转成坐标点进行操作的。

文本 && 其他 相关

Robotium中webview源码分析_第4张图片
这是solo提供关于text的api,所以提供了两种方式注入文本
enterTextInWebElement
typeTextInWebElement
有啥区别呢,对于enterxxx来说:
Robotium中webview源码分析_第5张图片
是调用类似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方式输入文本。

你可能感兴趣的:(自动化测试)