Skip to content

Commit

Permalink
added UiScrollable support to -android uiautomator locator strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonahss committed Jun 11, 2014
1 parent e365cdb commit 13d38bb
Show file tree
Hide file tree
Showing 6 changed files with 562 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
import io.appium.android.bootstrap.exceptions.ElementNotFoundException;
import io.appium.android.bootstrap.exceptions.InvalidStrategyException;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;
import io.appium.android.bootstrap.exceptions.UnallowedTagNameException;
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 io.appium.android.bootstrap.utils.UiAutomatorParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
Expand All @@ -35,7 +34,7 @@ public class Find extends CommandHandler {
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
static JSONObject apkStrings = null;
UiSelectorParser uiSelectorParser = new UiSelectorParser();
UiAutomatorParser uiAutomatorParser = new UiAutomatorParser();

/*
* @param command The {@link AndroidCommand} used for this handler.
Expand Down Expand Up @@ -191,29 +190,33 @@ public AndroidCommandResult execute(final AndroidCommand command)

try {
Object result = null;
final JSONArray array = new JSONArray();
for (final UiSelector sel : getSelector(strategy, text, multiple)) {
// With multiple selectors, we expect that some elements may not
// exist.
try {
if (!multiple) {
List<UiSelector> selectors = getSelectors(strategy, text, multiple);

if (!multiple) {
for (final UiSelector sel : selectors) {
try {
result = fetchElement(sel, contextId);
// Return first element when multiple is false.
if (result != null) {
break;
}
} else {
final JSONArray results = fetchElements(sel, contextId);
for (int a = 0, len = results.length(); a < len; a++) {
array.put(results.get(a));
}
} catch (final ElementNotFoundException e) {
}
if (result != null) {
break;
}
} catch (final ElementNotFoundException e) {
}
}

if (multiple) {
result = array;
} else {
List<AndroidElement> foundElements = new ArrayList<AndroidElement>();
for (final UiSelector sel : selectors) {
// With multiple selectors, we expect that some elements may not
// exist.
try {
List<AndroidElement> elementsFromSelector = fetchElements(sel, contextId);
foundElements.addAll(elementsFromSelector);
} catch (final UiObjectNotFoundException e) {
}
}
if (strategy == Strategy.ANDROID_UIAUTOMATOR) {
foundElements = ElementHelpers.dedupe(foundElements);
}
result = elementsToJSONArray(foundElements);
}

// If there are no results, then return an error.
Expand All @@ -227,8 +230,6 @@ public AndroidCommandResult execute(final AndroidCommand command)
return getErrorResult(e.getMessage());
} catch (final UiSelectorSyntaxException e) {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
} catch (final ElementNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
}
Expand Down Expand Up @@ -280,23 +281,32 @@ private JSONObject fetchElementByIndexPath(final String indexPath)
}

/**
* Get an array of elements from the {@link AndroidElementsHash} and return
* the element's ids using JSON.
* Get an array of AndroidElement objects from the {@link AndroidElementsHash}
*
* @param sel
* A UiSelector that targets the element to fetch.
* @param contextId
* The Id of the element used for the context.
* @return JSONObject
* @throws JSONException
* @return ArrayList<AndroidElement>
* @throws UiObjectNotFoundException
*/
private JSONArray fetchElements(final UiSelector sel, final String contextId)
throws JSONException, UiObjectNotFoundException {
private ArrayList<AndroidElement> fetchElements(final UiSelector sel, final String contextId)
throws UiObjectNotFoundException {

return elements.getElements(sel, contextId);
}

/**
* Get a JSONArray to represent a collection of AndroidElements
*
* @param els collection of AndroidElement objects
* @return elements in the format which appium server returns
* @throws JSONException
*/
private JSONArray elementsToJSONArray(List<AndroidElement> els) throws JSONException {
final JSONArray resArray = new JSONArray();
final ArrayList<AndroidElement> els = elements.getElements(sel, contextId);
for (final AndroidElement el : els) {
resArray.put(new JSONObject().put("ELEMENT", el.getId()));
for (AndroidElement el : els) {
resArray.put(ElementHelpers.toJSON(el));
}
return resArray;
}
Expand Down Expand Up @@ -346,8 +356,8 @@ private AndroidCommandResult findElementsByIndexPaths(final String selector,
* @throws InvalidStrategyException
* @throws ElementNotFoundException
*/
private List<UiSelector> getSelector(final Strategy strategy,
final String text, final boolean many) throws InvalidStrategyException,
private List<UiSelector> getSelectors(final Strategy strategy,
final String text, final boolean many) throws InvalidStrategyException,
ElementNotFoundException, UiSelectorSyntaxException {
final List<UiSelector> selectors = new ArrayList<UiSelector>();
UiSelector sel = new UiSelector();
Expand Down Expand Up @@ -403,16 +413,18 @@ private List<UiSelector> getSelector(final Strategy strategy,
selectors.add(sel);
break;
case ANDROID_UIAUTOMATOR:
List<UiSelector> parsedSelectors = new ArrayList<UiSelector>();
try {
sel = uiSelectorParser.parse(text);
parsedSelectors = uiAutomatorParser.parse(text);
} catch (final UiSelectorSyntaxException e) {
throw new UiSelectorSyntaxException(
"Could not parse UiSelector argument: " + e.getMessage());
}
if (!many) {
sel = sel.instance(0);

for (UiSelector selector : parsedSelectors) {
selectors.add(selector);
}
selectors.add(sel);

break;
case LINK_TEXT:
case PARTIAL_LINK_TEXT:
Expand Down Expand Up @@ -460,4 +472,4 @@ private UiSelector stringsXmlId(final boolean many, String text)
}
return sel;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObject;
import io.appium.android.bootstrap.AndroidElement;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Method;
import java.util.ArrayList;
Expand Down Expand Up @@ -50,4 +52,13 @@ public static List<AndroidElement> dedupe(List<AndroidElement> elements) {

return result;
}

/**
* Return the JSONObject which Appium returns for an element
*
* For example, appium returns elements like [{"ELEMENT":1}, {"ELEMENT":2}]
*/
public static JSONObject toJSON(AndroidElement el) throws JSONException {
return new JSONObject().put("ELEMENT", el.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.appium.android.bootstrap.utils;

import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;

import java.util.ArrayList;
import java.util.List;

/**
* For parsing strings passed in for the "-android uiautomator" locator strategy
*/
public class UiAutomatorParser {

private String text;
private List<UiSelector> selectors;
private UiScrollableParser scrollableParser = new UiScrollableParser();
private UiSelectorParser selectorParser = new UiSelectorParser();

public List<UiSelector> parse(String textToParse) throws UiSelectorSyntaxException {
if (textToParse.isEmpty()) {
throw new UiSelectorSyntaxException("Tried to parse an empty string. Expected to see a string consisting of text to be interpreted as UiAutomator java code.");
}
selectors = new ArrayList<UiSelector>();
text = textToParse.trim();
removeTailingSemicolon();
trimWhitespace();

consumeStatement();
while (text.length() > 0) {
trimWhitespace();
consumeSemicolon();
trimWhitespace();
consumeStatement();
}

return selectors;
}

private void trimWhitespace() {
text = text.trim();
}

private void removeTailingSemicolon() {
if (text.charAt(text.length()-1) == ';') {
text = text.substring(0, text.length()-1);
}
}

private void consumeSemicolon() throws UiSelectorSyntaxException {
if (text.charAt(0) != ';') {
throw new UiSelectorSyntaxException("Expected ';' but saw '" + text.charAt(0) +"'");
}

text = text.substring(1);
}

private void consumeStatement() throws UiSelectorSyntaxException {
String statement;
int index = 0;
int parenCount = -1; // semicolons could appear inside String arguments, so we make sure we only count occurrences outside of a parenthesis pair
while (index < text.length()) {
if (text.charAt(index) == ';' && parenCount == 0) {
break;
}
if (text.charAt(index) == '(') {
if (parenCount < 0) {
parenCount = 1;
} else {
parenCount++;
}
}
if (text.charAt(index) == ')') {
parenCount--;
}
index++;
}

statement = text.substring(0, index);
if (UiScrollableParser.isUiScrollable(statement)) {
selectors.add(scrollableParser.parse(statement));
} else {
selectors.add(selectorParser.parse(statement));
}

text = text.substring(index);
}

}
Loading

0 comments on commit 13d38bb

Please sign in to comment.