在前面四篇笔记中基本完成了对Solo类大部分方法的记录,在这篇笔记中将对webView的操作做一个记录
还是老套路,先从获取所有的WebElement控件开始,Robotium主要是使用了一套JavaScript脚本,将脚本注入的网页中并执行响应的函数来获取Elements的,下面来看看代码
public WebElement getWebElement(By by, int index){
int match = index + 1;
WebElement webElement = waiter.waitForWebElement(by, match, Timeout.getSmallTimeout(), true);
if(webElement == null) {
if(match > 1){
Assert.fail(match + " WebElements with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' are not found!");
}
else {
Assert.fail("WebElement with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' is not found!");
}
}
return webElement;
}
这个方法主要是调用waitForElement(),继续往下看searcher.searchForWebElement()
这里也没有其他什么特别,一个计时器,然后调用searcher.searchForWebElement(by, minimumNumberOfMatches); 继续往下看,在这里这到了我想要的东西 getWebElement()
打开看到如下:
public ArrayList<WebElement> getWebElements(final By by, boolean onlySufficientlyVisbile){
boolean javaScriptWasExecuted = executeJavaScript(by, false);
if(config.useJavaScriptToClickWebElements){
if(!javaScriptWasExecuted){
return new ArrayList<WebElement>();
}
return webElementCreator.getWebElementsFromWebViews();
}
return getWebElements(javaScriptWasExecuted, onlySufficientlyVisbile);
}
这里这首先调用了executeJavaScript(by, false);
public boolean executeJavaScript(final By by, boolean shouldClick){
if(by instanceof By.Id){
return executeJavaScriptFunction("id(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
else if(by instanceof By.Xpath){
return executeJavaScriptFunction("xpath(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
else if(by instanceof By.CssSelector){
return executeJavaScriptFunction("cssSelector(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
else if(by instanceof By.Name){
return executeJavaScriptFunction("name(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
else if(by instanceof By.ClassName){
return executeJavaScriptFunction("className(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
else if(by instanceof By.Text){
return executeJavaScriptFunction("textContent(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
else if(by instanceof By.TagName){
return executeJavaScriptFunction("tagName(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
}
return false;
}
可以看出根据By类传入不同类型的参数执行不同的js方法
private boolean executeJavaScriptFunction(final String function){
final WebView webView = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(WebView.class, true));
if(webView == null){
return false;
}
final String javaScript = prepareForStartOfJavascriptExecution();
activityUtils.getCurrentActivity(false).runOnUiThread(new Runnable() {
public void run() {
if(webView != null){
webView.loadUrl("javascript:" + javaScript + function);
}
}
});
return true;
}
这个方法首先拿到一个webView对象,prepareForStartOfJavaScriptExecution()是将RobotiumWeb.js文件加载成字符串,并将RobotiumWebClient.class这个类的实例加载(这个类继承自google的WebChromeClient类,主要用来处理 prompt)
最后LoadUrl 将js字符串和函数拼接成了一个大的js脚本 形成类似这样的脚本: 调用 allWebElement()函数
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字符串的提示框,用于标识脚本注入执行结束
}
生成一个prompt 含有 id,text,name等信息,在这里刚才注册的WebChromeClient就起到作用了,他可以处理这个 prompt(当网页弹出prompt时 会自动调用onjsPrompt方法)
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(message, view);将prompt中的信息解析存贮到 webElements中.
public void createWebElementAndAddInList(String webData, WebView webView){
WebElement webElement = createWebElementAndSetLocation(webData, webView);
if((webElement!=null))
webElements.add(webElement);
}
private WebElement createWebElementAndSetLocation(String information, WebView webView){
String[] data = information.split(";,");
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]);
}
}
}
createWebElementAndSetLocation这个方法是将 prompt中具体的参数封装成一个 Element对象包括 id,name 坐标等等这个坐标应该是 Element相对于WebView的坐标
回到WebUtils的getWebElements方法,在最后调用getWebElements(javaScriptWasExecuted, onlySufficientlyVisbile)
private ArrayList<WebElement> getWebElements(boolean javaScriptWasExecuted, boolean onlySufficientlyVisbile){
ArrayList<WebElement> webElements = new ArrayList<WebElement>();
if(javaScriptWasExecuted){
for(WebElement webElement : webElementCreator.getWebElementsFromWebViews()){
if(!onlySufficientlyVisbile){
webElements.add(webElement);
}
else if(isWebElementSufficientlyShown(webElement)){
webElements.add(webElement);
}
}
}
return webElements;
}
getWebElements是筛选出当前可见的的Element并返回集合,就找到了需要的Element
下面来看看点击Element的操作,点击element其实和点击view的原理差不多,使用上面已经得到了element,中坐标属性就可以了
public void clickOnWebElement(By by, int match, boolean scroll, boolean useJavaScriptToClick){
WebElement webElement = null;
if(useJavaScriptToClick){
webElement = waiter.waitForWebElement(by, match, Timeout.getSmallTimeout(), false);
if(webElement == null){
Assert.fail("WebElement with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' is not found!");
}
webUtils.executeJavaScript(by, true);
return;
}
WebElement webElementToClick = waiter.waitForWebElement(by, match, Timeout.getSmallTimeout(), scroll);
if(webElementToClick == null){
if(match > 1) {
Assert.fail(match + " WebElements with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' are not found!");
}
else {
Assert.fail("WebElement with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' is not found!");
}
}
clickOnScreen(webElementToClick.getLocationX(), webElementToClick.getLocationY(), null);
}
关键的方法和view的click是一样的,其实最开始我在这里有一点不明白,就是获取element的坐标值,不知道是相对于webview还是相对于屏幕的,但是看到这里基本上可以判断这个坐标值是相对于屏幕的了.
Robotium的学习目前告一段落了,其实还有很多地方不是完全明白,可能也有很多说错的地方,希望大家直接指出来,欢迎拍砖