mirror of
https://github.com/appium/appium.git
synced 2026-02-08 02:30:32 -06:00
Merge pull request #2712 from bootstraponline/dedupe_complex_find
Dedupe complex find
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user