package com.robotium.solo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.robotium.solo.Solo.Config;
import android.app.Instrumentation;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.TextView;
/**
* Contains web related methods. Examples are:
* enterTextIntoWebElement(), getWebTexts(), getWebElements().
*
* @author Renas Reda, renas.reda@robotium.com
*
*/
class WebUtils {
private ViewFetcher viewFetcher;
private Instrumentation inst;
RobotiumWebClient robotiumWebCLient;
WebElementCreator webElementCreator;
WebChromeClient originalWebChromeClient = null;
private Config config;
/**
* Constructs this object.
*
* @param config the {@code Config} instance
* @param instrumentation the {@code Instrumentation} instance
* @param viewFetcher the {@code ViewFetcher}
* @param sleeper the {@code Sleeper} instance
*/
public WebUtils(Config config, Instrumentation instrumentation, ViewFetcher viewFetcher, Sleeper sleeper){
this.config = config;
this.inst = instrumentation;
this.viewFetcher = viewFetcher;
webElementCreator = new WebElementCreator(sleeper);
robotiumWebCLient = new RobotiumWebClient(instrumentation, webElementCreator);
}
/**
* Returns {@code TextView} objects based on web elements shown in the present WebViews
*
* @param onlyFromVisibleWebViews true if only from visible WebViews
* @return an {@code ArrayList} of {@code TextViews}s created from the present {@code WebView}s
*/
public ArrayList<TextView> getTextViewsFromWebView(){
boolean javaScriptWasExecuted = executeJavaScriptFunction("allTexts();");
return createAndReturnTextViewsFromWebElements(javaScriptWasExecuted);
}
/**
* Creates and returns TextView objects based on WebElements
*
* @return an ArrayList with TextViews
*/
private ArrayList <TextView> createAndReturnTextViewsFromWebElements(boolean javaScriptWasExecuted){
ArrayList<TextView> webElementsAsTextViews = new ArrayList<TextView>();
if(javaScriptWasExecuted){
for(WebElement webElement : webElementCreator.getWebElementsFromWebViews()){
if(isWebElementSufficientlyShown(webElement)){
RobotiumTextView textView = new RobotiumTextView(inst.getContext(), webElement.getText(), webElement.getLocationX(), webElement.getLocationY());
webElementsAsTextViews.add(textView);
}
}
}
return webElementsAsTextViews;
}
/**
* Returns an ArrayList of WebElements currently shown in the active WebView.
*
* @param onlySufficientlyVisible true if only sufficiently visible {@link WebElement} objects should be returned
* @return an {@code ArrayList} of the {@link WebElement} objects shown in the active WebView
*/
public ArrayList<WebElement> getWebElements(boolean onlySufficientlyVisible){
boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");
return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
}
/**
* Returns an ArrayList of WebElements of the specified By object currently shown in the active WebView.
*
* @param by the By object. Examples are By.id("id") and By.name("name")
* @param onlySufficientlyVisible true if only sufficiently visible {@link WebElement} objects should be returned
* @return an {@code ArrayList} of the {@link WebElement} objects currently shown in the active WebView
*/
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);
}
/**
* Returns the sufficiently shown WebElements
*
* @param javaScriptWasExecuted true if JavaScript was executed
* @param onlySufficientlyVisible true if only sufficiently visible {@link WebElement} objects should be returned
* @return the sufficiently shown WebElements
*/
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;
}
/**
* Prepares for start of JavaScript execution
*
* @return the JavaScript as a String
*/
private String prepareForStartOfJavascriptExecution(List<WebView> webViews) {
webElementCreator.prepareForStart();
WebChromeClient currentWebChromeClient = getCurrentWebChromeClient();
if(currentWebChromeClient != null && !currentWebChromeClient.getClass().isAssignableFrom(RobotiumWebClient.class)){
originalWebChromeClient = currentWebChromeClient;
}
robotiumWebCLient.enableJavascriptAndSetRobotiumWebClient(webViews, originalWebChromeClient);
return getJavaScriptAsString();
}
/**
* Returns the current WebChromeClient through reflection
*
* @return the current WebChromeClient
*
*/
private WebChromeClient getCurrentWebChromeClient(){
WebChromeClient currentWebChromeClient = null;
Object currentWebView = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(WebView.class, true));
if (android.os.Build.VERSION.SDK_INT >= 16) {
try{
currentWebView = new Reflect(currentWebView).field("mProvider").out(Object.class);
}catch(IllegalArgumentException ignored) {}
}
try{
if (android.os.Build.VERSION.SDK_INT >= 19) {
Object mClientAdapter = new Reflect(currentWebView).field("mContentsClientAdapter").out(Object.class);
currentWebChromeClient = new Reflect(mClientAdapter).field("mWebChromeClient").out(WebChromeClient.class);
}
else {
Object mCallbackProxy = new Reflect(currentWebView).field("mCallbackProxy").out(Object.class);
currentWebChromeClient = new Reflect(mCallbackProxy).field("mWebChromeClient").out(WebChromeClient.class);
}
}catch(Exception ignored){}
return currentWebChromeClient;
}
/**
* Enters text into a web element using the given By method
*
* @param by the By object e.g. By.id("id");
* @param text the text to enter
*/
public void enterTextIntoWebElement(final By by, final String text){
if(by instanceof By.Id){
executeJavaScriptFunction("enterTextById(\""+by.getValue()+"\", \""+text+"\");");
}
else if(by instanceof By.Xpath){
executeJavaScriptFunction("enterTextByXpath(\""+by.getValue()+"\", \""+text+"\");");
}
else if(by instanceof By.CssSelector){
executeJavaScriptFunction("enterTextByCssSelector(\""+by.getValue()+"\", \""+text+"\");");
}
else if(by instanceof By.Name){
executeJavaScriptFunction("enterTextByName(\""+by.getValue()+"\", \""+text+"\");");
}
else if(by instanceof By.ClassName){
executeJavaScriptFunction("enterTextByClassName(\""+by.getValue()+"\", \""+text+"\");");
}
else if(by instanceof By.Text){
executeJavaScriptFunction("enterTextByTextContent(\""+by.getValue()+"\", \""+text+"\");");
}
else if(by instanceof By.TagName){
executeJavaScriptFunction("enterTextByTagName(\""+by.getValue()+"\", \""+text+"\");");
}
}
/**
* Executes JavaScript determined by the given By object
*
* @param by the By object e.g. By.id("id");
* @param shouldClick true if click should be performed
* @return true if JavaScript function was executed
*/
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;
}
/**
* Executes the given JavaScript function
*
* @param function the function as a String
* @return true if JavaScript function was executed
*/
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;
}
private String setWebFrame(String javascript){
String frame = config.webFrame;
if(frame.isEmpty() || frame.equals("document")){
return javascript;
}
javascript = javascript.replaceAll(Pattern.quote("document, "), "document.getElementById(\""+frame+"\").contentDocument, ");
javascript = javascript.replaceAll(Pattern.quote("document.body, "), "document.getElementById(\""+frame+"\").contentDocument, ");
return javascript;
}
/**
* Returns true if the view is sufficiently shown
*
* @param view the view to check
* @return true if the view is sufficiently shown
*/
public final boolean isWebElementSufficientlyShown(WebElement webElement){
final WebView webView = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(WebView.class, true));
final int[] xyWebView = new int[2];
if(webView != null && webElement != null){
webView.getLocationOnScreen(xyWebView);
if(xyWebView[1] + webView.getHeight() > webElement.getLocationY())
return true;
}
return false;
}
/**
* Splits a name by upper case.
*
* @param name the name to split
* @return a String with the split name
*
*/
public String splitNameByUpperCase(String name) {
String [] texts = name.split("(?=\\p{Upper})");
StringBuilder stringToReturn = new StringBuilder();
for(String string : texts){
if(stringToReturn.length() > 0) {
stringToReturn.append(" " + string.toLowerCase());
}
else {
stringToReturn.append(string.toLowerCase());
}
}
return stringToReturn.toString();
}
/**
* Returns the JavaScript file RobotiumWeb.js as a String
*
* @return the JavaScript file RobotiumWeb.js as a {@code String}
*/
private String getJavaScriptAsString() {
InputStream fis = getClass().getResourceAsStream("RobotiumWeb.js");
StringBuffer javaScript = new StringBuffer();
try {
BufferedReader input = new BufferedReader(new InputStreamReader(fis));
String line = null;
while (( line = input.readLine()) != null){
javaScript.append(line);
javaScript.append("\n");
}
input.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return javaScript.toString();
}
}