robotium框架支持WebView,在robotium中有getWebElements()、getWebElements(By by)等方法来获取android中的WebView的元素,并提供了 clickOnWebElement方法来完成点击事件.android中的原生控件是比较好攻取的,那么对于WebView这个框架是怎么获取的呢。

 



第一步:利用JS获取页面中的所有元素



在PC上,获取网页的元素可以通过注入javascript元素来完成,以Chrome浏览器为例,打开工具——JavaScript控制台(快捷方式:Ctrl+Shift+J),输入 javascript:prompt(document.URL)即会弹出含当前页面的URL的提示框,因此通过编写适当的JS脚本是可以在这个弹出框中显示所有页面元素的。RobotiumWeb.js就是此功能实现用的JS脚本。以solo中getWebElements()为例,



 



[java]



public ArrayList getWebElements(boolean onlySufficientlyVisible){ 
    
 
   
1. boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");
2. return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
3. }
 
  
public ArrayList getWebElements(boolean onlySufficientlyVisible){
                boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");
                
                return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
        }

[java]



 


1. private boolean executeJavaScriptFunction(final String function){

2. final WebView webView = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(WebView.class, true));
3. if(webView == null){

4. return false;
5. }
6.  //做一些JS注入执行前的准备工作,例如将WebView设为可允许执行JS等,并将RobotiumWeb.js中的脚本以String形式返回
7. final String javaScript = prepareForStartOfJavascriptExecution();
8.  activityUtils.getCurrentActivity(false).runOnUiThread(new Runnable() {

9. public void run() {

10. if(webView != null){

11. webView.loadUrl("javascript:" + javaScript + function);
12. }
13. }
14. });
15. return true;
16. }


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

                if(webView == null){
                        return false;
                }
                //做一些JS注入执行前的准备工作,例如将WebView设为可允许执行JS等,并将RobotiumWeb.js中的脚本以String形式返回
                final String javaScript = prepareForStartOfJavascriptExecution();

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



可以看出这个方法执行的是allWebElements();函数,即类似执行RobotiumWeb.js文件中如下JS代码片段:



可以把如下片段放到JavaScript控制台中看效果



 



javascript:


1. function allWebElements() {

2. for (var key in document.all){

3. try{

4. promptElement(document.all[key]); //调用promptElement(element)函数
5. }catch(ignored){}
6. }
7. finished(); //执行完后,调用finished()函数
8. }
9. function promptElement(element) {

10. var id = element.id;
11. var text = element.innerText;
12. if(text.trim().length == 0){

13. text = element.value;
14. }
15. var name = element.getAttribute('name');
16. var className = element.className;
17. var tagName = element.tagName;
18. var attributes = "";
19. var htmlAttributes = element.attributes;
20. for (var i = 0, htmlAttribute; htmlAttribute = htmlAttributes[i]; i++){

21. attributes += htmlAttribute.name + "::" + htmlAttribute.value;
22. if (i + 1 < htmlAttributes.length) {

23. attributes += "#$";
24. }
25. }
26. var rect = element.getBoundingClientRect();
27. if(rect.width > 0 && rect.height > 0 && rect.left >= 0 && rect.top >= 0){

28. prompt(id + ';,' + text + ';,' + name + ";," + className + ";," + tagName + ";," + rect.left + ';,' + rect.top + ';,' + rect.width + ';,' + rect.height + ';,' + attributes); //弹出包含id、text、name等字段的提示框
29. }
30. }
31. function finished(){

32. prompt('robotium-finished'); //弹出包含robotium-finished字符串的提示框,用于标识脚本注入执行结束
33. }
 
  
javascript:
function allWebElements() {
        for (var key in document.all){
                try{
                        promptElement(document.all[key]);       //调用promptElement(element)函数            
                }catch(ignored){}
        }
        finished();    //执行完后,调用finished()函数
}

function promptElement(element) {
        var id = element.id;
        var text = element.innerText;
        if(text.trim().length == 0){
                text = element.value;
        }
        var name = element.getAttribute('name');
        var className = element.className;
        var tagName = element.tagName;
        var attributes = "";
        var htmlAttributes = element.attributes;
        for (var i = 0, htmlAttribute; htmlAttribute = htmlAttributes[i]; i++){
                attributes += htmlAttribute.name + "::" + htmlAttribute.value;
                if (i + 1 < htmlAttributes.length) {
                        attributes += "#$";
                }
        }

        var rect = element.getBoundingClientRect();
        if(rect.width > 0 && rect.height > 0 && rect.left >= 0 && rect.top >= 0){
                prompt(id + ';,' + text + ';,' + name + ";," + className + ";," + tagName + ";," + rect.left + ';,' + rect.top + ';,' + rect.width + ';,' + rect.height + ';,' + attributes);   //弹出包含id、text、name等字段的提示框
        }
}
function finished(){
        prompt('robotium-finished');    //弹出包含robotium-finished字符串的提示框,用于标识脚本注入执行结束
}



从脚本中可以看出JS获得页面元素后还进行了一定的格式化处理,在每个元素之间加了;,符号,这也是为了在后面代码中更加方便地解析。脚本的最后调用了finished()函数,即弹出包含robotium-finished的提示框。这一步完成了页面元素的获取,那么提示框中包含的内容在Android中怎么获取呢?






第二步:在Android中获取WebView中prompt提示框中的信息



在Android的Webkit包中有个WebChromeClient类,这个类中的onJsPrompt方法就是用于处理WebView中的提示框的,当WebView中有JS提示框时,会回调该方法,String message参数将包含提示框中的信息,因此robotium写了个继承自WebChromeClient类的RobotiumWebClient类。覆写了onJsPrompt



onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult

 



[java]


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

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

4.  //如果提示框中包含robotium-finished字符串,即表示那段JS注入脚本执行完毕了
5.  if(message.equals("robotium-finished")){

6.  webElementCreator.setFinished(true);
7. }
8. else{

9. webElementCreator.createWebElementAndAddInList(message, view);//有人提示框中的内容,那么就可以对提示框中的内容进行处理了
10. }
11. r.confirm();
12. return true;
13. }
14. else {

15. if(originalWebChromeClient != null) {

16. return originalWebChromeClient.onJsPrompt(view, url, message, defaultValue, r);
17. }
18. return true;
19. }
20. }
 
     
@Override
        public boolean onJsPrompt(WebView view, String url, String message,     String defaultValue, JsPromptResult r) {

                if(message != null && (message.contains(";,") || message.contains("robotium-finished"))){
                        //如果提示框中包含robotium-finished字符串,即表示那段JS注入脚本执行完毕了
                        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;
                }

        }


另外,原本的WebView默认是不允许执行JS的,因此需要先执行 enableJavascriptAndSetRobotiumWebClient方法。将JavaScriptEnabled设置为true,将将WebChromeClient设置为robotiumWebClient



 



[java]


1. public void enableJavascriptAndSetRobotiumWebClient(List webViews, WebChromeClient originalWebChromeClient){

2. this.originalWebChromeClient = originalWebChromeClient;
3. for(final WebView webView : webViews){

4. if(webView != null){

5. inst.runOnMainSync(new Runnable() {

6. public void run() {

7.  webView.getSettings().setJavaScriptEnabled(true);
8.  webView.setWebChromeClient(robotiumWebClient);
9. }
10. });
11. }
12. }
13. }
 
    
public void enableJavascriptAndSetRobotiumWebClient(List webViews, WebChromeClient originalWebChromeClient){
                this.originalWebChromeClient = originalWebChromeClient;

                for(final WebView webView : webViews){

                        if(webView != null){ 
                                inst.runOnMainSync(new Runnable() {
                                        public void run() {
                                                webView.getSettings().setJavaScriptEnabled(true);
                                                webView.setWebChromeClient(robotiumWebClient);

                                        }
                                });
                        }
                }
        }


第三步:将提示框中的消息存入WebElement Java bean中



       获取到了prompt提示框中的消息后,接下来就是对这些已经过处理含特殊格式的消息进行解析处理了,依次得到WebElement的id、text、name等字段。




element ui 源码解析 element ui原理_element ui 源码解析



 


[java]


1. private WebElement createWebElementAndSetLocation(String information, WebView webView){

2. String[] data = information.split(";,"); //将消息按;,符号分割,其中;,符号是在前面执行JS时加入的
3. String[] elements = null;
4. int x = 0;
5. int y = 0;
6. int width = 0;
7. int height = 0;
8. Hashtable attributes = new Hashtable();
9. try{

10. x = Math.round(Float.valueOf(data[5]));
11. y = Math.round(Float.valueOf(data[6]));
12. width = Math.round(Float.valueOf(data[7]));
13. height = Math.round(Float.valueOf(data[8]));
14. elements = data[9].split("\\#\\$");
15. }catch(Exception ignored){}
16. if(elements != null) {

17. for (int index = 0; index < elements.length; index++){

18. String[] element = elements[index].split("::");
19. if (element.length > 1) {

20. attributes.put(element[0], element[1]);
21. } else {

22. attributes.put(element[0], element[0]);
23. }
24. }
25. }
26. WebElement webElement = null;
27. try{

28. webElement = new WebElement(data[0], data[1], data[2], data[3], data[4], attributes);//将id、text、name等字段存入
29. setLocation(webElement, webView, x, y, width, height);
30. }catch(Exception ignored) {}
31. return webElement;
32. }
     
private WebElement createWebElementAndSetLocation(String information, WebView webView){                String[] data = information.split(";,");            //将消息按;,符号分割,其中;,符号是在前面执行JS时加入的
                String[] elements = null;
                int x = 0;
                int y = 0;
                int width = 0;
                int height = 0;
                Hashtable attributes = new Hashtable();
                try{
                        x = Math.round(Float.valueOf(data[5]));
                        y = Math.round(Float.valueOf(data[6]));
                        width = Math.round(Float.valueOf(data[7]));
                        height = Math.round(Float.valueOf(data[8]));    
                        elements = data[9].split("\\#\\$");
                }catch(Exception ignored){}

                if(elements != null) {
                        for (int index = 0; index < elements.length; index++){
                                String[] element = elements[index].split("::");
                                if (element.length > 1) {
                                        attributes.put(element[0], element[1]);
                                } else {
                                        attributes.put(element[0], element[0]);
                                }
                        }
                }

                WebElement webElement = null;

                try{
                        webElement = new WebElement(data[0], data[1], data[2], data[3], data[4], attributes);//将id、text、name等字段存入
                        setLocation(webElement, webView, x, y, width, height);
                }catch(Exception ignored) {}

                return webElement;
        }


[java]

1. private void setLocation(WebElement webElement, WebView webView, int x, int y, int width, int height ){

2. float scale = webView.getScale();
3. int[] locationOfWebViewXY = new int[2];
4.  webView.getLocationOnScreen(locationOfWebViewXY);
5. int locationX = (int) (locationOfWebViewXY[0] + (x + (Math.floor(width / 2))) * scale);
6. int locationY = (int) (locationOfWebViewXY[1] + (y + (Math.floor(height / 2))) * scale);
7. webElement.setLocationX(locationX);
8. webElement.setLocationY(locationY);
9. }
     
private void setLocation(WebElement webElement, WebView webView, int x, int y, int width, int height ){                float scale = webView.getScale();
                int[] locationOfWebViewXY = new int[2];
                webView.getLocationOnScreen(locationOfWebViewXY);

                int locationX = (int) (locationOfWebViewXY[0] + (x + (Math.floor(width / 2))) * scale);
                int locationY = (int) (locationOfWebViewXY[1] + (y + (Math.floor(height / 2))) * scale);

                webElement.setLocationX(locationX);
                webElement.setLocationY(locationY);
        }

     至此,WebElement对象中包含了id、text、name等字段,还包含了x、y坐标,知道了坐标后就可以像其它Android中的原生View一样根据坐标发送点击事件。



网页: http://www.robotium.cn/archives/239,也提到了一种方法:

public void testAddJsToWebView(){class ExecObj{
public void showMessage(){
Toast.makeText(solo.getCurrentActivity(), "注入js,由js处理并调用ExecObj对象的方法!", Toast.LENGTH_LONG).show();
}
}
solo.sleep(1000);
//获得webview控件
webView = (WebView) solo.getView(R.id.webView);
webView.loadUrl("http://www.robotium.cn");
solo.sleep(4000);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new ExecObj(), "injectedObject");
webView.loadUrl("javascript:injectedObject.showMessage()");
solo.sleep(10000);
}