Merge pull request #2712 from bootstraponline/dedupe_complex_find

Dedupe complex find
This commit is contained in:
Jonathan Lipps
2014-05-30 09:43:21 -07:00
10 changed files with 120 additions and 76 deletions

View File

@@ -1,14 +1,12 @@
package com.android.uiautomator.common;
import android.os.Build;
import com.android.uiautomator.core.UiDevice;
import io.appium.android.bootstrap.Logger;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.os.Build;
import com.android.uiautomator.core.UiDevice;
public class ReflectionUtils {
private static Field enableField(final Class<?> clazz, final String field)
throws SecurityException, NoSuchFieldException {
@@ -44,13 +42,15 @@ public class ReflectionUtils {
return controller;
}
public Method getMethod(final String name, final Class<?>... parameterTypes)
public Method getControllerMethod(final String name, final Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
final Class<?> controllerClass = controller.getClass();
return getMethod(controller.getClass(), name, parameterTypes);
}
Logger.debug("Finding methods on class: " + controllerClass);
final Method method;
method = controllerClass.getDeclaredMethod(name, parameterTypes);
public Method getMethod(final Class clazz, String name, final Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Logger.debug("Finding methods on class: " + clazz);
final Method method = clazz.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method;

View File

@@ -1,6 +1,7 @@
package io.appium.android.bootstrap;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
@@ -90,8 +91,8 @@ public class Dynamic {
return value;
}
public static ArrayList<String> finalize(
final ArrayList<AndroidElement> elements, final int finalizer)
public static List<String> finalize(
final List<AndroidElement> elements, final int finalizer)
throws Exception {
final ArrayList<String> results = new ArrayList<String>();
for (final AndroidElement e : elements) {

View File

@@ -1,52 +1,37 @@
package io.appium.android.bootstrap.handler;
import io.appium.android.bootstrap.AndroidCommand;
import io.appium.android.bootstrap.AndroidCommandResult;
import io.appium.android.bootstrap.AndroidElement;
import io.appium.android.bootstrap.AndroidElementsHash;
import io.appium.android.bootstrap.CommandHandler;
import io.appium.android.bootstrap.Dynamic;
import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.WDStatus;
import io.appium.android.bootstrap.exceptions.AndroidCommandException;
import io.appium.android.bootstrap.exceptions.ElementNotFoundException;
import io.appium.android.bootstrap.exceptions.ElementNotInHashException;
import io.appium.android.bootstrap.exceptions.InvalidStrategyException;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;
import io.appium.android.bootstrap.exceptions.UnallowedTagNameException;
import android.os.Build;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.*;
import io.appium.android.bootstrap.exceptions.*;
import io.appium.android.bootstrap.selector.Strategy;
import io.appium.android.bootstrap.utils.ElementHelpers;
import io.appium.android.bootstrap.utils.NotImportantViews;
import io.appium.android.bootstrap.utils.UiSelectorParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Build;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
/**
* This handler is used to find elements in the Android UI.
*
* <p/>
* Based on which {@link Strategy}, {@link UiSelector}, and optionally the
* contextId, the element Id or Ids are returned to the user.
*
*/
public class Find extends CommandHandler {
// These variables are expected to persist across executions.
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
public static JSONObject apkStrings = null;
UiSelectorParser uiSelectorParser = new UiSelectorParser();
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
public static JSONObject apkStrings = null;
UiSelectorParser uiSelectorParser = new UiSelectorParser();
/*
* @param command The {@link AndroidCommand} used for this handler.
@@ -104,7 +89,8 @@ public class Find extends CommandHandler {
try {
int finalizer = 0;
JSONArray pair = null;
final JSONArray results = new JSONArray();
List<AndroidElement> elementResults = new ArrayList<AndroidElement>();
final JSONArray jsonResults = new JSONArray();
// Start at 1 to skip over all.
for (int selIndex = all || scroll ? 1 : 0; selIndex < selectors
.length(); selIndex++) {
@@ -128,11 +114,11 @@ public class Find extends CommandHandler {
if (finalizer != 0) {
if (all) {
Logger.debug("Finding all with finalizer");
final ArrayList<AndroidElement> eles = elements.getElements(
List<AndroidElement> eles = elements.getElements(
sel, contextId);
Logger.debug("Elements found: " + eles);
for (final String found : Dynamic.finalize(eles, finalizer)) {
results.put(found);
jsonResults.put(found);
}
continue;
} else {
@@ -143,10 +129,8 @@ public class Find extends CommandHandler {
}
if (all) {
final ArrayList<AndroidElement> els = elements.getElements(sel,
contextId);
for (final AndroidElement el : els) {
results.put(new JSONObject().put("ELEMENT", el.getId()));
for (AndroidElement e : elements.getElements(sel, contextId)) {
elementResults.add(e);
}
continue;
} else if (scroll && canScroll) {
@@ -163,9 +147,16 @@ public class Find extends CommandHandler {
} catch (final ElementNotFoundException enf) {
Logger.debug("Not found.");
}
}
if (all && results.length() > 0) {
return getSuccessResult(results);
} // end for loop
if (all) {
// matching on multiple selectors may return duplicate elements
elementResults = ElementHelpers.dedupe(elementResults);
for (final AndroidElement el : elementResults) {
jsonResults.put(new JSONObject().put("ELEMENT", el.getId()));
}
return getSuccessResult(jsonResults);
}
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
"No such element.");
@@ -173,7 +164,7 @@ public class Find extends CommandHandler {
final String errorMessage = e.getMessage();
if (errorMessage != null
&& errorMessage
.contains("UiAutomationService not connected. Did you call #register()?")) {
.contains("UiAutomationService not connected. Did you call #register()?")) {
// Crash on not connected so Appium restarts the bootstrap jar.
throw new RuntimeException(e);
}
@@ -247,12 +238,11 @@ public class Find extends CommandHandler {
/**
* Get the element from the {@link AndroidElementsHash} and return the element
* id using JSON.
*
*
* @param sel
* A UiSelector that targets the element to fetch.
* A UiSelector that targets the element to fetch.
* @param contextId
* The Id of the element used for the context.
*
* The Id of the element used for the context.
* @return JSONObject
* @throws JSONException
* @throws ElementNotFoundException
@@ -268,7 +258,7 @@ public class Find extends CommandHandler {
/**
* Get a single element by its index and its parent indexes. Used to resolve
* an xpath query
*
*
* @param indexPath
* @return
* @throws ElementNotInHashException
@@ -295,12 +285,11 @@ public class Find extends CommandHandler {
/**
* Get an array of elements from the {@link AndroidElementsHash} and return
* the element's ids using JSON.
*
*
* @param sel
* A UiSelector that targets the element to fetch.
* A UiSelector that targets the element to fetch.
* @param contextId
* The Id of the element used for the context.
*
* The Id of the element used for the context.
* @return JSONObject
* @throws JSONException
* @throws UiObjectNotFoundException
@@ -320,12 +309,12 @@ public class Find extends CommandHandler {
/**
* Get a find element result by looking through the paths of indexes used to
* retrieve elements from an XPath search
*
*
* @param selector
* @return
*/
private AndroidCommandResult findElementsByIndexPaths(final String selector,
final Boolean multiple) {
final Boolean multiple) {
final ArrayList<String> indexPaths = new ArrayList<String>(
Arrays.asList(selector.split(",")));
final JSONArray resArray = new JSONArray();
@@ -354,20 +343,20 @@ public class Find extends CommandHandler {
/**
* Create and return a UiSelector based on the strategy, text, and how many
* you want returned.
*
*
* @param strategy
* The {@link Strategy} used to search for the element.
* The {@link Strategy} used to search for the element.
* @param text
* Any text used in the search (i.e. match, regex, etc.)
* Any text used in the search (i.e. match, regex, etc.)
* @param many
* Boolean that is either only one element (false), or many (true)
* Boolean that is either only one element (false), or many (true)
* @return UiSelector
* @throws InvalidStrategyException
* @throws AndroidCommandException
* @throws ElementNotFoundException
*/
private List<UiSelector> getSelector(final Strategy strategy,
final String text, final boolean many) throws InvalidStrategyException,
final String text, final boolean many) throws InvalidStrategyException,
AndroidCommandException, UnallowedTagNameException,
ElementNotFoundException, UiSelectorSyntaxException {
final List<UiSelector> selectors = new ArrayList<UiSelector>();

View File

@@ -42,7 +42,7 @@ public class LongPressKeyCode extends CommandHandler {
throws JSONException {
try {
final ReflectionUtils utils = new ReflectionUtils();
final Method injectEventSync = utils.getMethod("injectEventSync",
final Method injectEventSync = utils.getControllerMethod("injectEventSync",
InputEvent.class);
final Hashtable<String, Object> params = command.params();
keyCode = (Integer) params.get("keycode");

View File

@@ -68,7 +68,7 @@ public class MultiPointerGesture extends CommandHandler {
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
final ReflectionUtils utils = new ReflectionUtils();
final Method pmpg = utils.getMethod("performMultiPointerGesture",
final Method pmpg = utils.getControllerMethod("performMultiPointerGesture",
PointerCoords[][].class);
final Boolean rt = (Boolean) pmpg.invoke(utils.getController(),
(Object) pcs);

View File

@@ -19,7 +19,7 @@ public class TouchDown extends TouchEvent {
printEventDebugLine("TouchDown");
try {
final ReflectionUtils utils = new ReflectionUtils();
final Method touchDown = utils.getMethod("touchDown", int.class,
final Method touchDown = utils.getControllerMethod("touchDown", int.class,
int.class);
return (Boolean) touchDown.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {

View File

@@ -26,9 +26,9 @@ public class TouchLongClick extends TouchEvent {
*/
final ReflectionUtils utils = new ReflectionUtils();
final Method touchDown = utils.getMethod("touchDown", int.class,
final Method touchDown = utils.getControllerMethod("touchDown", int.class,
int.class);
final Method touchUp = utils.getMethod("touchUp", int.class, int.class);
final Method touchUp = utils.getControllerMethod("touchUp", int.class, int.class);
if ((Boolean) touchDown.invoke(utils.getController(), x, y)) {
SystemClock.sleep(duration);

View File

@@ -19,7 +19,7 @@ public class TouchMove extends TouchEvent {
printEventDebugLine("TouchMove");
try {
final ReflectionUtils utils = new ReflectionUtils();
final Method touchMove = utils.getMethod("touchMove", int.class,
final Method touchMove = utils.getControllerMethod("touchMove", int.class,
int.class);
return (Boolean) touchMove.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {

View File

@@ -19,7 +19,7 @@ public class TouchUp extends TouchEvent {
printEventDebugLine("TouchUp");
try {
final ReflectionUtils utils = new ReflectionUtils();
final Method touchUp = utils.getMethod("touchUp", int.class, int.class);
final Method touchUp = utils.getControllerMethod("touchUp", int.class, int.class);
return (Boolean) touchUp.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {
Logger.debug("Problem invoking touchUp: " + e);

View File

@@ -0,0 +1,54 @@
package io.appium.android.bootstrap.utils;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObject;
import io.appium.android.bootstrap.AndroidElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public abstract class ElementHelpers {
private static Method findAccessibilityNodeInfo;
private static AccessibilityNodeInfo elementToNode(AndroidElement element) {
AccessibilityNodeInfo result = null;
try {
result = (AccessibilityNodeInfo) findAccessibilityNodeInfo.invoke(element.getUiObject(), 5000L);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* Remove all duplicate elements from the provided list
*
* @param elements - elements to remove duplicates from
* @return a new list with duplicates removed
*/
public static List<AndroidElement> dedupe(List<AndroidElement> elements) {
try {
ReflectionUtils utils = new ReflectionUtils();
findAccessibilityNodeInfo = utils.getMethod(UiObject.class, "findAccessibilityNodeInfo", long.class);
} catch (Exception e) {
e.printStackTrace();
}
List<AndroidElement> result = new ArrayList<AndroidElement>();
List<AccessibilityNodeInfo> nodes = new ArrayList<AccessibilityNodeInfo>();
for (int index = 0; index < elements.size(); index++) {
AndroidElement element = elements.get(index);
AccessibilityNodeInfo node = elementToNode(element);
if (!nodes.contains(node)) {
nodes.add(node);
result.add(element);
}
}
return result;
}
}