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()為例,
public ArrayList<WebElement> getWebElements(boolean onlySufficientlyVisible){boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);}
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: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 result) |
@Overridepublic 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
public void enableJavascriptAndSetRobotiumWebClient(List<WebView> 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等欄位。
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<String, String> attributes = new Hashtable<String, String>();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;}
/** * Sets the location of a {@code WebElement} * * @param webElement the {@code TextView} object to set location * @param webView the {@code WebView} the text is shown in * @param x the x location to set * @param y the y location to set * @param width the width to set * @param height the height to set */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一樣根據座標發送點擊事件。